beaedc36创建于 2025年8月29日历史提交
<?php

/*
 *
 *  ____            _        _   __  __ _                  __  __ ____
 * |  _ \ ___   ___| | _____| |_|  \/  (_)_ __   ___      |  \/  |  _ \
 * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
 * |  __/ (_) | (__|   <  __/ |_| |  | | | | | |  __/_____| |  | |  __/
 * |_|   \___/ \___|_|\_\___|\__|_|  |_|_|_| |_|\___|     |_|  |_|_|
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * @author PocketMine Team
 * @link http://www.pocketmine.net/
 *
 *
 */

declare(strict_types=1);

namespace pocketmine\data\bedrock\block\convert\property;

use pocketmine\block\Button;
use pocketmine\block\Door;
use pocketmine\block\DoublePlant;
use pocketmine\block\FenceGate;
use pocketmine\block\ItemFrame;
use pocketmine\block\Liquid;
use pocketmine\block\SimplePressurePlate;
use pocketmine\block\Slab;
use pocketmine\block\Stair;
use pocketmine\block\Stem;
use pocketmine\block\Torch;
use pocketmine\block\Trapdoor;
use pocketmine\block\utils\Ageable;
use pocketmine\block\utils\AnalogRedstoneSignalEmitter;
use pocketmine\block\utils\AnyFacing;
use pocketmine\block\utils\Colored;
use pocketmine\block\utils\CopperMaterial;
use pocketmine\block\utils\CopperOxidation;
use pocketmine\block\utils\CoralMaterial;
use pocketmine\block\utils\CoralType;
use pocketmine\block\utils\DyeColor;
use pocketmine\block\utils\HorizontalFacing;
use pocketmine\block\utils\Lightable;
use pocketmine\block\utils\MultiAnyFacing;
use pocketmine\block\utils\PillarRotation;
use pocketmine\block\utils\SignLikeRotation;
use pocketmine\block\utils\SlabType;
use pocketmine\block\Wall;
use pocketmine\block\Wood;
use pocketmine\data\bedrock\block\BlockLegacyMetadata;
use pocketmine\data\bedrock\block\BlockStateNames as StateNames;
use pocketmine\data\bedrock\block\BlockStateStringValues;
use pocketmine\math\Facing;
use pocketmine\utils\SingletonTrait;

final class CommonProperties{
	use SingletonTrait;

	/** @phpstan-var ValueFromStringProperty<AnyFacing, int> */
	public readonly ValueFromStringProperty $blockFace;
	/** @phpstan-var ValueFromStringProperty<PillarRotation, int> */
	public readonly ValueFromStringProperty $pillarAxis;
	/** @phpstan-var ValueFromStringProperty<Torch, int> */
	public readonly ValueFromStringProperty $torchFacing;

	/** @phpstan-var ValueFromStringProperty<HorizontalFacing, int> */
	public readonly ValueFromStringProperty $horizontalFacingCardinal;
	/** @phpstan-var ValueFromIntProperty<HorizontalFacing, int> */
	public readonly ValueFromIntProperty $horizontalFacingSWNE;
	/** @phpstan-var ValueFromIntProperty<HorizontalFacing, int> */
	public readonly ValueFromIntProperty $horizontalFacingSWNEInverted;
	/** @phpstan-var ValueFromIntProperty<HorizontalFacing, int> */
	public readonly ValueFromIntProperty $horizontalFacingClassic;

	/** @phpstan-var ValueFromIntProperty<AnyFacing, int> */
	public readonly ValueFromIntProperty $anyFacingClassic;

	/** @phpstan-var ValueSetFromIntProperty<MultiAnyFacing, int> */
	public readonly ValueSetFromIntProperty $multiFacingFlags;

	/** @phpstan-var IntProperty<SignLikeRotation> */
	public readonly IntProperty $floorSignLikeRotation;

	/** @phpstan-var IntProperty<AnalogRedstoneSignalEmitter> */
	public readonly IntProperty $analogRedstoneSignal;

	/** @phpstan-var IntProperty<Ageable> */
	public readonly IntProperty $cropAgeMax7;
	/** @phpstan-var BoolProperty<DoublePlant> */
	public readonly BoolProperty $doublePlantHalf;

	/** @phpstan-var IntProperty<Liquid> */
	public readonly IntProperty $liquidData;

	/** @phpstan-var BoolProperty<Lightable> */
	public readonly BoolProperty $lit;

	public readonly DummyProperty $dummyCardinalDirection;
	public readonly DummyProperty $dummyPillarAxis;

	/** @phpstan-var ValueFromStringProperty<Colored, DyeColor> */
	public readonly ValueFromStringProperty $dyeColorIdInfix;

	/** @phpstan-var BoolFromStringProperty<Lightable> */
	public readonly BoolFromStringProperty $litIdInfix;

	/** @phpstan-var BoolFromStringProperty<Slab> */
	public readonly BoolFromStringProperty $slabIdInfix;
	/** @phpstan-var BoolFromStringProperty<Slab> */
	public readonly BoolFromStringProperty $slabPositionProperty;

	/**
	 * @var StringProperty[]
	 * @phpstan-var non-empty-list<string|StringProperty<CoralMaterial>>
	 */
	public readonly array $coralIdPrefixes;
	/**
	 * @var StringProperty[]
	 * @phpstan-var non-empty-list<string|StringProperty<CopperMaterial>>
	 */
	public readonly array $copperIdPrefixes;

	/**
	 * @var StringProperty[]
	 * @phpstan-var non-empty-list<string|StringProperty<contravariant Lightable>>
	 */
	public readonly array $furnaceIdPrefixes;

	/**
	 * @var StringProperty[]|string[]
	 * @phpstan-var non-empty-list<string|StringProperty<contravariant Liquid>>
	 */
	public readonly array $liquidIdPrefixes;

	/**
	 * @var StringProperty[]
	 * @phpstan-var non-empty-list<string|StringProperty<contravariant Wood>>
	 */
	public readonly array $woodIdPrefixes;

	/**
	 * @var Property[]
	 * @phpstan-var non-empty-list<Property<contravariant Button>>
	 */
	public readonly array $buttonProperties;

	/**
	 * @var Property[]
	 * @phpstan-var non-empty-list<Property<contravariant Lightable & HorizontalFacing>>
	 */
	public readonly array $campfireProperties;

	/**
	 * @var Property[]
	 * @phpstan-var non-empty-list<Property<contravariant Door>>
	 */
	public readonly array $doorProperties;

	/**
	 * @var Property[]
	 * @phpstan-var non-empty-list<Property<contravariant FenceGate>>
	 */
	public readonly array $fenceGateProperties;

	/**
	 * @var Property[]
	 * @phpstan-var non-empty-list<Property<contravariant ItemFrame>>
	 */
	public readonly array $itemFrameProperties;

	/**
	 * @var Property[]
	 * @phpstan-var non-empty-list<Property<contravariant SimplePressurePlate>>
	 */
	public readonly array $simplePressurePlateProperties;

	/**
	 * @var Property[]
	 * @phpstan-var non-empty-list<Property<contravariant Stair>>
	 */
	public readonly array $stairProperties;

	/**
	 * @var Property[]
	 * @phpstan-var non-empty-list<Property<contravariant Stem>>
	 */
	public readonly array $stemProperties;

	/**
	 * @var Property[]
	 * @phpstan-var non-empty-list<Property<contravariant Trapdoor>>
	 */
	public readonly array $trapdoorProperties;

	/**
	 * @var Property[]
	 * @phpstan-var non-empty-list<Property<contravariant Wall>>
	 */
	public readonly array $wallProperties;

	private function __construct(){
		$vm = ValueMappings::getInstance();

		$hfGet = fn(HorizontalFacing $v) => $v->getFacing();
		$hfSet = fn(HorizontalFacing $v, int $facing) => $v->setFacing($facing);
		$this->horizontalFacingCardinal = new ValueFromStringProperty(StateNames::MC_CARDINAL_DIRECTION, $vm->cardinalDirection, $hfGet, $hfSet);

		$this->blockFace = new ValueFromStringProperty(
			StateNames::MC_BLOCK_FACE,
			$vm->blockFace,
			fn(AnyFacing $b) => $b->getFacing(),
			fn(AnyFacing $b, int $v) => $b->setFacing($v)
		);

		$this->pillarAxis = new ValueFromStringProperty(
			StateNames::PILLAR_AXIS,
			$vm->pillarAxis,
			fn(PillarRotation $b) => $b->getAxis(),
			fn(PillarRotation $b, int $v) => $b->setAxis($v)
		);

		$this->torchFacing = new ValueFromStringProperty(
			StateNames::TORCH_FACING_DIRECTION,
			$vm->torchFacing,
			fn(Torch $b) => $b->getFacing(),
			fn(Torch $b, int $v) => $b->setFacing($v)
		);

		$this->horizontalFacingSWNE = new ValueFromIntProperty(StateNames::DIRECTION, $vm->horizontalFacingSWNE, $hfGet, $hfSet);
		$this->horizontalFacingSWNEInverted = new ValueFromIntProperty(StateNames::DIRECTION, $vm->horizontalFacingSWNEInverted, $hfGet, $hfSet);
		$this->horizontalFacingClassic = new ValueFromIntProperty(StateNames::FACING_DIRECTION, $vm->horizontalFacingClassic, $hfGet, $hfSet);

		$this->anyFacingClassic = new ValueFromIntProperty(
			StateNames::FACING_DIRECTION,
			$vm->facing,
			fn(AnyFacing $b) => $b->getFacing(),
			fn(AnyFacing $b, int $v) => $b->setFacing($v)
		);

		$this->multiFacingFlags = new ValueSetFromIntProperty(
			StateNames::MULTI_FACE_DIRECTION_BITS,
			IntFromRawStateMap::int([
				Facing::DOWN => BlockLegacyMetadata::MULTI_FACE_DIRECTION_FLAG_DOWN,
				Facing::UP => BlockLegacyMetadata::MULTI_FACE_DIRECTION_FLAG_UP,
				Facing::NORTH => BlockLegacyMetadata::MULTI_FACE_DIRECTION_FLAG_NORTH,
				Facing::SOUTH => BlockLegacyMetadata::MULTI_FACE_DIRECTION_FLAG_SOUTH,
				Facing::WEST => BlockLegacyMetadata::MULTI_FACE_DIRECTION_FLAG_WEST,
				Facing::EAST => BlockLegacyMetadata::MULTI_FACE_DIRECTION_FLAG_EAST
			]),
			fn(MultiAnyFacing $b) => $b->getFaces(),
			fn(MultiAnyFacing $b, array $v) => $b->setFaces($v)
		);

		$this->floorSignLikeRotation = new IntProperty(StateNames::GROUND_SIGN_DIRECTION, 0, 15, fn(SignLikeRotation $b) => $b->getRotation(), fn(SignLikeRotation $b, int $v) => $b->setRotation($v));

		$this->analogRedstoneSignal = new IntProperty(StateNames::REDSTONE_SIGNAL, 0, 15, fn(AnalogRedstoneSignalEmitter $b) => $b->getOutputSignalStrength(), fn(AnalogRedstoneSignalEmitter $b, int $v) => $b->setOutputSignalStrength($v));

		$this->cropAgeMax7 = new IntProperty(StateNames::GROWTH, 0, 7, fn(Ageable $b) => $b->getAge(), fn(Ageable $b, int $v) => $b->setAge($v));
		$this->doublePlantHalf = new BoolProperty(StateNames::UPPER_BLOCK_BIT, fn(DoublePlant $b) => $b->isTop(), fn(DoublePlant $b, bool $v) => $b->setTop($v));

		$fallingFlag = BlockLegacyMetadata::LIQUID_FALLING_FLAG;
		$this->liquidData = new IntProperty(
			StateNames::LIQUID_DEPTH,
			0,
			15,
			fn(Liquid $b) => $b->getDecay() | ($b->isFalling() ? $fallingFlag : 0),
			fn(Liquid $b, int $v) => $b->setDecay($v & ~$fallingFlag)->setFalling(($v & $fallingFlag) !== 0)
		);

		$this->lit = new BoolProperty(StateNames::LIT, fn(Lightable $b) => $b->isLit(), fn(Lightable $b, bool $v) => $b->setLit($v));

		$this->dummyCardinalDirection = new DummyProperty(StateNames::MC_CARDINAL_DIRECTION, BlockStateStringValues::MC_CARDINAL_DIRECTION_SOUTH);
		$this->dummyPillarAxis = new DummyProperty(StateNames::PILLAR_AXIS, BlockStateStringValues::PILLAR_AXIS_Y);

		$this->dyeColorIdInfix = new ValueFromStringProperty("color", $vm->dyeColor, fn(Colored $b) => $b->getColor(), fn(Colored $b, DyeColor $v) => $b->setColor($v));
		$this->litIdInfix = new BoolFromStringProperty("lit", "", "lit_", fn(Lightable $b) => $b->isLit(), fn(Lightable $b, bool $v) => $b->setLit($v));

		$this->slabIdInfix = new BoolFromStringProperty(
			"double",
			"",
			"double_",
			fn(Slab $b) => $b->getSlabType() === SlabType::DOUBLE,

			//we don't know this is actually a bottom slab yet but we don't have enough information to set the
			//correct type in this handler
			//BOTTOM serves as a signal value for the state deserializer to decide whether to ignore the
			//upper_block_bit property
			fn(Slab $b, bool $v) => $b->setSlabType($v ? SlabType::DOUBLE : SlabType::BOTTOM)
		);
		$this->slabPositionProperty = new BoolFromStringProperty(
			StateNames::MC_VERTICAL_HALF,
			BlockStateStringValues::MC_VERTICAL_HALF_BOTTOM,
			BlockStateStringValues::MC_VERTICAL_HALF_TOP,
			fn(Slab $b) => $b->getSlabType() === SlabType::TOP,

			//Ignore the value for double slabs (should be set by ID component before this is reached)
			fn(Slab $b, bool $v) => $b->getSlabType() !== SlabType::DOUBLE ? $b->setSlabType($v ? SlabType::TOP : SlabType::BOTTOM) : null
		);

		$this->coralIdPrefixes = [
			"minecraft:",
			new BoolFromStringProperty("dead", "", "dead_", fn(CoralMaterial $b) => $b->isDead(), fn(CoralMaterial $b, bool $v) => $b->setDead($v)),
			new ValueFromStringProperty("type", EnumFromRawStateMap::string(CoralType::class, fn(CoralType $case) => match ($case) {
				CoralType::BRAIN => "brain",
				CoralType::BUBBLE => "bubble",
				CoralType::FIRE => "fire",
				CoralType::HORN => "horn",
				CoralType::TUBE => "tube"
			}), fn(CoralMaterial $b) => $b->getCoralType(), fn(CoralMaterial $b, CoralType $v) => $b->setCoralType($v)),
		];
		$this->copperIdPrefixes = [
			"minecraft:",
			new BoolFromStringProperty("waxed", "", "waxed_", fn(CopperMaterial $b) => $b->isWaxed(), fn(CopperMaterial $b, bool $v) => $b->setWaxed($v)),
			new ValueFromStringProperty("oxidation", EnumFromRawStateMap::string(CopperOxidation::class, fn(CopperOxidation $case) => match ($case) {
				CopperOxidation::NONE => "",
				CopperOxidation::EXPOSED => "exposed_",
				CopperOxidation::WEATHERED => "weathered_",
				CopperOxidation::OXIDIZED => "oxidized_",
			}), fn(CopperMaterial $b) => $b->getOxidation(), fn(CopperMaterial $b, CopperOxidation $v) => $b->setOxidation($v))
		];

		$this->furnaceIdPrefixes = ["minecraft:", $this->litIdInfix];

		$this->liquidIdPrefixes = [
			"minecraft:",
			new BoolFromStringProperty("still", "flowing_", "", fn(Liquid $b) => $b->isStill(), fn(Liquid $b, bool $v) => $b->setStill($v))
		];

		$this->woodIdPrefixes = [
			"minecraft:",
			new BoolFromStringProperty("stripped", "", "stripped_", fn(Wood $b) => $b->isStripped(), fn(Wood $b, bool $v) => $b->setStripped($v)),
		];

		$this->buttonProperties = [
			$this->anyFacingClassic,
			new BoolProperty(StateNames::BUTTON_PRESSED_BIT, fn(Button $b) => $b->isPressed(), fn(Button $b, bool $v) => $b->setPressed($v)),
		];

		$this->campfireProperties = [
			$this->horizontalFacingCardinal,
			new BoolProperty(StateNames::EXTINGUISHED, fn(Lightable $b) => $b->isLit(), fn(Lightable $b, bool $v) => $b->setLit($v), inverted: true),
		];

		//TODO: check if these need any special treatment to get the appropriate data to both halves of the door
		$this->doorProperties = [
			new BoolProperty(StateNames::UPPER_BLOCK_BIT, fn(Door $b) => $b->isTop(), fn(Door $b, bool $v) => $b->setTop($v)),
			new BoolProperty(StateNames::DOOR_HINGE_BIT, fn(Door $b) => $b->isHingeRight(), fn(Door $b, bool $v) => $b->setHingeRight($v)),
			new BoolProperty(StateNames::OPEN_BIT, fn(Door $b) => $b->isOpen(), fn(Door $b, bool $v) => $b->setOpen($v)),
			new ValueFromStringProperty(
				StateNames::MC_CARDINAL_DIRECTION,
				IntFromRawStateMap::string([
					//a door facing "east" is actually facing north - thanks mojang
					Facing::NORTH => BlockStateStringValues::MC_CARDINAL_DIRECTION_EAST,
					Facing::EAST => BlockStateStringValues::MC_CARDINAL_DIRECTION_SOUTH,
					Facing::SOUTH => BlockStateStringValues::MC_CARDINAL_DIRECTION_WEST,
					Facing::WEST => BlockStateStringValues::MC_CARDINAL_DIRECTION_NORTH
				]),
				fn(HorizontalFacing $b) => $b->getFacing(),
				fn(HorizontalFacing $b, int $v) => $b->setFacing($v)
			)
		];

		$this->fenceGateProperties = [
			new BoolProperty(StateNames::IN_WALL_BIT, fn(FenceGate $b) => $b->isInWall(), fn(FenceGate $b, bool $v) => $b->setInWall($v)),
			new BoolProperty(StateNames::OPEN_BIT, fn(FenceGate $b) => $b->isOpen(), fn(FenceGate $b, bool $v) => $b->setOpen($v)),
			$this->horizontalFacingCardinal,
		];

		$this->itemFrameProperties = [
			new DummyProperty(StateNames::ITEM_FRAME_PHOTO_BIT, false), //TODO: not sure what the point of this is
			new BoolProperty(StateNames::ITEM_FRAME_MAP_BIT, fn(ItemFrame $b) => $b->hasMap(), fn(ItemFrame $b, bool $v) => $b->setHasMap($v)),
			$this->anyFacingClassic
		];

		$this->simplePressurePlateProperties = [
			//TODO: not sure what the deal is here ... seems like a mojang bug / artifact of bad implementation?
			//best to keep this separate from weighted plates anyway...
			new IntProperty(
				StateNames::REDSTONE_SIGNAL,
				0,
				15,
				fn(SimplePressurePlate $b) => $b->isPressed() ? 15 : 0,
				fn(SimplePressurePlate $b, int $v) => $b->setPressed($v !== 0)
			)
		];

		$this->stairProperties = [
			new BoolProperty(StateNames::UPSIDE_DOWN_BIT, fn(Stair $b) => $b->isUpsideDown(), fn(Stair $b, bool $v) => $b->setUpsideDown($v)),
			new ValueFromIntProperty(StateNames::WEIRDO_DIRECTION, $vm->horizontalFacing5Minus, $hfGet, $hfSet),
		];

		$this->stemProperties = [
			new ValueFromIntProperty(StateNames::FACING_DIRECTION, $vm->facingStem, fn(Stem $b) => $b->getFacing(), fn(Stem $b, int $v) => $b->setFacing($v)),
			$this->cropAgeMax7
		];

		$this->trapdoorProperties = [
			//this uses the same values as stairs, but the state is named differently
			new ValueFromIntProperty(StateNames::DIRECTION, $vm->horizontalFacing5Minus, $hfGet, $hfSet),

			new BoolProperty(StateNames::UPSIDE_DOWN_BIT, fn(Trapdoor $b) => $b->isTop(), fn(Trapdoor $b, bool $v) => $b->setTop($v)),
			new BoolProperty(StateNames::OPEN_BIT, fn(Trapdoor $b) => $b->isOpen(), fn(Trapdoor $b, bool $v) => $b->setOpen($v)),
		];

		$wallProperties = [
			new BoolProperty(StateNames::WALL_POST_BIT, fn(Wall $b) => $b->isPost(), fn(Wall $b, bool $v) => $b->setPost($v)),
		];
		foreach([
			Facing::NORTH => StateNames::WALL_CONNECTION_TYPE_NORTH,
			Facing::SOUTH => StateNames::WALL_CONNECTION_TYPE_SOUTH,
			Facing::WEST => StateNames::WALL_CONNECTION_TYPE_WEST,
			Facing::EAST => StateNames::WALL_CONNECTION_TYPE_EAST
		] as $facing => $stateName){
			$wallProperties[] = new ValueFromStringProperty(
				$stateName,
				EnumFromRawStateMap::string(WallConnectionTypeShim::class, fn(WallConnectionTypeShim $case) => $case->getValue()),
				fn(Wall $b) => WallConnectionTypeShim::serialize($b->getConnection($facing)),
				fn(Wall $b, WallConnectionTypeShim $v) => $b->setConnection($facing, $v->deserialize())
			);
		}
		$this->wallProperties = $wallProperties;
	}
}