Merge 'minor-next' into 'major-next'

Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/12191303914
This commit is contained in:
pmmp-admin-bot[bot] 2024-12-06 01:38:37 +00:00
commit 6d2329128a
7 changed files with 867 additions and 733 deletions

29
.github/workflows/pr-stale.yml vendored Normal file
View File

@ -0,0 +1,29 @@
name: 'Clean up stale PRs'
on:
schedule:
- cron: '30 1 * * *'
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
with:
days-before-issue-stale: -1
days-before-issue-close: -1
stale-pr-message: |
This PR has been marked as "Waiting on Author", but we haven't seen any activity in 7 days.
If there is no further activity, it will be closed in 28 days.
Note for maintainers: Adding an assignee to the PR will prevent it from being marked as stale.
close-pr-message: |
As this PR hasn't been updated for a while, unfortunately we'll have to close it.
days-before-pr-stale: 7
days-before-pr-close: 28
only-labels: "Status: Waiting on Author"
close-pr-label: "Resolution: Abandoned"
exempt-all-assignees: true

View File

@ -105,3 +105,12 @@ Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if
- Event handler tests have been converted to PHPUnit tests by mocking `Server` and `Plugin` instances. Previously, these required integration tests for these dependencies. - Event handler tests have been converted to PHPUnit tests by mocking `Server` and `Plugin` instances. Previously, these required integration tests for these dependencies.
- Fixed various deprecation warnings in PHP 8.4. - Fixed various deprecation warnings in PHP 8.4.
- `netresearch/jsonmapper` is now used at `5.0.0`. The PMMP fork of this library has been removed, as it is no longer needed. - `netresearch/jsonmapper` is now used at `5.0.0`. The PMMP fork of this library has been removed, as it is no longer needed.
# 5.23.1
Released 5th December 2024.
## Fixes
- Fixed signs not creating a tile when placed.
## Internals
- Improved blockstate consistency check to detect tiles disappearing during refactors.

View File

@ -31,7 +31,7 @@ use function str_repeat;
final class VersionInfo{ final class VersionInfo{
public const NAME = "PocketMine-MP"; public const NAME = "PocketMine-MP";
public const BASE_VERSION = "5.23.1"; public const BASE_VERSION = "5.23.2";
public const IS_DEVELOPMENT_BUILD = true; public const IS_DEVELOPMENT_BUILD = true;
public const BUILD_CHANNEL = "stable"; public const BUILD_CHANNEL = "stable";

View File

@ -54,6 +54,7 @@ use pocketmine\block\tile\MonsterSpawner as TileMonsterSpawner;
use pocketmine\block\tile\NormalFurnace as TileNormalFurnace; use pocketmine\block\tile\NormalFurnace as TileNormalFurnace;
use pocketmine\block\tile\Note as TileNote; use pocketmine\block\tile\Note as TileNote;
use pocketmine\block\tile\ShulkerBox as TileShulkerBox; use pocketmine\block\tile\ShulkerBox as TileShulkerBox;
use pocketmine\block\tile\Sign as TileSign;
use pocketmine\block\tile\Smoker as TileSmoker; use pocketmine\block\tile\Smoker as TileSmoker;
use pocketmine\block\tile\Tile; use pocketmine\block\tile\Tile;
use pocketmine\block\utils\AmethystTrait; use pocketmine\block\utils\AmethystTrait;
@ -1359,8 +1360,8 @@ final class VanillaBlocks{
WoodType::WARPED => VanillaItems::WARPED_SIGN(...), WoodType::WARPED => VanillaItems::WARPED_SIGN(...),
WoodType::CHERRY => VanillaItems::CHERRY_SIGN(...), WoodType::CHERRY => VanillaItems::CHERRY_SIGN(...),
}; };
self::register($idName("sign"), fn(BID $id) => new FloorSign($id, $name . " Sign", $signBreakInfo, $woodType, $signAsItem)); self::register($idName("sign"), fn(BID $id) => new FloorSign($id, $name . " Sign", $signBreakInfo, $woodType, $signAsItem), TileSign::class);
self::register($idName("wall_sign"), fn(BID $id) => new WallSign($id, $name . " Wall Sign", $signBreakInfo, $woodType, $signAsItem)); self::register($idName("wall_sign"), fn(BID $id) => new WallSign($id, $name . " Wall Sign", $signBreakInfo, $woodType, $signAsItem), TileSign::class);
} }
} }

View File

@ -27,6 +27,7 @@ use PHPUnit\Framework\TestCase;
use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Filesystem; use pocketmine\utils\Filesystem;
use pocketmine\utils\Utils; use pocketmine\utils\Utils;
use function get_debug_type;
use function implode; use function implode;
use function is_array; use function is_array;
use function is_int; use function is_int;
@ -95,11 +96,12 @@ class BlockTest extends TestCase{
} }
/** /**
* @return int[] * @return int[][]|string[][]
* @phpstan-return array<string, int> * @phpstan-return array{array<string, int>, array<string, string>}
*/ */
public static function computeConsistencyCheckTable(RuntimeBlockStateRegistry $blockStateRegistry) : array{ public static function computeConsistencyCheckTable(RuntimeBlockStateRegistry $blockStateRegistry) : array{
$newTable = []; $newTable = [];
$newTileMap = [];
$idNameLookup = []; $idNameLookup = [];
//if we ever split up block registration into multiple registries (e.g. separating chemistry blocks), //if we ever split up block registration into multiple registries (e.g. separating chemistry blocks),
@ -118,36 +120,70 @@ class BlockTest extends TestCase{
} }
$idName = $idNameLookup[$block->getTypeId()]; $idName = $idNameLookup[$block->getTypeId()];
$newTable[$idName] = ($newTable[$idName] ?? 0) + 1; $newTable[$idName] = ($newTable[$idName] ?? 0) + 1;
}
return $newTable; $tileClass = $block->getIdInfo()->getTileClass();
if($tileClass !== null){
if(isset($newTileMap[$idName]) && $newTileMap[$idName] !== $tileClass){
throw new AssumptionFailedError("Tile entity $tileClass for $idName is inconsistent");
}
$newTileMap[$idName] = $tileClass;
}
}
return [$newTable, $newTileMap];
} }
/** /**
* @phpstan-param array<string, int> $actual * @phpstan-param array<string, int> $actualStateCounts
* @phpstan-param array<string, string> $actualTiles
* *
* @return string[] * @return string[]
*/ */
public static function computeConsistencyCheckDiff(string $expectedFile, array $actual) : array{ public static function computeConsistencyCheckDiff(string $expectedFile, array $actualStateCounts, array $actualTiles) : array{
$expected = json_decode(Filesystem::fileGetContents($expectedFile), true, 2, JSON_THROW_ON_ERROR); $expected = json_decode(Filesystem::fileGetContents($expectedFile), true, 3, JSON_THROW_ON_ERROR);
if(!is_array($expected)){ if(!is_array($expected)){
throw new AssumptionFailedError("Old table should be array<string, int>"); throw new AssumptionFailedError("Old table should be array{stateCounts: array<string, int>, tiles: array<string, string>}");
}
$expectedStates = $expected["stateCounts"] ?? [];
$expectedTiles = $expected["tiles"] ?? [];
if(!is_array($expectedStates)){
throw new AssumptionFailedError("stateCounts should be an array, but have " . get_debug_type($expectedStates));
}
if(!is_array($expectedTiles)){
throw new AssumptionFailedError("tiles should be an array, but have " . get_debug_type($expectedTiles));
} }
$errors = []; $errors = [];
foreach(Utils::promoteKeys($expected) as $typeName => $numStates){ foreach(Utils::promoteKeys($expectedStates) as $typeName => $numStates){
if(!is_string($typeName) || !is_int($numStates)){ if(!is_string($typeName) || !is_int($numStates)){
throw new AssumptionFailedError("Old table should be array<string, int>"); throw new AssumptionFailedError("Old table should be array<string, int>");
} }
if(!isset($actual[$typeName])){ if(!isset($actualStateCounts[$typeName])){
$errors[] = "Removed block type $typeName ($numStates permutations)"; $errors[] = "Removed block type $typeName ($numStates permutations)";
}elseif($actual[$typeName] !== $numStates){ }elseif($actualStateCounts[$typeName] !== $numStates){
$errors[] = "Block type $typeName permutation count changed: $numStates -> " . $actual[$typeName]; $errors[] = "Block type $typeName permutation count changed: $numStates -> " . $actualStateCounts[$typeName];
} }
} }
foreach(Utils::stringifyKeys($actual) as $typeName => $numStates){ foreach(Utils::stringifyKeys($actualStateCounts) as $typeName => $numStates){
if(!isset($expected[$typeName])){ if(!isset($expectedStates[$typeName])){
$errors[] = "Added block type $typeName (" . $actual[$typeName] . " permutations)"; $errors[] = "Added block type $typeName (" . $actualStateCounts[$typeName] . " permutations)";
}
}
foreach(Utils::promoteKeys($expectedTiles) as $typeName => $tile){
if(!is_string($typeName) || !is_string($tile)){
throw new AssumptionFailedError("Tile table should be array<string, string>");
}
if(isset($actualStateCounts[$typeName])){
if(!isset($actualTiles[$typeName])){
$errors[] = "$typeName no longer has a tile";
}elseif($actualTiles[$typeName] !== $tile){
$errors[] = "$typeName has changed tile ($tile -> " . $actualTiles[$typeName] . ")";
}
}
}
foreach(Utils::promoteKeys($actualTiles) as $typeName => $tile){
if(isset($expectedStates[$typeName]) && !isset($expectedTiles[$typeName])){
$errors[] = "$typeName has a tile when it previously didn't ($tile)";
} }
} }
@ -155,8 +191,8 @@ class BlockTest extends TestCase{
} }
public function testConsistency() : void{ public function testConsistency() : void{
$newTable = self::computeConsistencyCheckTable($this->blockFactory); [$newTable, $newTileMap] = self::computeConsistencyCheckTable($this->blockFactory);
$errors = self::computeConsistencyCheckDiff(__DIR__ . '/block_factory_consistency_check.json', $newTable); $errors = self::computeConsistencyCheckDiff(__DIR__ . '/block_factory_consistency_check.json', $newTable, $newTileMap);
self::assertEmpty($errors, "Block factory consistency check failed:\n" . implode("\n", $errors)); self::assertEmpty($errors, "Block factory consistency check failed:\n" . implode("\n", $errors));
} }

View File

@ -1,4 +1,5 @@
{ {
"stateCounts": {
"ACACIA_BUTTON": 12, "ACACIA_BUTTON": 12,
"ACACIA_DOOR": 32, "ACACIA_DOOR": 32,
"ACACIA_FENCE": 1, "ACACIA_FENCE": 1,
@ -709,4 +710,61 @@
"WHITE_TULIP": 1, "WHITE_TULIP": 1,
"WITHER_ROSE": 1, "WITHER_ROSE": 1,
"WOOL": 16 "WOOL": 16
},
"tiles": {
"ACACIA_SIGN": "pocketmine\\block\\tile\\Sign",
"ACACIA_WALL_SIGN": "pocketmine\\block\\tile\\Sign",
"BANNER": "pocketmine\\block\\tile\\Banner",
"BARREL": "pocketmine\\block\\tile\\Barrel",
"BEACON": "pocketmine\\block\\tile\\Beacon",
"BED": "pocketmine\\block\\tile\\Bed",
"BELL": "pocketmine\\block\\tile\\Bell",
"BIRCH_SIGN": "pocketmine\\block\\tile\\Sign",
"BIRCH_WALL_SIGN": "pocketmine\\block\\tile\\Sign",
"BLAST_FURNACE": "pocketmine\\block\\tile\\BlastFurnace",
"BREWING_STAND": "pocketmine\\block\\tile\\BrewingStand",
"CAMPFIRE": "pocketmine\\block\\tile\\Campfire",
"CAULDRON": "pocketmine\\block\\tile\\Cauldron",
"CHERRY_SIGN": "pocketmine\\block\\tile\\Sign",
"CHERRY_WALL_SIGN": "pocketmine\\block\\tile\\Sign",
"CHEST": "pocketmine\\block\\tile\\Chest",
"CHISELED_BOOKSHELF": "pocketmine\\block\\tile\\ChiseledBookshelf",
"CRIMSON_SIGN": "pocketmine\\block\\tile\\Sign",
"CRIMSON_WALL_SIGN": "pocketmine\\block\\tile\\Sign",
"DARK_OAK_SIGN": "pocketmine\\block\\tile\\Sign",
"DARK_OAK_WALL_SIGN": "pocketmine\\block\\tile\\Sign",
"DAYLIGHT_SENSOR": "pocketmine\\block\\tile\\DaylightSensor",
"DYED_SHULKER_BOX": "pocketmine\\block\\tile\\ShulkerBox",
"ENCHANTING_TABLE": "pocketmine\\block\\tile\\EnchantTable",
"ENDER_CHEST": "pocketmine\\block\\tile\\EnderChest",
"FLOWER_POT": "pocketmine\\block\\tile\\FlowerPot",
"FURNACE": "pocketmine\\block\\tile\\NormalFurnace",
"GLOWING_ITEM_FRAME": "pocketmine\\block\\tile\\GlowingItemFrame",
"HOPPER": "pocketmine\\block\\tile\\Hopper",
"ITEM_FRAME": "pocketmine\\block\\tile\\ItemFrame",
"JUKEBOX": "pocketmine\\block\\tile\\Jukebox",
"JUNGLE_SIGN": "pocketmine\\block\\tile\\Sign",
"JUNGLE_WALL_SIGN": "pocketmine\\block\\tile\\Sign",
"LAVA_CAULDRON": "pocketmine\\block\\tile\\Cauldron",
"LECTERN": "pocketmine\\block\\tile\\Lectern",
"MANGROVE_SIGN": "pocketmine\\block\\tile\\Sign",
"MANGROVE_WALL_SIGN": "pocketmine\\block\\tile\\Sign",
"MOB_HEAD": "pocketmine\\block\\tile\\MobHead",
"MONSTER_SPAWNER": "pocketmine\\block\\tile\\MonsterSpawner",
"NOTE_BLOCK": "pocketmine\\block\\tile\\Note",
"OAK_SIGN": "pocketmine\\block\\tile\\Sign",
"OAK_WALL_SIGN": "pocketmine\\block\\tile\\Sign",
"POTION_CAULDRON": "pocketmine\\block\\tile\\Cauldron",
"REDSTONE_COMPARATOR": "pocketmine\\block\\tile\\Comparator",
"SHULKER_BOX": "pocketmine\\block\\tile\\ShulkerBox",
"SMOKER": "pocketmine\\block\\tile\\Smoker",
"SOUL_CAMPFIRE": "pocketmine\\block\\tile\\Campfire",
"SPRUCE_SIGN": "pocketmine\\block\\tile\\Sign",
"SPRUCE_WALL_SIGN": "pocketmine\\block\\tile\\Sign",
"TRAPPED_CHEST": "pocketmine\\block\\tile\\Chest",
"WALL_BANNER": "pocketmine\\block\\tile\\Banner",
"WARPED_SIGN": "pocketmine\\block\\tile\\Sign",
"WARPED_WALL_SIGN": "pocketmine\\block\\tile\\Sign",
"WATER_CAULDRON": "pocketmine\\block\\tile\\Cauldron"
}
} }

View File

@ -28,11 +28,11 @@ require dirname(__DIR__, 3) . '/vendor/autoload.php';
/* This script needs to be re-run after any intentional blockfactory change (adding or removing a block state). */ /* This script needs to be re-run after any intentional blockfactory change (adding or removing a block state). */
$newTable = BlockTest::computeConsistencyCheckTable(RuntimeBlockStateRegistry::getInstance()); [$newTable, $newTiles] = BlockTest::computeConsistencyCheckTable(RuntimeBlockStateRegistry::getInstance());
$oldTablePath = __DIR__ . '/block_factory_consistency_check.json'; $oldTablePath = __DIR__ . '/block_factory_consistency_check.json';
if(file_exists($oldTablePath)){ if(file_exists($oldTablePath)){
$errors = BlockTest::computeConsistencyCheckDiff($oldTablePath, $newTable); $errors = BlockTest::computeConsistencyCheckDiff($oldTablePath, $newTable, $newTiles);
if(count($errors) > 0){ if(count($errors) > 0){
echo count($errors) . " changes detected:\n"; echo count($errors) . " changes detected:\n";
@ -47,5 +47,6 @@ if(file_exists($oldTablePath)){
} }
ksort($newTable, SORT_STRING); ksort($newTable, SORT_STRING);
ksort($newTiles, SORT_STRING);
file_put_contents($oldTablePath, json_encode($newTable, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT)); file_put_contents($oldTablePath, json_encode(["stateCounts" => $newTable, "tiles" => $newTiles], JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT));