Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Structure Block and Structure Void #6045

Open
wants to merge 22 commits into
base: minor-next
Choose a base branch
from

Conversation

Bqleine
Copy link

@Bqleine Bqleine commented Sep 8, 2023

Introduction

This pull request is looking to implement the structure void, and possibly the structure block as it is closely related.

I personally don't have the knowledge required to implement the full structure block, but I would like to get it working at least as an inanimate place-able block.

Relevant issues

Changes

This is a non-disruptive addition of blocks.

Tasks

  • Register structure void
    • Define block states (defaults as void)
    • Implement serialization
  • Register structure block
    • Implement interactions with the block
    • Define block states (defaults as save mode)
    • Implement serialization
    • Create tile

Left for a future PR:

  • Add events related to structure block modification?
  • Create permission for using structure block?
  • Plugin API
  • saving/loading of structures to disk (probably better left for another PR for full structure API)
  • Link block and tile type
  • Unit tests

Bugs

  • Falling blocks landing inside a structure void should replace it instead of dropping
  • The air variant of structure voids gets placed as a void variant in vanilla
  • Sneaking and right clicking to create a structure block on another while flying will create a ghost block instead

Notes

I have used arbitrary values in BlockTypeIds.php, as there is a gap there I am not sure if it should have used a different number inside the gap.
I have used blast resistance values as indicated in the minecraft wiki, the block is indestructible, does it really matter ?
Right now, both blocks appear as update blocks to the client. I am not sure why, is the block state mandatory for them to appear correctly ?

Tests

Structure Void

  • /give <player> structure_void
  • place it
  • save the world

Structure Block

plugin.yml

---
name: StructureBlockTests
version: 0.0.1
main: Baleine\StructureBlockTests\Main
api: 5.5.1
src-namespace-prefix: Baleine\StructureBlockTests
commands:
  test:
    usage: "/test"
    description: "Test"
    permission: command.test
permissions:
  command.test:
    default: true
...

Main.php

<?php

declare(strict_types=1);

namespace Baleine\StructureBlockTests;

use pocketmine\block\StructureBlock;
use pocketmine\block\tile\StructureBlock as TileStructureBlock;
use pocketmine\block\utils\StructureAnimationMode;
use pocketmine\block\utils\StructureAxes;
use pocketmine\block\utils\StructureBlockType;
use pocketmine\block\utils\StructureRotation;
use pocketmine\command\Command;
use pocketmine\command\CommandSender;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\plugin\PluginBase;

class Main extends PluginBase{
	/**
	 * @param string[] $args
	 */
	public function onCommand(CommandSender $sender, Command $command, string $label, array $args) : bool{
		if ($sender instanceof Player){
			switch ($command->getName()){
				case "test":
					var_dump($sender->getPosition());
					$world = $sender->getPosition()->getWorld();
					$position = $sender->getPosition()->add(0, -1, 0);
					$block = $world->getBlock($position);
					$tile = $world->getTile($position);
					if (!($block instanceof StructureBlock)) return false;
					if (!($tile instanceof TileStructureBlock)) return false;
					$tile->setType(StructureBlockType::LOAD);
					$tile->setShowBoundingBox(true);
					$tile->setStructureSize(new Vector3(16, 255, 16));
					$tile->setStructureOffset(new Vector3(0, -$position->getY(), 0));
					$tile->setAnimationMode(StructureAnimationMode::BY_BLOCK);
					$tile->setAnimationSeconds(5);
					$tile->setIgnoreEntities(true);
					$tile->setIntegrity(45.8);
					$tile->setIntegritySeed(76876867);
					$tile->setMirroredAxes(new StructureAxes(false, true));
					$tile->setRemoveBlocks(false);
					$tile->setRotation(new StructureRotation(180));
					$tile->setStructureName("Test123");
					$block->setType(StructureBlockType::LOAD);
					$world->setBlock($position, $block);
					break;
			}
		}
		return true;
	}
}

Place a structure block and stand on top of it, then execute the /test command and check that the parameters have been updated.
Problem: if $block->setType is not called the block will not visibly update (it calls setBlock). I am not sure how to go about this.
Plugins should call setBlock themselves after changing block properties.

@dktapps
Copy link
Member

dktapps commented Sep 9, 2023

I have used arbitrary values in BlockTypeIds.php, as there is a gap there I am not sure if it should have used a different number inside the gap.

The values can be basically anything. The usual way is to put them at the bottom and increase the FIRST_UNUSED_TYPE_ID constant (or whatever it's called, I forget) to accommodate. However, putting them in a gap will also work.

Right now, both blocks appear as update blocks to the client. I am not sure why, is the block state mandatory for them to appear correctly ?

You haven't registered them in BlockStateToObjectDeserializer and BlockObjectToStateSerializer.

@Bqleine
Copy link
Author

Bqleine commented Sep 9, 2023

After having implemented block states and serialization/deserialization. I'm getting this error when using the block:

[Server thread/CRITICAL]: pocketmine\utils\AssumptionFailedError: "Unmapped blockstate returned by blockstate serializer: TAG_Compound={ "name" => TAG_String="minecraft:structure_void" "version" => TAG_Int=18090528 "states" => TAG_Compound={ "structure_void_type" => TAG_Int=0 } "PMMPDataVersion" => TAG_Long=1 }" (EXCEPTION) in "pmsrc/src/network/mcpe/convert/ItemTranslator" at line 80
--- Stack trace ---
  #0 pmsrc/src/network/mcpe/convert/ItemTranslator(57): pocketmine\network\mcpe\convert\ItemTranslator->toNetworkId(object pocketmine\item\ItemBlock#148201)
  #1 pmsrc/src/network/mcpe/convert/TypeConverter(207): pocketmine\network\mcpe\convert\ItemTranslator->toNetworkIdQuiet(object pocketmine\item\ItemBlock#148201)
  #2 pmsrc/src/network/mcpe/InventoryManager(540): pocketmine\network\mcpe\convert\TypeConverter->coreItemStackToNet(object pocketmine\item\ItemBlock#148201)
  #3 pmsrc/src/network/mcpe/InventoryManager(561): pocketmine\network\mcpe\InventoryManager->syncContents(object pocketmine\inventory\PlayerInventory#160238)
  #4 pmsrc/src/network/mcpe/handler/PreSpawnPacketHandler(141): pocketmine\network\mcpe\InventoryManager->syncAll()
  #5 pmsrc/src/network/mcpe/NetworkSession(326): pocketmine\network\mcpe\handler\PreSpawnPacketHandler->setUp()
  #6 pmsrc/src/network/mcpe/NetworkSession(820): pocketmine\network\mcpe\NetworkSession->setHandler(object pocketmine\network\mcpe\handler\PreSpawnPacketHandler#160390)
  #7 pmsrc/src/network/mcpe/NetworkSession(277): pocketmine\network\mcpe\NetworkSession->beginSpawnSequence()
  #8 pmsrc/src/promise/Promise(45): pocketmine\network\mcpe\NetworkSession->onPlayerCreated(object pocketmine\player\Player#167479)
  #9 pmsrc/src/network/mcpe/NetworkSession(234): pocketmine\promise\Promise->onCompletion(object Closure#167480, object Closure#167559)
  #10 pmsrc/src/network/mcpe/NetworkSession(815): pocketmine\network\mcpe\NetworkSession->createPlayer()
  #11 pmsrc/src/network/mcpe/handler/ResourcePacksPacketHandler(142): pocketmine\network\mcpe\NetworkSession->pocketmine\network\mcpe\{closure}()
  #12 pmsrc/vendor/pocketmine/bedrock-protocol/src/ResourcePackClientResponsePacket(61): pocketmine\network\mcpe\handler\ResourcePacksPacketHandler->handleResourcePackClientResponse(object pocketmine\network\mcpe\protocol\ResourcePackClientResponsePacket#167671)
  #13 pmsrc/src/network/mcpe/NetworkSession(445): pocketmine\network\mcpe\protocol\ResourcePackClientResponsePacket->handle(object pocketmine\network\mcpe\handler\ResourcePacksPacketHandler#167540)
  #14 pmsrc/src/network/mcpe/NetworkSession(383): pocketmine\network\mcpe\NetworkSession->handleDataPacket(object pocketmine\network\mcpe\protocol\ResourcePackClientResponsePacket#167671, string[4] ....)
  #15 pmsrc/src/network/mcpe/raklib/RakLibInterface(219): pocketmine\network\mcpe\NetworkSession->handleEncoded(string[7]
c.`a`..)
  #16 pmsrc/vendor/pocketmine/raklib-ipc/src/RakLibToUserThreadMessageReceiver(40): pocketmine\network\mcpe\raklib\RakLibInterface->onPacketReceive(int 0, string[16] ....et...\^+`.[.)
  #17 pmsrc/src/network/mcpe/raklib/RakLibInterface(111): raklib\server\ipc\RakLibToUserThreadMessageReceiver->handle(object pocketmine\network\mcpe\raklib\RakLibInterface#168038)
  #18 pmsrc/vendor/pocketmine/snooze/src/SleeperHandler(120): pocketmine\network\mcpe\raklib\RakLibInterface->pocketmine\network\mcpe\raklib\{closure}()
  #19 pmsrc/src/TimeTrackingSleeperHandler(58): pocketmine\snooze\SleeperHandler->processNotifications()
  #20 pmsrc/vendor/pocketmine/snooze/src/SleeperHandler(79): pocketmine\TimeTrackingSleeperHandler->processNotifications()
  #21 pmsrc/src/Server(1681): pocketmine\snooze\SleeperHandler->sleepUntil(float 1694260926.2298)
  #22 pmsrc/src/Server(1064): pocketmine\Server->tickProcessor()
  #23 pmsrc/src/PocketMine(334): pocketmine\Server->__construct(object pocketmine\thread\ThreadSafeClassLoader#3, object pocketmine\utils\MainLogger#6, string[30] /home/bloup/Pocketmine-Source/, string[38] /home/bloup/Pocketmine-Source/plugins/)
  #24 pmsrc/src/PocketMine(357): pocketmine\server()
--- End of exception information ---

It seems these mappings are taken directly from bedrock data though. Have I used an incorrect constant ?

@Bqleine
Copy link
Author

Bqleine commented Sep 9, 2023

I have found the issue, I was using 0 and 1 for values instead of constant strings from BlockStateStringValues.

@Bqleine
Copy link
Author

Bqleine commented Sep 15, 2023

I think this is ready for an initial review. I've added a plugin to test in the pr description :)

@Bqleine Bqleine marked this pull request as ready for review September 15, 2023 21:53
Copy link
Contributor

@zSALLAZAR zSALLAZAR left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These functions should not be nullable

src/data/bedrock/block/convert/BlockStateReader.php Outdated Show resolved Hide resolved
src/data/bedrock/block/convert/BlockStateReader.php Outdated Show resolved Hide resolved
Copy link
Member

@ShockedPlot7560 ShockedPlot7560 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some phpstan and style correction need to be adresses, but the logic looks good

src/block/tile/StructureBlock.php Outdated Show resolved Hide resolved
src/network/mcpe/handler/InGamePacketHandler.php Outdated Show resolved Hide resolved
@ShockedPlot7560 ShockedPlot7560 added Category: Gameplay Related to Minecraft gameplay experience Status: Waiting on Author Type: Enhancement Contributes features or other improvements to PocketMine-MP labels Oct 6, 2023
@Bqleine
Copy link
Author

Bqleine commented Oct 13, 2023

I've done all the changes I wanted to do for this PR, the two blocks are added and the structure block can be interacted with and changes to structure settings are saved.
The rest of the implementation requires creating a plugin api, so I am leaving this work for a future PR, as written on this PR's description.
One last thing remains to be done, and that is fixing the bug shown in unit tests, but I don't understand what the issue is. Some help would be greatly appreciated.

Copy link
Member

@dktapps dktapps left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR is trying to do multiple things at once. Most of the Structure Void stuff is fine, but the majority of the PR is Structure Block stuff which I find has been implemented poorly. Please split the two blocks into separate PRs.

src/block/StructureBlock.php Outdated Show resolved Hide resolved
src/block/StructureBlock.php Outdated Show resolved Hide resolved
if ($player instanceof Player) {
if (!$player->isCreative(true)) return false;
// the 0 is dubious but any value works
$pk = ContainerOpenPacket::blockInv(0, WindowTypes::STRUCTURE_EDITOR, BlockPosition::fromVector3($this->getPosition()));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not like this. Packets shouldn't be directly baked into the core code like this.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I create an Inventory extending BlockInventory and TemporaryInventory with no slots then ? Sounds odd

5 => self::EXPORT,
default => throw new \ValueError("Unknown structure block type " . $type),
};
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These should not be visible in the API. The general way to handle these would be a map in pocketmine\data\bedrock.

self::INVALID => 4,
self::EXPORT => 5,
};
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same again here.


enum StructureVoidType{
case VOID;
case AIR;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the difference between these two types?

Copy link
Author

@Bqleine Bqleine Nov 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the minecraft wiki:

It exists as a data value of structure voids. It acts similarly to structure voids. When holding a structure air or structure void, it becomes visible. Structure air and structure voids can stack together, the icon of the stack is the icon of whichever block was most recently added to the stack. Transparent blocks can be placed on them just like with structure voids. In the inventory, structure air is called structure void. If you give yourself a structure air then whenever you hold a structure void it will look like a structure air in your hand in both third and first person. Structure air are copied by structure blocks. This makes structure air very useful so always use structure air for your creative builds instead of structure voids. Note: structure air becomes structure voids when placed by hand, but if placed with /setblock ~ ~ ~ structure_void ["structure_void_type":"air"] it will stay as a structure air.

I was not able to completely verify this information, the difference that matters in this pr is that vanilla minecraft will forbid you from placing structure airs, and in this pr you can place them fine.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And you can't stack two different types in the pr

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The wiki entry is gibberish that conveys no actual information. That's why I asked.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The difference is that structure air will replace whatever block is at that position with an air block, while structure void will just leave it be

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's exactly what normal air does ...

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Depends on the structure block settings iirc

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Meaning what?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking about the Remove Blocks setting, but it doesn't help (its just another weird setting).

After testing on 1.20.41.02, I was unable to find a useful difference between structure air and structure void. I was able to reproduce normal behavior with both:

Structure voids can be placed as part of a structure and are ignored when the structure file is loaded. This results in the blocks at the location where the structure is placed being maintained and not being overridden.

This is exactly what happened :

For example, if a player saves a 2 block high structure with air at the top and a structure void at the bottom, and loads it into a 2 block high area of stone, the top block becomes air but the bottom stays stone.

What did not happen:

  • "Structure air are copied by structure blocks."

TL;DR I was not able to find any difference in the structure block usage. I left a comment on the wiki page

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you try pasting structures underwater with this? I wonder if there's a difference in the water displacement behaviour.

/**
* @return $this
*/
public function setXMirror(bool $xMirror) : self{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, why is this mutable?

/**
* @return $this
*/
public function setYMirror(bool $yMirror) : self{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And again here

2 => self::BY_BLOCK,
default => throw new \ValueError("Unknown structure animation mode " . $mode),
};
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These should be handled in pocketmine\data

self::BY_LAYER => 1,
self::BY_BLOCK => 2,
};
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and again here

@dktapps dktapps added the Multiple changes PR contains multiple changes and requires separation label Nov 9, 2023
@Bqleine
Copy link
Author

Bqleine commented Nov 9, 2023

Thank you for the complete review, I will make a separate PR for the structure void.
I agree that the quality of the code is not optimal, this is my first PR and I am still learning about the inner workings of the codebase, I will take some time in the coming days to improve on the points you commented. 👍

@Bqleine Bqleine mentioned this pull request Nov 12, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Category: Gameplay Related to Minecraft gameplay experience Multiple changes PR contains multiple changes and requires separation Status: Waiting on Author Type: Enhancement Contributes features or other improvements to PocketMine-MP
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants