Merge branch 'minor-next' into feat/async-events

This commit is contained in:
Dylan T. 2024-11-29 13:49:20 +00:00 committed by GitHub
commit 866d473553
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
99 changed files with 696 additions and 195 deletions

View File

@ -64,6 +64,7 @@ jobs:
id: get-pm-version
run: |
echo PM_VERSION=$(php build/dump-version-info.php base_version) >> $GITHUB_OUTPUT
echo PM_MAJOR=$(php build/dump-version-info.php major_version) >> $GITHUB_OUTPUT
echo MCPE_VERSION=$(php build/dump-version-info.php mcpe_version) >> $GITHUB_OUTPUT
echo CHANGELOG_FILE_NAME=$(php build/dump-version-info.php changelog_file_name) >> $GITHUB_OUTPUT
echo CHANGELOG_MD_HEADER=$(php build/dump-version-info.php changelog_md_header) >> $GITHUB_OUTPUT
@ -72,7 +73,7 @@ jobs:
- name: Generate PHP binary download URL
id: php-binary-url
run: |
echo PHP_BINARY_URL="${{ github.server_url }}/${{ github.repository_owner }}/PHP-Binaries/releases/tag/php-${{ matrix.php-version }}-latest" >> $GITHUB_OUTPUT
echo PHP_BINARY_URL="${{ github.server_url }}/${{ github.repository_owner }}/PHP-Binaries/releases/tag/pm${{ steps.get-pm-version.outputs.PM_MAJOR }}-php-${{ matrix.php-version }}-latest" >> $GITHUB_OUTPUT
- name: Generate build info
run: |

View File

@ -30,7 +30,7 @@ jobs:
- uses: actions/checkout@v4
- name: Setup PHP
uses: pmmp/setup-php-action@3.1.0
uses: pmmp/setup-php-action@3.2.0
with:
php-version: ${{ inputs.php }}
install-path: "./bin"
@ -62,7 +62,7 @@ jobs:
- uses: actions/checkout@v4
- name: Setup PHP
uses: pmmp/setup-php-action@3.1.0
uses: pmmp/setup-php-action@3.2.0
with:
php-version: ${{ inputs.php }}
install-path: "./bin"
@ -96,7 +96,7 @@ jobs:
submodules: true
- name: Setup PHP
uses: pmmp/setup-php-action@3.1.0
uses: pmmp/setup-php-action@3.2.0
with:
php-version: ${{ inputs.php }}
install-path: "./bin"
@ -128,7 +128,7 @@ jobs:
- uses: actions/checkout@v4
- name: Setup PHP
uses: pmmp/setup-php-action@3.1.0
uses: pmmp/setup-php-action@3.2.0
with:
php-version: ${{ inputs.php }}
install-path: "./bin"

View File

@ -0,0 +1,33 @@
name: Remove waiting label from PRs
on:
pull_request_target:
types: synchronize
jobs:
delabel:
name: Remove label
runs-on: ubuntu-latest
steps:
- name: Remove label
uses: actions/github-script@v7
with:
github-token: ${{ github.token }}
script: |
const [owner, repo] = context.payload.repository.full_name.split('/');
try {
await github.rest.issues.removeLabel({
owner: owner,
repo: repo,
issue_number: context.payload.number,
name: "Status: Waiting on Author",
});
} catch (error) {
if (error.status === 404) {
//probably label wasn't set on the issue
console.log('Failed to remove label (probably label isn\'t on the PR): ' + error.message);
} else {
throw error;
}
}

View File

@ -9,9 +9,9 @@ on:
pull_request_target:
types:
- opened
- synchronize
- reopened
- ready_for_review
- synchronize
jobs:
dispatch:
@ -35,4 +35,4 @@ jobs:
token: ${{ steps.generate-token.outputs.token }}
repository: ${{ github.repository_owner }}/RestrictedActions
event-type: auto_approve_collaborator_pr
client-payload: '{"repo": "${{ github.repository }}", "pull_request_id": "${{ github.event.pull_request.number }}" }'
client-payload: '{"repo": "${{ github.repository }}", "pull_request_id": "${{ github.event.pull_request.number }}", "reviewer_id": "0" }'

View File

@ -36,6 +36,7 @@ require dirname(__DIR__) . '/vendor/autoload.php';
*/
$options = [
"base_version" => VersionInfo::BASE_VERSION,
"major_version" => fn() => explode(".", VersionInfo::BASE_VERSION)[0],
"mcpe_version" => ProtocolInfo::MINECRAFT_VERSION_NETWORK,
"is_dev" => VersionInfo::IS_DEVELOPMENT_BUILD,
"changelog_file_name" => function() : string{

View File

@ -122,7 +122,7 @@ if(!is_array($ids)){
throw new \RuntimeException("Invalid biome ID map, expected array for root JSON object");
}
$cleanedIds = [];
foreach($ids as $name => $id){
foreach(Utils::promoteKeys($ids) as $name => $id){
if(!is_string($name) || !is_int($id)){
throw new \RuntimeException("Invalid biome ID map, expected string => int map");
}

View File

@ -42,7 +42,7 @@ $constants = [];
* @phpstan-param-out array<string, string> $constants
*/
function collectProperties(string $prefix, array $properties, array &$constants) : void{
foreach($properties as $propertyName => $property){
foreach(Utils::promoteKeys($properties) as $propertyName => $property){
$fullPropertyName = ($prefix !== "" ? $prefix . "." : "") . $propertyName;
$constName = str_replace([".", "-"], "_", strtoupper($fullPropertyName));

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\build\update_registry_annotations;
use pocketmine\utils\Utils;
use function basename;
use function class_exists;
use function count;
@ -48,6 +49,7 @@ if(count($argv) !== 2){
/**
* @param object[] $members
* @phpstan-param array<string, object> $members
*/
function generateMethodAnnotations(string $namespaceName, array $members) : string{
$selfName = basename(__FILE__);
@ -60,7 +62,7 @@ function generateMethodAnnotations(string $namespaceName, array $members) : stri
static $lineTmpl = " * @method static %2\$s %s()";
$memberLines = [];
foreach($members as $name => $member){
foreach(Utils::stringifyKeys($members) as $name => $member){
$reflect = new \ReflectionClass($member);
while($reflect !== false && $reflect->isAnonymous()){
$reflect = $reflect->getParentClass();

@ -1 +1 @@
Subproject commit a51259d7a6ea649d64f409fc0276baa59cf4f19a
Subproject commit 8a396c63fc5e79ea2849bfca100ea21a49ba2933

View File

@ -332,7 +332,7 @@ class MemoryManager{
continue;
}
$methodStatics = [];
foreach($method->getStaticVariables() as $name => $variable){
foreach(Utils::promoteKeys($method->getStaticVariables()) as $name => $variable){
$methodStatics[$name] = self::continueDump($variable, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
}
if(count($methodStatics) > 0){
@ -360,7 +360,7 @@ class MemoryManager{
'_SESSION' => true
];
foreach($GLOBALS as $varName => $value){
foreach(Utils::promoteKeys($GLOBALS) as $varName => $value){
if(isset($ignoredGlobals[$varName])){
continue;
}
@ -376,7 +376,7 @@ class MemoryManager{
$reflect = new \ReflectionFunction($function);
$vars = [];
foreach($reflect->getStaticVariables() as $varName => $variable){
foreach(Utils::promoteKeys($reflect->getStaticVariables()) as $varName => $variable){
$vars[$varName] = self::continueDump($variable, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
}
if(count($vars) > 0){
@ -416,7 +416,7 @@ class MemoryManager{
$info["this"] = self::continueDump($closureThis, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
}
foreach($reflect->getStaticVariables() as $name => $variable){
foreach(Utils::promoteKeys($reflect->getStaticVariables()) as $name => $variable){
$info["referencedVars"][$name] = self::continueDump($variable, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
}
}else{
@ -499,7 +499,7 @@ class MemoryManager{
}
$data = [];
$numeric = 0;
foreach($from as $key => $value){
foreach(Utils::promoteKeys($from) as $key => $value){
$data[$numeric] = [
"k" => self::continueDump($key, $objects, $refCounts, $recursion + 1, $maxNesting, $maxStringSize),
"v" => self::continueDump($value, $objects, $refCounts, $recursion + 1, $maxNesting, $maxStringSize),

View File

@ -737,12 +737,15 @@ class Server{
/**
* @return string[][]
* @phpstan-return array<string, list<string>>
*/
public function getCommandAliases() : array{
$section = $this->configGroup->getProperty(Yml::ALIASES);
$result = [];
if(is_array($section)){
foreach($section as $key => $value){
foreach(Utils::promoteKeys($section) as $key => $value){
//TODO: more validation needed here
//key might not be a string, value might not be list<string>
$commands = [];
if(is_array($value)){
$commands = $value;
@ -750,7 +753,7 @@ class Server{
$commands[] = (string) $value;
}
$result[$key] = $commands;
$result[(string) $key] = $commands;
}
}
@ -1095,7 +1098,11 @@ class Server{
$anyWorldFailedToLoad = false;
foreach((array) $this->configGroup->getProperty(Yml::WORLDS, []) as $name => $options){
foreach(Utils::promoteKeys((array) $this->configGroup->getProperty(Yml::WORLDS, [])) as $name => $options){
if(!is_string($name)){
//TODO: this probably should be an error
continue;
}
if($options === null){
$options = [];
}elseif(!is_array($options)){

View File

@ -35,10 +35,10 @@ use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\utils\Utils;
use pocketmine\world\BlockTransaction;
use pocketmine\world\sound\AnvilFallSound;
use pocketmine\world\sound\Sound;
use function lcg_value;
use function round;
class Anvil extends Transparent implements Fallable{
@ -97,7 +97,7 @@ class Anvil extends Transparent implements Fallable{
}
public function onHitGround(FallingBlock $blockEntity) : bool{
if(lcg_value() < 0.05 + (round($blockEntity->getFallDistance()) - 1) * 0.05){
if(Utils::getRandomFloat() < 0.05 + (round($blockEntity->getFallDistance()) - 1) * 0.05){
if($this->damage !== self::VERY_DAMAGED){
$this->damage = $this->damage + 1;
}else{

View File

@ -52,6 +52,9 @@ class CakeWithCandle extends BaseCake{
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
if($this->lit && $face !== Facing::UP){
return true;
}
if($this->onInteractCandle($item, $face, $clickVector, $player, $returnedItems)){
return true;
}

View File

@ -31,8 +31,8 @@ use pocketmine\event\entity\EntityTrampleFarmlandEvent;
use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\utils\Utils;
use function intdiv;
use function lcg_value;
class Farmland extends Transparent{
public const MAX_WETNESS = 7;
@ -148,7 +148,7 @@ class Farmland extends Transparent{
}
public function onEntityLand(Entity $entity) : ?float{
if($entity instanceof Living && lcg_value() < $entity->getFallDistance() - 0.5){
if($entity instanceof Living && Utils::getRandomFloat() < $entity->getFallDistance() - 0.5){
$ev = new EntityTrampleFarmlandEvent($entity, $this);
$ev->call();
if(!$ev->isCancelled()){

View File

@ -31,13 +31,13 @@ use pocketmine\item\Item;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\utils\Utils;
use pocketmine\world\BlockTransaction;
use pocketmine\world\sound\ItemFrameAddItemSound;
use pocketmine\world\sound\ItemFrameRemoveItemSound;
use pocketmine\world\sound\ItemFrameRotateItemSound;
use function is_infinite;
use function is_nan;
use function lcg_value;
class ItemFrame extends Flowable{
use AnyFacingTrait;
@ -154,7 +154,7 @@ class ItemFrame extends Flowable{
return false;
}
$world = $this->position->getWorld();
if(lcg_value() <= $this->itemDropChance){
if(Utils::getRandomFloat() <= $this->itemDropChance){
$world->dropItem($this->position->add(0.5, 0.5, 0.5), clone $this->framedItem);
$world->addSound($this->position, new ItemFrameRemoveItemSound());
}
@ -185,7 +185,7 @@ class ItemFrame extends Flowable{
public function getDropsForCompatibleTool(Item $item) : array{
$drops = parent::getDropsForCompatibleTool($item);
if($this->framedItem !== null && lcg_value() <= $this->itemDropChance){
if($this->framedItem !== null && Utils::getRandomFloat() <= $this->itemDropChance){
$drops[] = clone $this->framedItem;
}

View File

@ -33,9 +33,9 @@ use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\utils\Utils;
use pocketmine\world\sound\FizzSound;
use pocketmine\world\sound\Sound;
use function lcg_value;
abstract class Liquid extends Transparent{
public const MAX_DECAY = 7;
@ -368,7 +368,7 @@ abstract class Liquid extends Transparent{
protected function liquidCollide(Block $cause, Block $result) : bool{
if(BlockEventHelper::form($this, $result, $cause)){
$this->position->getWorld()->addSound($this->position->add(0.5, 0.5, 0.5), new FizzSound(2.6 + (lcg_value() - lcg_value()) * 0.8));
$this->position->getWorld()->addSound($this->position->add(0.5, 0.5, 0.5), new FizzSound(2.6 + (Utils::getRandomFloat() - Utils::getRandomFloat()) * 0.8));
}
return true;
}

View File

@ -132,6 +132,7 @@ class RuntimeBlockStateRegistry{
/**
* @return Block[]
* @phpstan-return array<int, Block>
*/
public function getAllKnownStates() : array{
return $this->fullList;

View File

@ -39,7 +39,10 @@ class EnchantInventory extends SimpleInventory implements BlockInventory, Tempor
public const SLOT_INPUT = 0;
public const SLOT_LAPIS = 1;
/** @var EnchantingOption[] $options */
/**
* @var EnchantingOption[] $options
* @phpstan-var list<EnchantingOption>
*/
private array $options = [];
public function __construct(Position $holder){

View File

@ -36,13 +36,17 @@ use function str_contains;
class SignText{
public const LINE_COUNT = 4;
/** @var string[] */
/**
* @var string[]
* @phpstan-var array{0: string, 1: string, 2: string, 3: string}
*/
private array $lines;
private Color $baseColor;
private bool $glowing;
/**
* @param string[]|null $lines index-sensitive; keys 0-3 will be used, regardless of array order
* @phpstan-param array{0?: string, 1?: string, 2?: string, 3?: string}|null $lines
*
* @throws \InvalidArgumentException if the array size is greater than 4
* @throws \InvalidArgumentException if invalid keys (out of bounds or string) are found in the array
@ -82,6 +86,7 @@ class SignText{
* Returns an array of lines currently on the sign.
*
* @return string[]
* @phpstan-return array{0: string, 1: string, 2: string, 3: string}
*/
public function getLines() : array{
return $this->lines;

View File

@ -44,10 +44,16 @@ abstract class Command{
private string $nextLabel;
private string $label;
/** @var string[] */
/**
* @var string[]
* @phpstan-var list<string>
*/
private array $aliases = [];
/** @var string[] */
/**
* @var string[]
* @phpstan-var list<string>
*/
private array $activeAliases = [];
private ?CommandMap $commandMap = null;
@ -62,6 +68,7 @@ abstract class Command{
/**
* @param string[] $aliases
* @phpstan-param list<string> $aliases
*/
public function __construct(string $name, Translatable|string $description = "", Translatable|string|null $usageMessage = null, array $aliases = []){
$this->name = $name;
@ -182,6 +189,7 @@ abstract class Command{
/**
* @return string[]
* @phpstan-return list<string>
*/
public function getAliases() : array{
return $this->activeAliases;
@ -201,6 +209,7 @@ abstract class Command{
/**
* @param string[] $aliases
* @phpstan-param list<string> $aliases
*/
public function setAliases(array $aliases) : void{
$this->aliases = $aliases;

View File

@ -71,6 +71,7 @@ use pocketmine\lang\KnownTranslationFactory;
use pocketmine\Server;
use pocketmine\timings\Timings;
use pocketmine\utils\TextFormat;
use pocketmine\utils\Utils;
use function array_shift;
use function count;
use function implode;
@ -81,7 +82,10 @@ use function trim;
class SimpleCommandMap implements CommandMap{
/** @var Command[] */
/**
* @var Command[]
* @phpstan-var array<string, Command>
*/
protected array $knownCommands = [];
public function __construct(private Server $server){
@ -171,7 +175,7 @@ class SimpleCommandMap implements CommandMap{
}
public function unregister(Command $command) : bool{
foreach($this->knownCommands as $lbl => $cmd){
foreach(Utils::promoteKeys($this->knownCommands) as $lbl => $cmd){
if($cmd === $command){
unset($this->knownCommands[$lbl]);
}
@ -239,6 +243,7 @@ class SimpleCommandMap implements CommandMap{
/**
* @return Command[]
* @phpstan-return array<string, Command>
*/
public function getCommands() : array{
return $this->knownCommands;
@ -247,7 +252,7 @@ class SimpleCommandMap implements CommandMap{
public function registerServerAliases() : void{
$values = $this->server->getCommandAliases();
foreach($values as $alias => $commandStrings){
foreach(Utils::stringifyKeys($values) as $alias => $commandStrings){
if(str_contains($alias, ":")){
$this->server->getLogger()->warning($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_command_alias_illegal($alias)));
continue;

View File

@ -110,14 +110,15 @@ class CraftingManager{
/**
* @param Item[] $items
* @phpstan-param list<Item> $items
*
* @return Item[]
* @phpstan-return list<Item>
*/
private static function pack(array $items) : array{
/** @var Item[] $result */
$result = [];
foreach($items as $i => $item){
foreach($items as $item){
foreach($result as $otherItem){
if($item->canStackWith($otherItem)){
$otherItem->setCount($otherItem->getCount() + $item->getCount());
@ -134,6 +135,7 @@ class CraftingManager{
/**
* @param Item[] $outputs
* @phpstan-param list<Item> $outputs
*/
private static function hashOutputs(array $outputs) : string{
$outputs = self::pack($outputs);

View File

@ -193,7 +193,7 @@ final class CraftingManagerFromDataHelper{
*/
private static function loadJsonObjectListIntoModel(\JsonMapper $mapper, string $modelClass, array $data) : array{
$result = [];
foreach($data as $i => $item){
foreach(Utils::promoteKeys($data) as $i => $item){
if(!is_object($item)){
throw new SavedDataLoadingException("Invalid entry at index $i: expected object, got " . get_debug_type($item));
}

View File

@ -30,6 +30,7 @@ interface CraftingRecipe{
* Returns a list of items needed to craft this recipe. This MUST NOT include Air items or items with a zero count.
*
* @return RecipeIngredient[]
* @phpstan-return list<RecipeIngredient>
*/
public function getIngredientList() : array;
@ -37,6 +38,7 @@ interface CraftingRecipe{
* Returns a list of results this recipe will produce when the inputs in the given crafting grid are consumed.
*
* @return Item[]
* @phpstan-return list<Item>
*/
public function getResultsFor(CraftingGrid $grid) : array;

View File

@ -32,11 +32,20 @@ use function str_contains;
use function strlen;
class ShapedRecipe implements CraftingRecipe{
/** @var string[] */
/**
* @var string[]
* @phpstan-var list<string>
*/
private array $shape = [];
/** @var RecipeIngredient[] char => RecipeIngredient map */
/**
* @var RecipeIngredient[] char => RecipeIngredient map
* @phpstan-var array<string, RecipeIngredient>
*/
private array $ingredientList = [];
/** @var Item[] */
/**
* @var Item[]
* @phpstan-var list<Item>
*/
private array $results = [];
private int $height;
@ -56,6 +65,10 @@ class ShapedRecipe implements CraftingRecipe{
* @param Item[] $results List of items that this recipe produces when crafted.
*
* Note: Recipes **do not** need to be square. Do NOT add padding for empty rows/columns.
*
* @phpstan-param list<string> $shape
* @phpstan-param array<string, RecipeIngredient> $ingredients
* @phpstan-param list<Item> $results
*/
public function __construct(array $shape, array $ingredients, array $results){
$this->height = count($shape);
@ -84,7 +97,7 @@ class ShapedRecipe implements CraftingRecipe{
$this->shape = $shape;
foreach($ingredients as $char => $i){
foreach(Utils::stringifyKeys($ingredients) as $char => $i){
if(!str_contains(implode($this->shape), $char)){
throw new \InvalidArgumentException("Symbol '$char' does not appear in the recipe shape");
}
@ -105,6 +118,7 @@ class ShapedRecipe implements CraftingRecipe{
/**
* @return Item[]
* @phpstan-return list<Item>
*/
public function getResults() : array{
return Utils::cloneObjectArray($this->results);
@ -112,6 +126,7 @@ class ShapedRecipe implements CraftingRecipe{
/**
* @return Item[]
* @phpstan-return list<Item>
*/
public function getResultsFor(CraftingGrid $grid) : array{
return $this->getResults();
@ -119,6 +134,7 @@ class ShapedRecipe implements CraftingRecipe{
/**
* @return (RecipeIngredient|null)[][]
* @phpstan-return list<list<RecipeIngredient|null>>
*/
public function getIngredientMap() : array{
$ingredients = [];
@ -132,9 +148,6 @@ class ShapedRecipe implements CraftingRecipe{
return $ingredients;
}
/**
* @return RecipeIngredient[]
*/
public function getIngredientList() : array{
$ingredients = [];
@ -157,6 +170,7 @@ class ShapedRecipe implements CraftingRecipe{
/**
* Returns an array of strings containing characters representing the recipe's shape.
* @return string[]
* @phpstan-return list<string>
*/
public function getShape() : array{
return $this->shape;

View File

@ -28,15 +28,24 @@ use pocketmine\utils\Utils;
use function count;
class ShapelessRecipe implements CraftingRecipe{
/** @var RecipeIngredient[] */
/**
* @var RecipeIngredient[]
* @phpstan-var list<RecipeIngredient>
*/
private array $ingredients = [];
/** @var Item[] */
/**
* @var Item[]
* @phpstan-var list<Item>
*/
private array $results;
private ShapelessRecipeType $type;
/**
* @param RecipeIngredient[] $ingredients No more than 9 total. This applies to sum of item stack counts, not count of array.
* @param Item[] $results List of result items created by this recipe.
*
* @phpstan-param list<RecipeIngredient> $ingredients
* @phpstan-param list<Item> $results
*/
public function __construct(array $ingredients, array $results, ShapelessRecipeType $type){
$this->type = $type;
@ -50,6 +59,7 @@ class ShapelessRecipe implements CraftingRecipe{
/**
* @return Item[]
* @phpstan-return list<Item>
*/
public function getResults() : array{
return Utils::cloneObjectArray($this->results);
@ -63,9 +73,6 @@ class ShapelessRecipe implements CraftingRecipe{
return $this->type;
}
/**
* @return RecipeIngredient[]
*/
public function getIngredientList() : array{
return $this->ingredients;
}

View File

@ -43,7 +43,10 @@ final class CrashDumpData implements \JsonSerializable{
public string $plugin = "";
/** @var string[] */
/**
* @var string[]
* @phpstan-var array<int, string>
*/
public array $code = [];
/** @var string[] */
@ -55,7 +58,10 @@ final class CrashDumpData implements \JsonSerializable{
*/
public array $plugins = [];
/** @var string[] */
/**
* @var string[]
* @phpstan-var list<string>
*/
public array $parameters = [];
public string $serverDotProperties = "";

View File

@ -48,7 +48,7 @@ final class ItemTagToIdMap{
throw new AssumptionFailedError("Invalid item tag map, expected array");
}
$cleanMap = [];
foreach($map as $tagName => $ids){
foreach(Utils::promoteKeys($map) as $tagName => $ids){
if(!is_string($tagName)){
throw new AssumptionFailedError("Invalid item tag name $tagName, expected string as key");
}

View File

@ -25,6 +25,7 @@ namespace pocketmine\data\bedrock;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Filesystem;
use pocketmine\utils\Utils;
use function is_array;
use function is_int;
use function is_string;
@ -43,7 +44,7 @@ abstract class LegacyToStringIdMap{
if(!is_array($stringToLegacyId)){
throw new AssumptionFailedError("Invalid format of ID map");
}
foreach($stringToLegacyId as $stringId => $legacyId){
foreach(Utils::promoteKeys($stringToLegacyId) as $stringId => $legacyId){
if(!is_string($stringId) || !is_int($legacyId)){
throw new AssumptionFailedError("ID map should have string keys and int values");
}

View File

@ -266,6 +266,7 @@ final class ItemSerializerDeserializerRegistrar{
$this->map1to1Item(Ids::HONEY_BOTTLE, Items::HONEY_BOTTLE());
$this->map1to1Item(Ids::HONEYCOMB, Items::HONEYCOMB());
$this->map1to1Item(Ids::HOST_ARMOR_TRIM_SMITHING_TEMPLATE, Items::HOST_ARMOR_TRIM_SMITHING_TEMPLATE());
$this->map1to1Item(Ids::ICE_BOMB, Items::ICE_BOMB());
$this->map1to1Item(Ids::INK_SAC, Items::INK_SAC());
$this->map1to1Item(Ids::IRON_AXE, Items::IRON_AXE());
$this->map1to1Item(Ids::IRON_BOOTS, Items::IRON_BOOTS());

View File

@ -56,7 +56,7 @@ final class R12ItemIdToBlockIdMap{
}
$builtMap = [];
foreach($map as $itemId => $blockId){
foreach(Utils::promoteKeys($map) as $itemId => $blockId){
if(!is_string($itemId)){
throw new AssumptionFailedError("Invalid blockitem ID mapping table, expected string as key");
}

View File

@ -75,7 +75,6 @@ use function deg2rad;
use function floor;
use function fmod;
use function get_class;
use function lcg_value;
use function sin;
use function spl_object_id;
use const M_PI_2;
@ -103,7 +102,10 @@ abstract class Entity{
return self::$entityCount++;
}
/** @var Player[] */
/**
* @var Player[]
* @phpstan-var array<int, Player>
*/
protected array $hasSpawned = [];
protected int $id;
@ -907,7 +909,7 @@ abstract class Entity{
return false;
}
$force = lcg_value() * 0.2 + 0.1;
$force = Utils::getRandomFloat() * 0.2 + 0.1;
$this->motion = match($direction){
Facing::WEST => $this->motion->withComponents(-$force, null, null),

View File

@ -43,6 +43,7 @@ use pocketmine\entity\projectile\Arrow;
use pocketmine\entity\projectile\Egg;
use pocketmine\entity\projectile\EnderPearl;
use pocketmine\entity\projectile\ExperienceBottle;
use pocketmine\entity\projectile\IceBomb;
use pocketmine\entity\projectile\Snowball;
use pocketmine\entity\projectile\SplashPotion;
use pocketmine\item\Item;
@ -120,6 +121,10 @@ final class EntityFactory{
return new FallingBlock(Helper::parseLocation($nbt, $world), FallingBlock::parseBlockNBT(RuntimeBlockStateRegistry::getInstance(), $nbt), $nbt);
}, ['FallingSand', 'minecraft:falling_block']);
$this->register(IceBomb::class, function(World $world, CompoundTag $nbt) : IceBomb{
return new IceBomb(Helper::parseLocation($nbt, $world), null, $nbt);
}, ['minecraft:ice_bomb']);
$this->register(ItemEntity::class, function(World $world, CompoundTag $nbt) : ItemEntity{
$itemTag = $nbt->getCompoundTag(ItemEntity::TAG_ITEM);
if($itemTag === null){

View File

@ -58,6 +58,7 @@ use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataProperties;
use pocketmine\player\Player;
use pocketmine\timings\Timings;
use pocketmine\utils\Binary;
use pocketmine\utils\Utils;
use pocketmine\world\sound\BurpSound;
use pocketmine\world\sound\EntityLandSound;
use pocketmine\world\sound\EntityLongFallSound;
@ -69,7 +70,6 @@ use function ceil;
use function count;
use function floor;
use function ksort;
use function lcg_value;
use function max;
use function min;
use function mt_getrandmax;
@ -490,7 +490,7 @@ abstract class Living extends Entity{
$helmet = $this->armorInventory->getHelmet();
if($helmet instanceof Armor){
$finalDamage = $source->getFinalDamage();
$this->damageItem($helmet, (int) round($finalDamage * 4 + lcg_value() * $finalDamage * 2));
$this->damageItem($helmet, (int) round($finalDamage * 4 + Utils::getRandomFloat() * $finalDamage * 2));
$this->armorInventory->setHelmet($helmet);
}
}
@ -697,7 +697,7 @@ abstract class Living extends Entity{
$this->setBreathing(false);
if(($respirationLevel = $this->armorInventory->getHelmet()->getEnchantmentLevel(VanillaEnchantments::RESPIRATION())) <= 0 ||
lcg_value() <= (1 / ($respirationLevel + 1))
Utils::getRandomFloat() <= (1 / ($respirationLevel + 1))
){
$ticks -= $tickDiff;
if($ticks <= -20){

View File

@ -0,0 +1,86 @@
<?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\entity\projectile;
use pocketmine\block\Block;
use pocketmine\block\BlockTypeIds;
use pocketmine\block\VanillaBlocks;
use pocketmine\event\entity\ProjectileHitEvent;
use pocketmine\item\VanillaItems;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\RayTraceResult;
use pocketmine\math\Vector3;
use pocketmine\network\mcpe\protocol\types\entity\EntityIds;
use pocketmine\world\particle\ItemBreakParticle;
use pocketmine\world\sound\IceBombHitSound;
class IceBomb extends Throwable{
public static function getNetworkTypeId() : string{ return EntityIds::ICE_BOMB; }
public function getResultDamage() : int{
return -1;
}
protected function calculateInterceptWithBlock(Block $block, Vector3 $start, Vector3 $end) : ?RayTraceResult{
if($block->getTypeId() === BlockTypeIds::WATER){
$pos = $block->getPosition();
return AxisAlignedBB::one()->offset($pos->x, $pos->y, $pos->z)->calculateIntercept($start, $end);
}
return parent::calculateInterceptWithBlock($block, $start, $end);
}
protected function onHit(ProjectileHitEvent $event) : void{
$world = $this->getWorld();
$pos = $this->location;
$world->addSound($pos, new IceBombHitSound());
$itemBreakParticle = new ItemBreakParticle(VanillaItems::ICE_BOMB());
for($i = 0; $i < 6; ++$i){
$world->addParticle($pos, $itemBreakParticle);
}
}
protected function onHitBlock(Block $blockHit, RayTraceResult $hitResult) : void{
parent::onHitBlock($blockHit, $hitResult);
$pos = $blockHit->getPosition();
$world = $pos->getWorld();
$posX = $pos->getFloorX();
$posY = $pos->getFloorY();
$posZ = $pos->getFloorZ();
$ice = VanillaBlocks::ICE();
for($x = $posX - 1; $x <= $posX + 1; $x++){
for($y = $posY - 1; $y <= $posY + 1; $y++){
for($z = $posZ - 1; $z <= $posZ + 1; $z++){
if($world->getBlockAt($x, $y, $z)->getTypeId() === BlockTypeIds::WATER){
$world->setBlockAt($x, $y, $z, $ice);
}
}
}
}
}
}

View File

@ -41,7 +41,10 @@ use function spl_object_id;
*/
abstract class BaseInventory implements Inventory, SlotValidatedInventory{
protected int $maxStackSize = Inventory::MAX_STACK;
/** @var Player[] */
/**
* @var Player[]
* @phpstan-var array<int, Player>
*/
protected array $viewers = [];
/**
* @var InventoryListener[]|ObjectSet
@ -286,8 +289,6 @@ abstract class BaseInventory implements Inventory, SlotValidatedInventory{
}
public function removeItem(Item ...$slots) : array{
/** @var Item[] $searchItems */
/** @var Item[] $slots */
$searchItems = [];
foreach($slots as $slot){
if(!$slot->isNull()){

View File

@ -36,7 +36,10 @@ final class CreativeInventory{
use SingletonTrait;
use DestructorCallbackTrait;
/** @var Item[] */
/**
* @var Item[]
* @phpstan-var array<int, Item>
*/
private array $creative = [];
/** @phpstan-var ObjectSet<\Closure() : void> */
@ -69,6 +72,7 @@ final class CreativeInventory{
/**
* @return Item[]
* @phpstan-return array<int, Item>
*/
public function getAll() : array{
return Utils::cloneObjectArray($this->creative);

View File

@ -57,9 +57,15 @@ class CraftingTransaction extends InventoryTransaction{
protected ?CraftingRecipe $recipe = null;
protected ?int $repetitions = null;
/** @var Item[] */
/**
* @var Item[]
* @phpstan-var list<Item>
*/
protected array $inputs = [];
/** @var Item[] */
/**
* @var Item[]
* @phpstan-var list<Item>
*/
protected array $outputs = [];
private CraftingManager $craftingManager;
@ -74,6 +80,9 @@ class CraftingTransaction extends InventoryTransaction{
/**
* @param Item[] $providedItems
* @return Item[]
*
* @phpstan-param list<Item> $providedItems
* @phpstan-return list<Item>
*/
private static function packItems(array $providedItems) : array{
$packedProvidedItems = [];
@ -94,6 +103,9 @@ class CraftingTransaction extends InventoryTransaction{
/**
* @param Item[] $providedItems
* @param RecipeIngredient[] $recipeIngredients
*
* @phpstan-param list<Item> $providedItems
* @phpstan-param list<RecipeIngredient> $recipeIngredients
*/
public static function matchIngredients(array $providedItems, array $recipeIngredients, int $expectedIterations) : void{
if(count($recipeIngredients) === 0){
@ -172,6 +184,9 @@ class CraftingTransaction extends InventoryTransaction{
* @param Item[] $txItems
* @param Item[] $recipeItems
*
* @phpstan-param list<Item> $txItems
* @phpstan-param list<Item> $recipeItems
*
* @throws TransactionValidationException
*/
protected function matchOutputs(array $txItems, array $recipeItems) : int{

View File

@ -29,6 +29,7 @@ use pocketmine\inventory\transaction\action\InventoryAction;
use pocketmine\inventory\transaction\action\SlotChangeAction;
use pocketmine\item\Item;
use pocketmine\player\Player;
use pocketmine\utils\Utils;
use function array_keys;
use function array_values;
use function assert;
@ -57,10 +58,16 @@ use function spl_object_id;
class InventoryTransaction{
protected bool $hasExecuted = false;
/** @var Inventory[] */
/**
* @var Inventory[]
* @phpstan-var array<int, Inventory>
*/
protected array $inventories = [];
/** @var InventoryAction[] */
/**
* @var InventoryAction[]
* @phpstan-var array<int, InventoryAction>
*/
protected array $actions = [];
/**
@ -81,6 +88,7 @@ class InventoryTransaction{
/**
* @return Inventory[]
* @phpstan-return array<int, Inventory>
*/
public function getInventories() : array{
return $this->inventories;
@ -93,6 +101,7 @@ class InventoryTransaction{
* significance and should not be relied on.
*
* @return InventoryAction[]
* @phpstan-return array<int, InventoryAction>
*/
public function getActions() : array{
return $this->actions;
@ -102,6 +111,9 @@ class InventoryTransaction{
if(!isset($this->actions[$hash = spl_object_id($action)])){
$this->actions[$hash] = $action;
$action->onAddToTransaction($this);
if($action instanceof SlotChangeAction && !isset($this->inventories[$inventoryId = spl_object_id($action->getInventory())])){
$this->inventories[$inventoryId] = $action->getInventory();
}
}else{
throw new \InvalidArgumentException("Tried to add the same action to a transaction twice");
}
@ -120,21 +132,11 @@ class InventoryTransaction{
$this->actions = $actions;
}
/**
* @internal This method should not be used by plugins, it's used to add tracked inventories for InventoryActions
* involving inventories.
*/
public function addInventory(Inventory $inventory) : void{
if(!isset($this->inventories[$hash = spl_object_id($inventory)])){
$this->inventories[$hash] = $inventory;
}
}
/**
* @param Item[] $needItems
* @param Item[] $haveItems
* @phpstan-param-out Item[] $needItems
* @phpstan-param-out Item[] $haveItems
* @phpstan-param-out list<Item> $needItems
* @phpstan-param-out list<Item> $haveItems
*
* @throws TransactionValidationException
*/
@ -188,11 +190,8 @@ class InventoryTransaction{
* wrong order), so this method also tries to chain them into order.
*/
protected function squashDuplicateSlotChanges() : void{
/** @var SlotChangeAction[][] $slotChanges */
$slotChanges = [];
/** @var Inventory[] $inventories */
$inventories = [];
/** @var int[] $slots */
$slots = [];
foreach($this->actions as $key => $action){
@ -203,7 +202,7 @@ class InventoryTransaction{
}
}
foreach($slotChanges as $hash => $list){
foreach(Utils::stringifyKeys($slotChanges) as $hash => $list){
if(count($list) === 1){ //No need to compact slot changes if there is only one on this slot
continue;
}
@ -233,6 +232,7 @@ class InventoryTransaction{
/**
* @param SlotChangeAction[] $possibleActions
* @phpstan-param list<SlotChangeAction> $possibleActions
*/
protected function findResultItem(Item $needOrigin, array $possibleActions) : ?Item{
assert(count($possibleActions) > 0);

View File

@ -60,6 +60,7 @@ abstract class InventoryAction{
/**
* Called when the action is added to the specified InventoryTransaction.
* @deprecated
*/
public function onAddToTransaction(InventoryTransaction $transaction) : void{

View File

@ -25,7 +25,6 @@ namespace pocketmine\inventory\transaction\action;
use pocketmine\inventory\Inventory;
use pocketmine\inventory\SlotValidatedInventory;
use pocketmine\inventory\transaction\InventoryTransaction;
use pocketmine\inventory\transaction\TransactionValidationException;
use pocketmine\item\Item;
use pocketmine\player\Player;
@ -85,13 +84,6 @@ class SlotChangeAction extends InventoryAction{
}
}
/**
* Adds this action's target inventory to the transaction's inventory list.
*/
public function onAddToTransaction(InventoryTransaction $transaction) : void{
$transaction->addInventory($this->inventory);
}
/**
* Sets the item into the target inventory.
*/

View File

@ -33,7 +33,7 @@ use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\IntTag;
use pocketmine\player\Player;
use pocketmine\utils\Binary;
use function lcg_value;
use pocketmine\utils\Utils;
use function mt_rand;
class Armor extends Durable{
@ -129,7 +129,7 @@ class Armor extends Durable{
$chance = 1 / ($unbreakingLevel + 1);
for($i = 0; $i < $amount; ++$i){
if(mt_rand(1, 100) > 60 && lcg_value() > $chance){ //unbreaking only applies to armor 40% of the time at best
if(mt_rand(1, 100) > 60 && Utils::getRandomFloat() > $chance){ //unbreaking only applies to armor 40% of the time at best
$negated++;
}
}

View File

@ -25,7 +25,7 @@ namespace pocketmine\item;
use pocketmine\item\enchantment\VanillaEnchantments;
use pocketmine\nbt\tag\CompoundTag;
use function lcg_value;
use pocketmine\utils\Utils;
use function min;
abstract class Durable extends Item{
@ -87,7 +87,7 @@ abstract class Durable extends Item{
$chance = 1 / ($unbreakingLevel + 1);
for($i = 0; $i < $amount; ++$i){
if(lcg_value() > $chance){
if(Utils::getRandomFloat() > $chance){
$negated++;
}
}

48
src/item/IceBomb.php Normal file
View File

@ -0,0 +1,48 @@
<?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\item;
use pocketmine\entity\Location;
use pocketmine\entity\projectile\IceBomb as IceBombEntity;
use pocketmine\entity\projectile\Throwable;
use pocketmine\player\Player;
class IceBomb extends ProjectileItem{
public function getMaxStackSize() : int{
return 16;
}
protected function createEntity(Location $location, Player $thrower) : Throwable{
return new IceBombEntity($location, $thrower);
}
public function getThrowForce() : float{
return 1.5;
}
public function getCooldownTicks() : int{
return 10;
}
}

View File

@ -33,7 +33,10 @@ use function spl_object_id;
* The primary purpose of this trait is providing scope isolation for the methods it contains.
*/
trait ItemEnchantmentHandlingTrait{
/** @var EnchantmentInstance[] */
/**
* @var EnchantmentInstance[]
* @phpstan-var array<int, EnchantmentInstance>
*/
protected array $enchantments = [];
public function hasEnchantments() : bool{
@ -79,6 +82,7 @@ trait ItemEnchantmentHandlingTrait{
/**
* @return EnchantmentInstance[]
* @phpstan-return array<int, EnchantmentInstance>
*/
public function getEnchantments() : array{
return $this->enchantments;

View File

@ -326,8 +326,9 @@ final class ItemTypeIds{
public const NAME_TAG = 20287;
public const GOAT_HORN = 20288;
public const END_CRYSTAL = 20289;
public const ICE_BOMB = 20290;
public const FIRST_UNUSED_ITEM_ID = 20290;
public const FIRST_UNUSED_ITEM_ID = 20291;
private static int $nextDynamicId = self::FIRST_UNUSED_ITEM_ID;

View File

@ -29,6 +29,7 @@ use pocketmine\data\bedrock\item\upgrade\ItemDataUpgrader;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Filesystem;
use pocketmine\utils\SingletonTrait;
use pocketmine\utils\Utils;
use pocketmine\world\format\io\GlobalItemDataHandlers;
use Symfony\Component\Filesystem\Path;
use function explode;
@ -67,7 +68,7 @@ final class LegacyStringToItemParser{
$mappings = json_decode($mappingsRaw, true);
if(!is_array($mappings)) throw new AssumptionFailedError("Invalid mappings format, expected array");
foreach($mappings as $name => $id){
foreach(Utils::promoteKeys($mappings) as $name => $id){
if(!is_string($id)) throw new AssumptionFailedError("Invalid mappings format, expected string values");
$result->addMapping((string) $name, $id);
}

View File

@ -25,7 +25,7 @@ namespace pocketmine\item;
use pocketmine\entity\effect\EffectInstance;
use pocketmine\entity\effect\VanillaEffects;
use function lcg_value;
use pocketmine\utils\Utils;
class RottenFlesh extends Food{
@ -38,7 +38,7 @@ class RottenFlesh extends Food{
}
public function getAdditionalEffects() : array{
if(lcg_value() <= 0.8){
if(Utils::getRandomFloat() <= 0.8){
return [
new EffectInstance(VanillaEffects::HUNGER(), 600)
];

View File

@ -27,15 +27,15 @@ use pocketmine\block\Block;
use pocketmine\entity\Entity;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\utils\Utils;
use pocketmine\world\World;
use function lcg_value;
abstract class SpawnEgg extends Item{
abstract protected function createEntity(World $world, Vector3 $pos, float $yaw, float $pitch) : Entity;
public function onInteractBlock(Player $player, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, array &$returnedItems) : ItemUseResult{
$entity = $this->createEntity($player->getWorld(), $blockReplace->getPosition()->add(0.5, 0, 0.5), lcg_value() * 360, 0);
$entity = $this->createEntity($player->getWorld(), $blockReplace->getPosition()->add(0.5, 0, 0.5), Utils::getRandomFloat() * 360, 0);
if($this->hasCustomName()){
$entity->setNameTag($this->getCustomName());

View File

@ -1378,6 +1378,7 @@ final class StringToItemParser extends StringToTParser{
$result->register("honey_bottle", fn() => Items::HONEY_BOTTLE());
$result->register("host_armor_trim_smithing_template", fn() => Items::HOST_ARMOR_TRIM_SMITHING_TEMPLATE());
$result->register("honeycomb", fn() => Items::HONEYCOMB());
$result->register("ice_bomb", fn() => Items::ICE_BOMB());
$result->register("ink_sac", fn() => Items::INK_SAC());
$result->register("iron_axe", fn() => Items::IRON_AXE());
$result->register("iron_boots", fn() => Items::IRON_BOOTS());

View File

@ -192,6 +192,7 @@ use function strtolower;
* @method static Item HONEYCOMB()
* @method static HoneyBottle HONEY_BOTTLE()
* @method static Item HOST_ARMOR_TRIM_SMITHING_TEMPLATE()
* @method static IceBomb ICE_BOMB()
* @method static Item INK_SAC()
* @method static Axe IRON_AXE()
* @method static Armor IRON_BOOTS()
@ -504,6 +505,7 @@ final class VanillaItems{
self::register("heart_of_the_sea", fn(IID $id) => new Item($id, "Heart of the Sea"));
self::register("honey_bottle", fn(IID $id) => new HoneyBottle($id, "Honey Bottle"));
self::register("honeycomb", fn(IID $id) => new Item($id, "Honeycomb"));
self::register("ice_bomb", fn(IID $id) => new IceBomb($id, "Ice Bomb"));
self::register("ink_sac", fn(IID $id) => new Item($id, "Ink Sac"));
self::register("iron_ingot", fn(IID $id) => new Item($id, "Iron Ingot"));
self::register("iron_nugget", fn(IID $id) => new Item($id, "Iron Nugget"));

View File

@ -147,7 +147,7 @@ class Language{
$baseText = $this->parseTranslation($str, $onlyPrefix);
}
foreach($params as $i => $p){
foreach(Utils::promoteKeys($params) as $i => $p){
$replacement = $p instanceof Translatable ? $this->translate($p) : (string) $p;
$baseText = str_replace("{%$i}", $replacement, $baseText);
}
@ -161,7 +161,7 @@ class Language{
$baseText = $this->parseTranslation($c->getText());
}
foreach($c->getParameters() as $i => $p){
foreach(Utils::promoteKeys($c->getParameters()) as $i => $p){
$replacement = $p instanceof Translatable ? $this->translate($p) : $p;
$baseText = str_replace("{%$i}", $replacement, $baseText);
}

View File

@ -23,6 +23,8 @@ declare(strict_types=1);
namespace pocketmine\lang;
use pocketmine\utils\Utils;
final class Translatable{
/** @var string[]|Translatable[] $params */
protected array $params = [];
@ -34,7 +36,7 @@ final class Translatable{
protected string $text,
array $params = []
){
foreach($params as $k => $param){
foreach(Utils::promoteKeys($params) as $k => $param){
if(!($param instanceof Translatable)){
$this->params[$k] = (string) $param;
}else{

View File

@ -30,10 +30,16 @@ use function spl_object_id;
class NetworkSessionManager{
/** @var NetworkSession[] */
/**
* @var NetworkSession[]
* @phpstan-var array<int, NetworkSession>
*/
private array $sessions = [];
/** @var NetworkSession[] */
/**
* @var NetworkSession[]
* @phpstan-var array<int, NetworkSession>
*/
private array $pendingLoginSessions = [];
/**

View File

@ -592,7 +592,6 @@ class InventoryManager{
$info = $this->trackItemStack($entry, $slot, $itemStack, null);
$contents[] = new ItemStackWrapper($info->getStackId(), $itemStack);
}
$clearSlotWrapper = new ItemStackWrapper(0, ItemStack::null());
if($entry->complexSlotMap !== null){
foreach($contents as $slotId => $info){
$packetSlot = $entry->complexSlotMap->mapCoreToNet($slotId) ?? null;
@ -696,6 +695,7 @@ class InventoryManager{
/**
* @param EnchantingOption[] $options
* @phpstan-param list<EnchantingOption> $options
*/
public function syncEnchantingTableOptions(array $options) : void{
$protocolOptions = [];

View File

@ -1092,7 +1092,7 @@ class NetworkSession{
public function syncAvailableCommands() : void{
$commandData = [];
foreach($this->server->getCommandMap()->getCommands() as $name => $command){
foreach($this->server->getCommandMap()->getCommands() as $command){
if(isset($commandData[$command->getLabel()]) || $command->getLabel() === "help" || !$command->testPermissionSilent($this->player)){
continue;
}

View File

@ -53,7 +53,6 @@ final class StandardPacketBroadcaster implements PacketBroadcaster{
$compressors = [];
/** @var NetworkSession[][] $targetsByCompressor */
$targetsByCompressor = [];
foreach($recipients as $recipient){
//TODO: different compressors might be compatible, it might not be necessary to split them up by object

View File

@ -78,7 +78,10 @@ class ChunkCache implements ChunkListener{
}
}
/** @var CompressBatchPromise[] */
/**
* @var CompressBatchPromise[]
* @phpstan-var array<int, CompressBatchPromise>
*/
private array $caches = [];
private int $hits = 0;

View File

@ -28,6 +28,7 @@ use pocketmine\nbt\LittleEndianNbtSerializer;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\Tag;
use pocketmine\nbt\TreeRoot;
use pocketmine\utils\Utils;
use function count;
use function ksort;
use const SORT_STRING;
@ -43,6 +44,7 @@ final class BlockStateDictionaryEntry{
/**
* @param Tag[] $stateProperties
* @phpstan-param array<string, Tag> $stateProperties
*/
public function __construct(
private string $stateName,
@ -79,6 +81,7 @@ final class BlockStateDictionaryEntry{
/**
* @param Tag[] $properties
* @phpstan-param array<string, Tag> $properties
*/
public static function encodeStateProperties(array $properties) : string{
if(count($properties) === 0){
@ -87,7 +90,7 @@ final class BlockStateDictionaryEntry{
//TODO: make a more efficient encoding - NBT will do for now, but it's not very compact
ksort($properties, SORT_STRING);
$tag = new CompoundTag();
foreach($properties as $k => $v){
foreach(Utils::stringifyKeys($properties) as $k => $v){
$tag->setTag($k, $v);
}
return (new LittleEndianNbtSerializer())->write(new TreeRoot($tag));

View File

@ -26,6 +26,7 @@ namespace pocketmine\network\mcpe\convert;
use pocketmine\network\mcpe\protocol\serializer\ItemTypeDictionary;
use pocketmine\network\mcpe\protocol\types\ItemTypeEntry;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Utils;
use function is_array;
use function is_bool;
use function is_int;
@ -41,7 +42,7 @@ final class ItemTypeDictionaryFromDataHelper{
}
$params = [];
foreach($table as $name => $entry){
foreach(Utils::promoteKeys($table) as $name => $entry){
if(!is_array($entry) || !is_string($name) || !isset($entry["component_based"], $entry["runtime_id"]) || !is_bool($entry["component_based"]) || !is_int($entry["runtime_id"])){
throw new AssumptionFailedError("Invalid item list format");
}

View File

@ -251,7 +251,7 @@ class InGamePacketHandler extends PacketHandler{
if(count($blockActions) > 100){
throw new PacketHandlingException("Too many block actions in PlayerAuthInputPacket");
}
foreach($blockActions as $k => $blockAction){
foreach(Utils::promoteKeys($blockActions) as $k => $blockAction){
$actionHandled = false;
if($blockAction instanceof PlayerBlockActionStopBreak){
$actionHandled = $this->handlePlayerActionFromData($blockAction->getActionType(), new BlockPosition(0, 0, 0), Facing::DOWN);

View File

@ -54,6 +54,7 @@ use pocketmine\network\mcpe\protocol\types\inventory\stackresponse\ItemStackResp
use pocketmine\network\mcpe\protocol\types\inventory\UIInventorySlotOffset;
use pocketmine\player\Player;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Utils;
use function array_key_first;
use function count;
use function spl_object_id;
@ -370,7 +371,7 @@ class ItemStackRequestExecutor{
* @throws ItemStackRequestProcessException
*/
public function generateInventoryTransaction() : InventoryTransaction{
foreach($this->request->getActions() as $k => $action){
foreach(Utils::promoteKeys($this->request->getActions()) as $k => $action){
try{
$this->processItemStackRequestAction($action);
}catch(ItemStackRequestProcessException $e){

View File

@ -150,7 +150,7 @@ class LoginPacketHandler extends PacketHandler{
protected function fetchAuthData(JwtChain $chain) : AuthenticationData{
/** @var AuthenticationData|null $extraData */
$extraData = null;
foreach($chain->chain as $k => $jwt){
foreach($chain->chain as $jwt){
//validate every chain element
try{
[, $claims, ] = JwtUtils::parse($jwt);

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\permission;
use pocketmine\utils\Utils;
use function fclose;
use function fgets;
use function fopen;
@ -32,7 +33,10 @@ use function strtolower;
use function trim;
class BanList{
/** @var BanEntry[] */
/**
* @var BanEntry[]
* @phpstan-var array<string, BanEntry>
*/
private array $list = [];
private bool $enabled = true;
@ -101,7 +105,7 @@ class BanList{
}
public function removeExpired() : void{
foreach($this->list as $name => $entry){
foreach(Utils::promoteKeys($this->list) as $name => $entry){
if($entry->hasExpired()){
unset($this->list[$name]);
}

View File

@ -25,10 +25,14 @@ namespace pocketmine\permission;
use pocketmine\plugin\Plugin;
use pocketmine\plugin\PluginException;
use pocketmine\utils\Utils;
use function spl_object_id;
class PermissionAttachment{
/** @var bool[] */
/**
* @var bool[]
* @phpstan-var array<string, bool>
*/
private array $permissions = [];
/**
@ -60,6 +64,7 @@ class PermissionAttachment{
/**
* @return bool[]
* @phpstan-return array<string, bool>
*/
public function getPermissions() : array{
return $this->permissions;
@ -78,9 +83,10 @@ class PermissionAttachment{
/**
* @param bool[] $permissions
* @phpstan-param array<string, bool> $permissions
*/
public function setPermissions(array $permissions) : void{
foreach($permissions as $key => $value){
foreach(Utils::stringifyKeys($permissions) as $key => $value){
$this->permissions[$key] = $value;
}
$this->recalculatePermissibles();

View File

@ -23,6 +23,8 @@ declare(strict_types=1);
namespace pocketmine\permission;
use pocketmine\Server;
use pocketmine\utils\Utils;
use function count;
use function spl_object_id;
@ -37,9 +39,15 @@ class PermissionManager{
return self::$instance;
}
/** @var Permission[] */
/**
* @var Permission[]
* @phpstan-var array<string, Permission>
*/
protected array $permissions = [];
/** @var PermissibleInternal[][] */
/**
* @var PermissibleInternal[][]
* @phpstan-var array<string, array<int, PermissibleInternal>>
*/
protected array $permSubs = [];
public function getPermission(string $name) : ?Permission{
@ -64,6 +72,10 @@ class PermissionManager{
}
}
/**
* @deprecated Superseded by server chat broadcast channels
* @see Server::subscribeToBroadcastChannel()
*/
public function subscribeToPermission(string $permission, PermissibleInternal $permissible) : void{
if(!isset($this->permSubs[$permission])){
$this->permSubs[$permission] = [];
@ -71,6 +83,10 @@ class PermissionManager{
$this->permSubs[$permission][spl_object_id($permissible)] = $permissible;
}
/**
* @deprecated Superseded by server chat broadcast channels
* @see Server::unsubscribeFromBroadcastChannel()
*/
public function unsubscribeFromPermission(string $permission, PermissibleInternal $permissible) : void{
if(isset($this->permSubs[$permission][spl_object_id($permissible)])){
if(count($this->permSubs[$permission]) === 1){
@ -81,8 +97,12 @@ class PermissionManager{
}
}
/**
* @deprecated Superseded by server chat broadcast channels
* @see Server::unsubscribeFromAllBroadcastChannels()
*/
public function unsubscribeFromAllPermissions(PermissibleInternal $permissible) : void{
foreach($this->permSubs as $permission => $subs){
foreach(Utils::promoteKeys($this->permSubs) as $permission => $subs){
if(count($subs) === 1 && isset($subs[spl_object_id($permissible)])){
unset($this->permSubs[$permission]);
}else{
@ -92,6 +112,8 @@ class PermissionManager{
}
/**
* @deprecated Superseded by server chat broadcast channels
* @see Server::getBroadcastChannelSubscribers()
* @return PermissibleInternal[]
*/
public function getPermissionSubscriptions(string $permission) : array{

View File

@ -1794,7 +1794,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
return true;
}
if(!$this->isCreative() && !$block->getBreakInfo()->breaksInstantly()){
if(!$this->isCreative() && !$target->getBreakInfo()->breaksInstantly()){
$this->blockBreakHandler = new SurvivalBlockBreakHandler($this, $pos, $target, $face, 16);
}

View File

@ -70,7 +70,6 @@ final class ApiVersion{
* @return string[]
*/
public static function checkAmbiguousVersions(array $versions) : array{
/** @var VersionString[][] $indexedVersions */
$indexedVersions = [];
foreach($versions as $str){
@ -85,9 +84,8 @@ final class ApiVersion{
}
}
/** @var VersionString[] $result */
$result = [];
foreach($indexedVersions as $major => $list){
foreach($indexedVersions as $list){
if(count($list) > 1){
array_push($result, ...$list);
}

View File

@ -26,6 +26,7 @@ namespace pocketmine\plugin;
use pocketmine\permission\Permission;
use pocketmine\permission\PermissionParser;
use pocketmine\permission\PermissionParserException;
use pocketmine\utils\Utils;
use function array_map;
use function array_values;
use function get_debug_type;
@ -151,7 +152,7 @@ class PluginDescription{
$this->compatibleOperatingSystems = array_map("\strval", (array) ($plugin[self::KEY_OS] ?? []));
if(isset($plugin[self::KEY_COMMANDS]) && is_array($plugin[self::KEY_COMMANDS])){
foreach($plugin[self::KEY_COMMANDS] as $commandName => $commandData){
foreach(Utils::promoteKeys($plugin[self::KEY_COMMANDS]) as $commandName => $commandData){
if(!is_string($commandName)){
throw new PluginDescriptionParseException("Invalid Plugin commands, key must be the name of the command");
}
@ -177,7 +178,7 @@ class PluginDescription{
if(isset($plugin[self::KEY_EXTENSIONS])){
$extensions = (array) $plugin[self::KEY_EXTENSIONS];
$isLinear = $extensions === array_values($extensions);
foreach($extensions as $k => $v){
foreach(Utils::promoteKeys($extensions) as $k => $v){
if($isLinear){
$k = $v;
$v = "*";

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\plugin;
use pocketmine\utils\Utils;
use function array_flip;
use function is_array;
use function is_float;
@ -77,7 +78,7 @@ class PluginGraylist{
if(!is_array($array["plugins"])){
throw new \InvalidArgumentException("\"plugins\" must be an array");
}
foreach($array["plugins"] as $k => $v){
foreach(Utils::promoteKeys($array["plugins"]) as $k => $v){
if(!is_string($v) && !is_int($v) && !is_float($v)){
throw new \InvalidArgumentException("\"plugins\" contains invalid element at position $k");
}

View File

@ -73,10 +73,16 @@ use function strtolower;
* Manages all the plugins
*/
class PluginManager{
/** @var Plugin[] */
/**
* @var Plugin[]
* @phpstan-var array<string, Plugin>
*/
protected array $plugins = [];
/** @var Plugin[] */
/**
* @var Plugin[]
* @phpstan-var array<string, Plugin>
*/
protected array $enabledPlugins = [];
/** @var array<string, array<string, true>> */
@ -118,6 +124,7 @@ class PluginManager{
/**
* @return Plugin[]
* @phpstan-return array<string, Plugin>
*/
public function getPlugins() : array{
return $this->plugins;
@ -531,7 +538,7 @@ class PluginManager{
}
public function tickSchedulers(int $currentTick) : void{
foreach($this->enabledPlugins as $pluginName => $p){
foreach(Utils::promoteKeys($this->enabledPlugins) as $pluginName => $p){
if(isset($this->enabledPlugins[$pluginName])){
//the plugin may have been disabled as a result of updating other plugins' schedulers, and therefore
//removed from enabledPlugins; however, foreach will still see it due to copy-on-write

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\promise;
use pocketmine\utils\Utils;
use function count;
use function spl_object_id;
@ -84,7 +85,7 @@ final class Promise{
$toResolve = count($promises);
$continue = true;
foreach($promises as $key => $promise){
foreach(Utils::promoteKeys($promises) as $key => $promise){
$promise->onCompletion(
function(mixed $value) use ($resolver, $key, $toResolve, &$values) : void{
$values[$key] = $value;

View File

@ -25,6 +25,7 @@ namespace pocketmine\resourcepacks;
use pocketmine\utils\Config;
use pocketmine\utils\Filesystem;
use pocketmine\utils\Utils;
use Symfony\Component\Filesystem\Path;
use function array_keys;
use function copy;
@ -87,7 +88,7 @@ class ResourcePackManager{
throw new \InvalidArgumentException("\"resource_stack\" key should contain a list of pack names");
}
foreach($resourceStack as $pos => $pack){
foreach(Utils::promoteKeys($resourceStack) as $pos => $pack){
if(!is_string($pack) && !is_int($pack) && !is_float($pack)){
$logger->critical("Found invalid entry in resource pack list at offset $pos of type " . gettype($pack));
continue;

View File

@ -26,8 +26,12 @@ namespace pocketmine\scheduler;
use pocketmine\utils\Utils;
abstract class Task{
/** @phpstan-var TaskHandler<static>|null */
private ?TaskHandler $taskHandler = null;
/**
* @phpstan-return TaskHandler<static>|null
*/
final public function getHandler() : ?TaskHandler{
return $this->taskHandler;
}
@ -36,6 +40,9 @@ abstract class Task{
return Utils::getNiceClassName($this);
}
/**
* @phpstan-param TaskHandler<static>|null $taskHandler
*/
final public function setHandler(?TaskHandler $taskHandler) : void{
if($this->taskHandler === null || $taskHandler === null){
$this->taskHandler = $taskHandler;

View File

@ -26,6 +26,9 @@ namespace pocketmine\scheduler;
use pocketmine\timings\Timings;
use pocketmine\timings\TimingsHandler;
/**
* @template TTask of Task
*/
class TaskHandler{
protected int $nextRun;
@ -36,6 +39,9 @@ class TaskHandler{
private string $taskName;
private string $ownerName;
/**
* @phpstan-param TTask $task
*/
public function __construct(
protected Task $task,
protected int $delay = -1,
@ -66,6 +72,9 @@ class TaskHandler{
$this->nextRun = $ticks;
}
/**
* @phpstan-return TTask
*/
public function getTask() : Task{
return $this->task;
}

View File

@ -33,12 +33,12 @@ use pocketmine\utils\ReversePriorityQueue;
class TaskScheduler{
private bool $enabled = true;
/** @phpstan-var ReversePriorityQueue<int, TaskHandler> */
/** @phpstan-var ReversePriorityQueue<int, TaskHandler<covariant Task>> */
protected ReversePriorityQueue $queue;
/**
* @var ObjectSet|TaskHandler[]
* @phpstan-var ObjectSet<TaskHandler>
* @phpstan-var ObjectSet<TaskHandler<covariant Task>>
*/
protected ObjectSet $tasks;
@ -51,18 +51,42 @@ class TaskScheduler{
$this->tasks = new ObjectSet();
}
/**
* @phpstan-template TTask of Task
* @phpstan-param TTask $task
*
* @phpstan-return TaskHandler<TTask>
*/
public function scheduleTask(Task $task) : TaskHandler{
return $this->addTask($task, -1, -1);
}
/**
* @phpstan-template TTask of Task
* @phpstan-param TTask $task
*
* @phpstan-return TaskHandler<TTask>
*/
public function scheduleDelayedTask(Task $task, int $delay) : TaskHandler{
return $this->addTask($task, $delay, -1);
}
/**
* @phpstan-template TTask of Task
* @phpstan-param TTask $task
*
* @phpstan-return TaskHandler<TTask>
*/
public function scheduleRepeatingTask(Task $task, int $period) : TaskHandler{
return $this->addTask($task, -1, $period);
}
/**
* @phpstan-template TTask of Task
* @phpstan-param TTask $task
*
* @phpstan-return TaskHandler<TTask>
*/
public function scheduleDelayedRepeatingTask(Task $task, int $delay, int $period) : TaskHandler{
return $this->addTask($task, $delay, $period);
}
@ -77,10 +101,19 @@ class TaskScheduler{
}
}
/**
* @phpstan-param TaskHandler<covariant Task> $task
*/
public function isQueued(TaskHandler $task) : bool{
return $this->tasks->contains($task);
}
/**
* @phpstan-template TTask of Task
* @phpstan-param TTask $task
*
* @phpstan-return TaskHandler<TTask>
*/
private function addTask(Task $task, int $delay, int $period) : TaskHandler{
if(!$this->enabled){
throw new \LogicException("Tried to schedule task to disabled scheduler");
@ -99,6 +132,11 @@ class TaskScheduler{
return $this->handle(new TaskHandler($task, $delay, $period, $this->owner));
}
/**
* @phpstan-template TTask of Task
* @phpstan-param TaskHandler<TTask> $handler
* @phpstan-return TaskHandler<TTask>
*/
private function handle(TaskHandler $handler) : TaskHandler{
if($handler->isDelayed()){
$nextRun = $this->currentTick + $handler->getDelay();
@ -128,7 +166,7 @@ class TaskScheduler{
}
$this->currentTick = $currentTick;
while($this->isReady($this->currentTick)){
/** @var TaskHandler $task */
/** @phpstan-var TaskHandler<covariant Task> $task */
$task = $this->queue->extract();
if($task->isCancelled()){
$this->tasks->remove($task);

View File

@ -31,6 +31,7 @@ use pocketmine\network\mcpe\protocol\ClientboundPacket;
use pocketmine\network\mcpe\protocol\ServerboundPacket;
use pocketmine\player\Player;
use pocketmine\scheduler\AsyncTask;
use pocketmine\scheduler\Task;
use pocketmine\scheduler\TaskHandler;
use function get_class;
use function str_starts_with;
@ -195,6 +196,10 @@ abstract class Timings{
}
/**
* @template TTask of Task
* @phpstan-param TaskHandler<TTask> $task
*/
public static function getScheduledTaskTimings(TaskHandler $task, int $period) : TimingsHandler{
self::init();
$name = "Task: " . $task->getTaskName();

View File

@ -67,6 +67,8 @@ use function is_nan;
use function is_object;
use function is_string;
use function mb_check_encoding;
use function mt_getrandmax;
use function mt_rand;
use function ob_end_clean;
use function ob_get_contents;
use function ob_start;
@ -403,7 +405,7 @@ final class Utils{
}
/**
* @param mixed[] $trace
* @param mixed[][] $trace
* @return string[]
*/
public static function printableExceptionInfo(\Throwable $e, $trace = null) : array{
@ -445,6 +447,7 @@ final class Utils{
* @phpstan-param list<array<string, mixed>> $trace
*
* @return string[]
* @phpstan-return list<string>
*/
public static function printableTrace(array $trace, int $maxStringLength = 80) : array{
$messages = [];
@ -456,7 +459,7 @@ final class Utils{
}else{
$args = $trace[$i]["params"];
}
/** @var mixed[] $args */
/** @phpstan-var array<int, mixed> $args */
$paramsList = [];
$offset = 0;
@ -608,6 +611,18 @@ final class Utils{
}
}
/**
* Gets rid of PHPStan BenevolentUnionType on array keys, so that wrong type errors get reported properly
* Use this if you don't care what the key type is and just want proper PHPStan error reporting
*
* @phpstan-template TValueType
* @phpstan-param array<TValueType> $array
* @phpstan-return array<int|string, TValueType>
*/
public static function promoteKeys(array $array) : array{
return $array;
}
public static function checkUTF8(string $string) : void{
if(!mb_check_encoding($string, 'UTF-8')){
throw new \InvalidArgumentException("Text must be valid UTF-8");
@ -675,4 +690,12 @@ final class Utils{
//jit not available
return null;
}
/**
* Returns a random float between 0.0 and 1.0
* Drop-in replacement for lcg_value()
*/
public static function getRandomFloat() : float{
return mt_rand() / mt_getrandmax();
}
}

View File

@ -28,7 +28,10 @@ use pocketmine\math\Vector3;
use pocketmine\utils\Utils;
class BlockTransaction{
/** @var Block[][][] */
/**
* @var Block[][][]
* @phpstan-var array<int, array<int, array<int, Block>>>
*/
private array $blocks = [];
/**

View File

@ -83,6 +83,7 @@ use pocketmine\ServerConfigGroup;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Limits;
use pocketmine\utils\ReversePriorityQueue;
use pocketmine\utils\Utils;
use pocketmine\world\biome\Biome;
use pocketmine\world\biome\BiomeRegistry;
use pocketmine\world\format\Chunk;
@ -120,7 +121,6 @@ use function get_class;
use function gettype;
use function is_a;
use function is_object;
use function lcg_value;
use function max;
use function microtime;
use function min;
@ -1998,10 +1998,10 @@ class World implements ChunkManager{
return null;
}
$itemEntity = new ItemEntity(Location::fromObject($source, $this, lcg_value() * 360, 0), $item);
$itemEntity = new ItemEntity(Location::fromObject($source, $this, Utils::getRandomFloat() * 360, 0), $item);
$itemEntity->setPickupDelay($delay);
$itemEntity->setMotion($motion ?? new Vector3(lcg_value() * 0.2 - 0.1, 0.2, lcg_value() * 0.2 - 0.1));
$itemEntity->setMotion($motion ?? new Vector3(Utils::getRandomFloat() * 0.2 - 0.1, 0.2, Utils::getRandomFloat() * 0.2 - 0.1));
$itemEntity->spawnToAll();
return $itemEntity;
@ -2018,9 +2018,9 @@ class World implements ChunkManager{
$orbs = [];
foreach(ExperienceOrb::splitIntoOrbSizes($amount) as $split){
$orb = new ExperienceOrb(Location::fromObject($pos, $this, lcg_value() * 360, 0), $split);
$orb = new ExperienceOrb(Location::fromObject($pos, $this, Utils::getRandomFloat() * 360, 0), $split);
$orb->setMotion(new Vector3((lcg_value() * 0.2 - 0.1) * 2, lcg_value() * 0.4, (lcg_value() * 0.2 - 0.1) * 2));
$orb->setMotion(new Vector3((Utils::getRandomFloat() * 0.2 - 0.1) * 2, Utils::getRandomFloat() * 0.4, (Utils::getRandomFloat() * 0.2 - 0.1) * 2));
$orb->spawnToAll();
$orbs[] = $orb;

View File

@ -56,7 +56,10 @@ use function trim;
class WorldManager{
public const TICKS_PER_AUTOSAVE = 300 * Server::TARGET_TICKS_PER_SECOND;
/** @var World[] */
/**
* @var World[]
* @phpstan-var array<int, World>
*/
private array $worlds = [];
private ?World $defaultWorld = null;
@ -76,6 +79,7 @@ class WorldManager{
/**
* @return World[]
* @phpstan-return array<int, World>
*/
public function getWorlds() : array{
return $this->worlds;

View File

@ -24,7 +24,6 @@ declare(strict_types=1);
namespace pocketmine\world\format;
use function array_map;
use function array_values;
use function count;
class SubChunk{
@ -36,6 +35,7 @@ class SubChunk{
* SubChunk constructor.
*
* @param PalettedBlockArray[] $blockLayers
* @phpstan-param list<PalettedBlockArray> $blockLayers
*/
public function __construct(
private int $emptyBlockId,
@ -85,6 +85,7 @@ class SubChunk{
/**
* @return PalettedBlockArray[]
* @phpstan-return list<PalettedBlockArray>
*/
public function getBlockLayers() : array{
return $this->blockLayers;
@ -129,17 +130,18 @@ class SubChunk{
}
public function collectGarbage() : void{
foreach($this->blockLayers as $k => $layer){
$cleanedLayers = [];
foreach($this->blockLayers as $layer){
$layer->collectGarbage();
foreach($layer->getPalette() as $p){
if($p !== $this->emptyBlockId){
$cleanedLayers[] = $layer;
continue 2;
}
}
unset($this->blockLayers[$k]);
}
$this->blockLayers = array_values($this->blockLayers);
$this->blockLayers = $cleanedLayers;
$this->biomes->collectGarbage();
if($this->skyLight !== null && $this->skyLight->isUniform(0)){

View File

@ -65,6 +65,8 @@ abstract class BaseWorldProvider implements WorldProvider{
abstract protected function loadLevelData() : WorldData;
private function translatePalette(PalettedBlockArray $blockArray, \Logger $logger) : PalettedBlockArray{
//TODO: missing type info in stubs
/** @phpstan-var list<int> $palette */
$palette = $blockArray->getPalette();
$newPalette = [];

View File

@ -32,6 +32,10 @@ final class ChunkData{
* @param SubChunk[] $subChunks
* @param CompoundTag[] $entityNBT
* @param CompoundTag[] $tileNBT
*
* @phpstan-param array<int, SubChunk> $subChunks
* @phpstan-param list<CompoundTag> $entityNBT
* @phpstan-param list<CompoundTag> $tileNBT
*/
public function __construct(
private array $subChunks,
@ -42,14 +46,21 @@ final class ChunkData{
/**
* @return SubChunk[]
* @phpstan-return array<int, SubChunk>
*/
public function getSubChunks() : array{ return $this->subChunks; }
public function isPopulated() : bool{ return $this->populated; }
/** @return CompoundTag[] */
/**
* @return CompoundTag[]
* @phpstan-return list<CompoundTag>
*/
public function getEntityNBT() : array{ return $this->entityNBT; }
/** @return CompoundTag[] */
/**
* @return CompoundTag[]
* @phpstan-return list<CompoundTag>
*/
public function getTileNBT() : array{ return $this->tileNBT; }
}

View File

@ -31,7 +31,10 @@ use const SORT_NUMERIC;
final class RegionGarbageMap{
/** @var RegionLocationTableEntry[] */
/**
* @var RegionLocationTableEntry[]
* @phpstan-var array<int, RegionLocationTableEntry>
*/
private array $entries = [];
private bool $clean = false;
@ -48,7 +51,6 @@ final class RegionGarbageMap{
* @param RegionLocationTableEntry[]|null[] $locationTable
*/
public static function buildFromLocationTable(array $locationTable) : self{
/** @var RegionLocationTableEntry[] $usedMap */
$usedMap = [];
foreach($locationTable as $entry){
if($entry === null){
@ -62,12 +64,10 @@ final class RegionGarbageMap{
ksort($usedMap, SORT_NUMERIC);
/** @var RegionLocationTableEntry[] $garbageMap */
$garbageMap = [];
/** @var RegionLocationTableEntry|null $prevEntry */
$prevEntry = null;
foreach($usedMap as $firstSector => $entry){
foreach($usedMap as $entry){
$prevEndPlusOne = ($prevEntry !== null ? $prevEntry->getLastSector() + 1 : RegionLoader::FIRST_SECTOR);
$currentStart = $entry->getFirstSector();
if($prevEndPlusOne < $currentStart){

View File

@ -67,7 +67,10 @@ class RegionLoader{
/** @var resource */
protected $filePointer;
protected int $nextSector = self::FIRST_SECTOR;
/** @var RegionLocationTableEntry[]|null[] */
/**
* @var RegionLocationTableEntry[]|null[]
* @phpstan-var list<RegionLocationTableEntry|null>
*/
protected array $locationTable = [];
protected RegionGarbageMap $garbageTable;
public int $lastUsed;
@ -327,7 +330,6 @@ class RegionLoader{
* @throws CorruptedRegionException
*/
private function checkLocationTableValidity() : void{
/** @var int[] $usedOffsets */
$usedOffsets = [];
$fileSize = filesize($this->filePath);
@ -355,7 +357,7 @@ class RegionLoader{
}
ksort($usedOffsets, SORT_NUMERIC);
$prevLocationIndex = null;
foreach($usedOffsets as $startOffset => $locationTableIndex){
foreach($usedOffsets as $locationTableIndex){
if($this->locationTable[$locationTableIndex] === null){
continue;
}

View File

@ -72,7 +72,10 @@ abstract class RegionWorldProvider extends BaseWorldProvider{
return false;
}
/** @var RegionLoader[] */
/**
* @var RegionLoader[]
* @phpstan-var array<int, RegionLoader>
*/
protected array $regions = [];
protected function loadLevelData() : WorldData{

View File

@ -76,7 +76,10 @@ class PopulationTask extends AsyncTask{
$chunk = $this->chunk !== null ? FastChunkSerializer::deserializeTerrain($this->chunk) : null;
/** @var string[] $serialChunks */
/**
* @var string[] $serialChunks
* @phpstan-var array<int, string|null> $serialChunks
*/
$serialChunks = igbinary_unserialize($this->adjacentChunks);
$chunks = array_map(
function(?string $serialized) : ?Chunk{
@ -92,7 +95,6 @@ class PopulationTask extends AsyncTask{
self::setOrGenerateChunk($manager, $generator, $this->chunkX, $this->chunkZ, $chunk);
/** @var Chunk[] $resultChunks */
$resultChunks = []; //this is just to keep phpstan's type inference happy
foreach($chunks as $relativeChunkHash => $c){
World::getXZ($relativeChunkHash, $relativeX, $relativeZ);

View File

@ -0,0 +1,34 @@
<?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\world\sound;
use pocketmine\math\Vector3;
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
use pocketmine\network\mcpe\protocol\types\LevelSoundEvent;
final class IceBombHitSound implements Sound{
public function encode(Vector3 $pos) : array{
return [LevelSoundEventPacket::nonActorSound(LevelSoundEvent::ICEBOMB_HIT, $pos, false)];
}
}

View File

@ -801,7 +801,7 @@ parameters:
path: ../../../src/scheduler/BulkCurlTask.php
-
message: "#^Cannot call method getNextRun\\(\\) on array\\<string, int\\|pocketmine\\\\scheduler\\\\TaskHandler\\>\\|int\\|pocketmine\\\\scheduler\\\\TaskHandler\\.$#"
message: "#^Cannot call method getNextRun\\(\\) on array\\<string, int\\|pocketmine\\\\scheduler\\\\TaskHandler\\<covariant pocketmine\\\\scheduler\\\\Task\\>\\>\\|int\\|pocketmine\\\\scheduler\\\\TaskHandler\\<covariant pocketmine\\\\scheduler\\\\Task\\>\\.$#"
count: 1
path: ../../../src/scheduler/TaskScheduler.php
@ -840,11 +840,6 @@ parameters:
count: 1
path: ../../../src/utils/Utils.php
-
message: "#^Parameter \\#1 \\$trace of static method pocketmine\\\\utils\\\\Utils\\:\\:printableTrace\\(\\) expects array\\<int, array\\<string, mixed\\>\\>, array given\\.$#"
count: 1
path: ../../../src/utils/Utils.php
-
message: "#^Parameter \\#2 \\$file of class pocketmine\\\\thread\\\\ThreadCrashInfoFrame constructor expects string\\|null, mixed given\\.$#"
count: 1

View File

@ -110,3 +110,8 @@ parameters:
count: 2
path: ../../phpunit/promise/PromiseTest.php
-
message: "#^Strict comparison using \\=\\=\\= between 0 and 0 will always evaluate to true\\.$#"
count: 1
path: ../rules/UnsafeForeachArrayOfStringRule.php

View File

@ -28,6 +28,7 @@ use PhpParser\Node\Stmt\Foreach_;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Type\BenevolentUnionType;
use PHPStan\Type\ClassStringType;
use PHPStan\Type\IntegerType;
use PHPStan\Type\StringType;
@ -62,8 +63,17 @@ final class UnsafeForeachArrayOfStringRule implements Rule{
$hasCastableKeyTypes = false;
$expectsIntKeyTypes = false;
TypeTraverser::map($iterableType->getIterableKeyType(), function(Type $type, callable $traverse) use (&$hasCastableKeyTypes, &$expectsIntKeyTypes) : Type{
if($type instanceof IntegerType){
$implicitType = false;
$benevolentUnionDepth = 0;
TypeTraverser::map($iterableType->getIterableKeyType(), function(Type $type, callable $traverse) use (&$hasCastableKeyTypes, &$expectsIntKeyTypes, &$benevolentUnionDepth, &$implicitType) : Type{
if($type instanceof BenevolentUnionType){
$implicitType = true;
$benevolentUnionDepth++;
$result = $traverse($type);
$benevolentUnionDepth--;
return $result;
}
if($type instanceof IntegerType && $benevolentUnionDepth === 0){
$expectsIntKeyTypes = true;
return $type;
}
@ -78,12 +88,20 @@ final class UnsafeForeachArrayOfStringRule implements Rule{
return $type;
});
if($hasCastableKeyTypes && !$expectsIntKeyTypes){
$func = Utils::stringifyKeys(...);
$tip = $implicitType ?
sprintf(
"Declare a key type using @phpstan-var or @phpstan-param, or use %s() to promote the key type to get proper error reporting",
Utils::getNiceClosureName(Utils::promoteKeys(...))
) :
sprintf(
"Use %s() to get a \Generator that will force the keys to string",
Utils::getNiceClosureName(Utils::stringifyKeys(...)),
);
return [
RuleErrorBuilder::message(sprintf(
"Unsafe foreach on array with key type %s (they might be casted to int).",
$iterableType->getIterableKeyType()->describe(VerbosityLevel::value())
))->tip(sprintf("Use %s() for a safe Generator-based iterator.", Utils::getNiceClosureName($func)))->build()
))->tip($tip)->build()
];
}
return [];

View File

@ -135,7 +135,7 @@ class BlockTest extends TestCase{
}
$errors = [];
foreach($expected as $typeName => $numStates){
foreach(Utils::promoteKeys($expected) as $typeName => $numStates){
if(!is_string($typeName) || !is_int($numStates)){
throw new AssumptionFailedError("Old table should be array<string, int>");
}

View File

@ -33,15 +33,26 @@ use pocketmine\Server;
final class EventTest extends TestCase{
public function testHandlerInheritance() : void{
private Plugin $mockPlugin;
private PluginManager $pluginManager;
protected function setUp() : void{
HandlerListManager::global()->unregisterAll();
//TODO: this is a really bad hack and could break any time if PluginManager decides to access its Server field
//we really need to make it possible to register events without a Plugin or Server context
$mockServer = $this->createMock(Server::class);
$mockPlugin = self::createStub(Plugin::class);
$mockPlugin->method('isEnabled')->willReturn(true);
$this->mockPlugin = self::createStub(Plugin::class);
$this->mockPlugin->method('isEnabled')->willReturn(true);
$pluginManager = new PluginManager($mockServer, null);
$this->pluginManager = new PluginManager($mockServer, null);
}
public static function tearDownAfterClass() : void{
HandlerListManager::global()->unregisterAll();
}
public function testHandlerInheritance() : void{
$expectedOrder = [
TestGrandchildEvent::class,
TestChildEvent::class,
@ -50,13 +61,13 @@ final class EventTest extends TestCase{
$actualOrder = [];
foreach($expectedOrder as $class){
$pluginManager->registerEvent(
$this->pluginManager->registerEvent(
$class,
function(TestParentEvent $event) use (&$actualOrder, $class) : void{
$actualOrder[] = $class;
},
EventPriority::NORMAL,
$mockPlugin
$this->mockPlugin
);
}

View File

@ -89,7 +89,6 @@ class ItemTest extends TestCase{
}
public function testGetEnchantments() : void{
/** @var EnchantmentInstance[] $enchantments */
$enchantments = [
new EnchantmentInstance(VanillaEnchantments::EFFICIENCY(), 5),
new EnchantmentInstance(VanillaEnchantments::SHARPNESS(), 1)

View File

@ -47,7 +47,7 @@ final class CloningRegistryTraitTest extends TestCase{
public function testGetAllClone() : void{
$list1 = TestCloningRegistry::getAll();
$list2 = TestCloningRegistry::getAll();
foreach($list1 as $k => $member){
foreach(Utils::promoteKeys($list1) as $k => $member){
self::assertNotSame($member, $list2[$k], "VanillaBlocks ought to clone its members");
}
}

View File

@ -37,9 +37,13 @@ final class TestCloningRegistry{
/**
* @return \stdClass[]
* @phpstan-return array<string, \stdClass>
*/
public static function getAll() : array{
/** @var \stdClass[] $result */
/**
* @var \stdClass[] $result
* @phpstan-var array<string, \stdClass> $result
*/
$result = self::_registryGetAll();
return $result;
}

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\tools\compact_regions;
use pocketmine\utils\Utils;
use pocketmine\world\format\io\exception\CorruptedChunkException;
use pocketmine\world\format\io\region\CorruptedRegionException;
use pocketmine\world\format\io\region\RegionLoader;
@ -59,6 +60,7 @@ const SUPPORTED_EXTENSIONS = [
/**
* @param int[] $files
* @phpstan-param array<string, int> $files
* @phpstan-param-out array<string, int> $files
*/
function find_regions_recursive(string $dir, array &$files) : void{
$dirFiles = scandir($dir, SCANDIR_SORT_NONE);
@ -112,7 +114,7 @@ function main(array $argv) : int{
$corruptedFiles = [];
$doneCount = 0;
$totalCount = count($files);
foreach($files as $file => $size){
foreach(Utils::stringifyKeys($files) as $file => $size){
try{
$oldRegion = RegionLoader::loadExisting($file);
}catch(CorruptedRegionException $e){
@ -162,7 +164,7 @@ function main(array $argv) : int{
clearstatcache();
$newSize = 0;
foreach($files as $file => $oldSize){
foreach(Utils::stringifyKeys($files) as $file => $oldSize){
$newSize += file_exists($file) ? filesize($file) : 0;
}
$diff = $currentSize - $newSize;

View File

@ -198,12 +198,12 @@ class ParserPacketHandler extends PacketHandler{
$result = (array) ($object instanceof \JsonSerializable ? $object->jsonSerialize() : $object);
ksort($result, SORT_STRING);
foreach($result as $property => $value){
foreach(Utils::promoteKeys($result) as $property => $value){
if(is_object($value)){
$result[$property] = self::objectToOrderedArray($value);
}elseif(is_array($value)){
$array = [];
foreach($value as $k => $v){
foreach(Utils::promoteKeys($value) as $k => $v){
if(is_object($v)){
$array[$k] = self::objectToOrderedArray($v);
}else{
@ -224,7 +224,7 @@ class ParserPacketHandler extends PacketHandler{
}
if(is_array($object)){
$result = [];
foreach($object as $k => $v){
foreach(Utils::promoteKeys($object) as $k => $v){
$result[$k] = self::sort($v);
}
return $result;
@ -247,7 +247,7 @@ class ParserPacketHandler extends PacketHandler{
ksort($table, SORT_STRING);
file_put_contents($this->bedrockDataPath . '/required_item_list.json', json_encode($table, JSON_PRETTY_PRINT) . "\n");
foreach($packet->levelSettings->experiments->getExperiments() as $name => $experiment){
foreach(Utils::promoteKeys($packet->levelSettings->experiments->getExperiments()) as $name => $experiment){
echo "Experiment \"$name\" is " . ($experiment ? "" : "not ") . "active\n";
}
return true;
@ -317,8 +317,8 @@ class ParserPacketHandler extends PacketHandler{
$char = ord("A");
$outputsByKey = [];
foreach($entry->getInput() as $x => $row){
foreach($row as $y => $ingredient){
foreach(Utils::promoteKeys($entry->getInput()) as $x => $row){
foreach(Utils::promoteKeys($row) as $y => $ingredient){
if($ingredient->getDescriptor() === null){
$shape[$x][$y] = " ";
}else{
@ -337,7 +337,7 @@ class ParserPacketHandler extends PacketHandler{
}
$unlockingIngredients = $entry->getUnlockingRequirement()->getUnlockingIngredients();
return new ShapedRecipeData(
array_map(fn(array $array) => implode('', $array), $shape),
array_map(fn(array $array) => implode('', array_values($array)), array_values($shape)),
$outputsByKey,
array_map(fn(ItemStack $output) => $this->itemStackToJson($output), $entry->getOutput()),
$entry->getBlockName(),