mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-04-20 16:00:20 +00:00
Merge 'minor-next' into 'major-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/12111121061
This commit is contained in:
commit
007673cb96
@ -123,7 +123,7 @@ The following are required as a minimum for pull requests. PRs that don't meet t
|
||||
- Remember, PRs with small diffs are much easier to review. Small PRs are generally reviewed and merged much faster than large ones.
|
||||
- **Start small.** Try fixing minor bugs or doing something isolated (e.g. adding a new block or item) before attempting larger changes.
|
||||
- This helps you get familiar with the codebase, the contribution process, and the expectations of maintainers.
|
||||
- Check out the [issues page]() for something that you could tackle without too much effort.
|
||||
- Check out ["Easy task" issues](https://github.com/pmmp/PocketMine-MP/issues?q=is%3Aissue+is%3Aopen+label%3A%22Easy+task%22) on the issues page for something that you could tackle without too much effort.
|
||||
- **Do not copy-paste other people's code**. Many PRs involve discussion about the changes, and changes are often requested by reviewers. If you don't understand the code you're copy-pasting, your PR is likely to fail.
|
||||
- **Do not edit code directly on github.com.** We recommend learning how to use [`git`](https://git-scm.com). `git` allows you to "clone" a repository onto your computer, so that you can make changes using an IDE.
|
||||
- **Use an IDE, not a text editor.** We recommend PhpStorm or VSCode.
|
||||
|
@ -39,7 +39,7 @@ class Magma extends Opaque{
|
||||
}
|
||||
|
||||
public function onEntityInside(Entity $entity) : bool{
|
||||
if($entity instanceof Living && !$entity->isSneaking()){
|
||||
if($entity instanceof Living && !$entity->isSneaking() && $entity->getFrostWalkerLevel() === 0){
|
||||
$ev = new EntityDamageByBlockEvent($this, $entity, EntityDamageEvent::CAUSE_FIRE, 1);
|
||||
$entity->attack($ev);
|
||||
}
|
||||
|
60
src/command/ClosureCommand.php
Normal file
60
src/command/ClosureCommand.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?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\command;
|
||||
|
||||
use pocketmine\lang\Translatable;
|
||||
use pocketmine\utils\Utils;
|
||||
|
||||
/**
|
||||
* @phpstan-type Execute \Closure(CommandSender $sender, Command $command, string $commandLabel, list<string> $args) : mixed
|
||||
*/
|
||||
final class ClosureCommand extends Command{
|
||||
/** @phpstan-var Execute */
|
||||
private \Closure $execute;
|
||||
|
||||
/**
|
||||
* @param string[] $permissions
|
||||
* @phpstan-param Execute $execute
|
||||
*/
|
||||
public function __construct(
|
||||
string $name,
|
||||
\Closure $execute,
|
||||
array $permissions,
|
||||
Translatable|string $description = "",
|
||||
Translatable|string|null $usageMessage = null,
|
||||
array $aliases = []
|
||||
){
|
||||
Utils::validateCallableSignature(
|
||||
fn(CommandSender $sender, Command $command, string $commandLabel, array $args) : mixed => 1,
|
||||
$execute,
|
||||
);
|
||||
$this->execute = $execute;
|
||||
parent::__construct($name, $description, $usageMessage, $aliases);
|
||||
$this->setPermissions($permissions);
|
||||
}
|
||||
|
||||
public function execute(CommandSender $sender, string $commandLabel, array $args){
|
||||
return ($this->execute)($sender, $this, $commandLabel, $args);
|
||||
}
|
||||
}
|
@ -66,5 +66,7 @@ final class EnchantmentIdMap{
|
||||
$this->register(EnchantmentIds::VANISHING, VanillaEnchantments::VANISHING());
|
||||
|
||||
$this->register(EnchantmentIds::SWIFT_SNEAK, VanillaEnchantments::SWIFT_SNEAK());
|
||||
|
||||
$this->register(EnchantmentIds::FROST_WALKER, VanillaEnchantments::FROST_WALKER());
|
||||
}
|
||||
}
|
||||
|
@ -352,6 +352,7 @@ final class ItemSerializerDeserializerRegistrar{
|
||||
$this->map1to1Item(Ids::RAW_COPPER, Items::RAW_COPPER());
|
||||
$this->map1to1Item(Ids::RAW_GOLD, Items::RAW_GOLD());
|
||||
$this->map1to1Item(Ids::RAW_IRON, Items::RAW_IRON());
|
||||
$this->map1to1Item(Ids::RECOVERY_COMPASS, Items::RECOVERY_COMPASS());
|
||||
$this->map1to1Item(Ids::REDSTONE, Items::REDSTONE_DUST());
|
||||
$this->map1to1Item(Ids::RIB_ARMOR_TRIM_SMITHING_TEMPLATE, Items::RIB_ARMOR_TRIM_SMITHING_TEMPLATE());
|
||||
$this->map1to1Item(Ids::ROTTEN_FLESH, Items::ROTTEN_FLESH());
|
||||
|
@ -34,6 +34,7 @@ interface FoodSource extends Consumable{
|
||||
|
||||
/**
|
||||
* Returns whether a Human eating this FoodSource must have a non-full hunger bar.
|
||||
* This is ignored in creative mode and in peaceful difficulty.
|
||||
*/
|
||||
public function requiresHunger() : bool;
|
||||
}
|
||||
|
@ -68,6 +68,7 @@ use pocketmine\network\mcpe\protocol\types\PlayerPermissions;
|
||||
use pocketmine\network\mcpe\protocol\UpdateAbilitiesPacket;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\world\sound\TotemUseSound;
|
||||
use pocketmine\world\World;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use Ramsey\Uuid\UuidInterface;
|
||||
use function array_fill;
|
||||
@ -189,8 +190,16 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
|
||||
return $this->hungerManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the Human can eat food. This may return a different result than {@link HungerManager::isHungry()},
|
||||
* as HungerManager only handles the hunger bar.
|
||||
*/
|
||||
public function canEat() : bool{
|
||||
return $this->hungerManager->isHungry() || $this->getWorld()->getDifficulty() === World::DIFFICULTY_PEACEFUL;
|
||||
}
|
||||
|
||||
public function consumeObject(Consumable $consumable) : bool{
|
||||
if($consumable instanceof FoodSource && $consumable->requiresHunger() && !$this->hungerManager->isHungry()){
|
||||
if($consumable instanceof FoodSource && $consumable->requiresHunger() && !$this->canEat()){
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -88,7 +88,8 @@ class HungerManager{
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this Human may consume objects requiring hunger.
|
||||
* Returns whether the food level is below the maximum.
|
||||
* This doesn't decide if the entity can eat food. Use {@link Human::canEat()} for that.
|
||||
*/
|
||||
public function isHungry() : bool{
|
||||
return $this->getFood() < $this->getMaxFood();
|
||||
|
@ -25,6 +25,8 @@ namespace pocketmine\entity;
|
||||
|
||||
use pocketmine\block\Block;
|
||||
use pocketmine\block\BlockTypeIds;
|
||||
use pocketmine\block\VanillaBlocks;
|
||||
use pocketmine\block\Water;
|
||||
use pocketmine\data\bedrock\EffectIdMap;
|
||||
use pocketmine\entity\animation\DeathAnimation;
|
||||
use pocketmine\entity\animation\HurtAnimation;
|
||||
@ -44,6 +46,7 @@ use pocketmine\item\Durable;
|
||||
use pocketmine\item\enchantment\Enchantment;
|
||||
use pocketmine\item\enchantment\VanillaEnchantments;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\math\AxisAlignedBB;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\math\VoxelRayTrace;
|
||||
use pocketmine\nbt\tag\CompoundTag;
|
||||
@ -64,6 +67,7 @@ use pocketmine\world\sound\EntityLandSound;
|
||||
use pocketmine\world\sound\EntityLongFallSound;
|
||||
use pocketmine\world\sound\EntityShortFallSound;
|
||||
use pocketmine\world\sound\ItemBreakSound;
|
||||
use function abs;
|
||||
use function array_shift;
|
||||
use function atan2;
|
||||
use function ceil;
|
||||
@ -128,6 +132,8 @@ abstract class Living extends Entity{
|
||||
protected bool $gliding = false;
|
||||
protected bool $swimming = false;
|
||||
|
||||
private ?int $frostWalkerLevel = null;
|
||||
|
||||
protected function getInitialDragMultiplier() : float{ return 0.02; }
|
||||
|
||||
protected function getInitialGravity() : float{ return 0.08; }
|
||||
@ -151,6 +157,14 @@ abstract class Living extends Entity{
|
||||
$this->getViewers(),
|
||||
fn(EntityEventBroadcaster $broadcaster, array $recipients) => $broadcaster->onMobArmorChange($recipients, $this)
|
||||
)));
|
||||
$this->armorInventory->getListeners()->add(new CallbackInventoryListener(
|
||||
onSlotChange: function(Inventory $inventory, int $slot) : void{
|
||||
if($slot === ArmorInventory::SLOT_FEET){
|
||||
$this->frostWalkerLevel = null;
|
||||
}
|
||||
},
|
||||
onContentChange: function() : void{ $this->frostWalkerLevel = null; }
|
||||
));
|
||||
|
||||
$health = $this->getMaxHealth();
|
||||
|
||||
@ -687,6 +701,47 @@ abstract class Living extends Entity{
|
||||
return $hasUpdate;
|
||||
}
|
||||
|
||||
protected function move(float $dx, float $dy, float $dz) : void{
|
||||
$oldX = $this->location->x;
|
||||
$oldZ = $this->location->z;
|
||||
|
||||
parent::move($dx, $dy, $dz);
|
||||
|
||||
$frostWalkerLevel = $this->getFrostWalkerLevel();
|
||||
if($frostWalkerLevel > 0 && (abs($this->location->x - $oldX) > self::MOTION_THRESHOLD || abs($this->location->z - $oldZ) > self::MOTION_THRESHOLD)){
|
||||
$this->applyFrostWalker($frostWalkerLevel);
|
||||
}
|
||||
}
|
||||
|
||||
protected function applyFrostWalker(int $level) : void{
|
||||
$radius = $level + 2;
|
||||
$world = $this->getWorld();
|
||||
|
||||
$baseX = $this->location->getFloorX();
|
||||
$y = $this->location->getFloorY() - 1;
|
||||
$baseZ = $this->location->getFloorZ();
|
||||
|
||||
$frostedIce = VanillaBlocks::FROSTED_ICE();
|
||||
for($x = $baseX - $radius; $x <= $baseX + $radius; $x++){
|
||||
for($z = $baseZ - $radius; $z <= $baseZ + $radius; $z++){
|
||||
$block = $world->getBlockAt($x, $y, $z);
|
||||
if(
|
||||
!$block instanceof Water ||
|
||||
!$block->isSource() ||
|
||||
$world->getBlockAt($x, $y + 1, $z)->getTypeId() !== BlockTypeIds::AIR ||
|
||||
count($world->getNearbyEntities(AxisAlignedBB::one()->offset($x, $y, $z))) !== 0
|
||||
){
|
||||
continue;
|
||||
}
|
||||
$world->setBlockAt($x, $y, $z, $frostedIce);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getFrostWalkerLevel() : int{
|
||||
return $this->frostWalkerLevel ??= $this->armorInventory->getBoots()->getEnchantmentLevel(VanillaEnchantments::FROST_WALKER());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ticks the entity's air supply, consuming it when underwater and regenerating it when out of water.
|
||||
*/
|
||||
|
@ -44,6 +44,6 @@ abstract class Food extends Item implements FoodSourceItem{
|
||||
}
|
||||
|
||||
public function canStartUsingItem(Player $player) : bool{
|
||||
return !$this->requiresHunger() || $player->getHungerManager()->isHungry();
|
||||
return !$this->requiresHunger() || $player->canEat();
|
||||
}
|
||||
}
|
||||
|
@ -327,8 +327,9 @@ final class ItemTypeIds{
|
||||
public const GOAT_HORN = 20288;
|
||||
public const END_CRYSTAL = 20289;
|
||||
public const ICE_BOMB = 20290;
|
||||
public const RECOVERY_COMPASS = 20291;
|
||||
|
||||
public const FIRST_UNUSED_ITEM_ID = 20291;
|
||||
public const FIRST_UNUSED_ITEM_ID = 20292;
|
||||
|
||||
private static int $nextDynamicId = self::FIRST_UNUSED_ITEM_ID;
|
||||
|
||||
|
@ -1481,6 +1481,7 @@ final class StringToItemParser extends StringToTParser{
|
||||
$result->register("record_strad", fn() => Items::RECORD_STRAD());
|
||||
$result->register("record_wait", fn() => Items::RECORD_WAIT());
|
||||
$result->register("record_ward", fn() => Items::RECORD_WARD());
|
||||
$result->register("recovery_compass", fn() => Items::RECOVERY_COMPASS());
|
||||
$result->register("redstone", fn() => Items::REDSTONE_DUST());
|
||||
$result->register("redstone_dust", fn() => Items::REDSTONE_DUST());
|
||||
$result->register("rib_armor_trim_smithing_template", fn() => Items::RIB_ARMOR_TRIM_SMITHING_TEMPLATE());
|
||||
|
@ -284,6 +284,7 @@ use function strtolower;
|
||||
* @method static Record RECORD_STRAD()
|
||||
* @method static Record RECORD_WAIT()
|
||||
* @method static Record RECORD_WARD()
|
||||
* @method static Item RECOVERY_COMPASS()
|
||||
* @method static Redstone REDSTONE_DUST()
|
||||
* @method static Item RIB_ARMOR_TRIM_SMITHING_TEMPLATE()
|
||||
* @method static RottenFlesh ROTTEN_FLESH()
|
||||
@ -574,6 +575,7 @@ final class VanillaItems{
|
||||
self::register("record_strad", fn(IID $id) => new Record($id, RecordType::DISK_STRAD, "Record Strad"));
|
||||
self::register("record_wait", fn(IID $id) => new Record($id, RecordType::DISK_WAIT, "Record Wait"));
|
||||
self::register("record_ward", fn(IID $id) => new Record($id, RecordType::DISK_WARD, "Record Ward"));
|
||||
self::register("recovery_compass", fn(IID $id) => new Item($id, "Recovery Compass"));
|
||||
self::register("redstone_dust", fn(IID $id) => new Redstone($id, "Redstone"));
|
||||
self::register("rotten_flesh", fn(IID $id) => new RottenFlesh($id, "Rotten Flesh"));
|
||||
self::register("scute", fn(IID $id) => new Item($id, "Scute"));
|
||||
|
@ -57,6 +57,7 @@ final class AvailableEnchantmentRegistry{
|
||||
$this->register(Enchantments::THORNS(), [Tags::CHESTPLATE], [Tags::HELMET, Tags::LEGGINGS, Tags::BOOTS]);
|
||||
$this->register(Enchantments::RESPIRATION(), [Tags::HELMET], []);
|
||||
$this->register(Enchantments::AQUA_AFFINITY(), [Tags::HELMET], []);
|
||||
$this->register(Enchantments::FROST_WALKER(), [/* no primary items */], [Tags::BOOTS]);
|
||||
$this->register(Enchantments::SHARPNESS(), [Tags::SWORD, Tags::AXE], []);
|
||||
$this->register(Enchantments::KNOCKBACK(), [Tags::SWORD], []);
|
||||
$this->register(Enchantments::FIRE_ASPECT(), [Tags::SWORD], []);
|
||||
|
@ -44,6 +44,7 @@ final class StringToEnchantmentParser extends StringToTParser{
|
||||
$result->register("fire_protection", fn() => VanillaEnchantments::FIRE_PROTECTION());
|
||||
$result->register("flame", fn() => VanillaEnchantments::FLAME());
|
||||
$result->register("fortune", fn() => VanillaEnchantments::FORTUNE());
|
||||
$result->register("frost_walker", fn() => VanillaEnchantments::FROST_WALKER());
|
||||
$result->register("infinity", fn() => VanillaEnchantments::INFINITY());
|
||||
$result->register("knockback", fn() => VanillaEnchantments::KNOCKBACK());
|
||||
$result->register("mending", fn() => VanillaEnchantments::MENDING());
|
||||
|
@ -41,6 +41,7 @@ use pocketmine\utils\RegistryTrait;
|
||||
* @method static ProtectionEnchantment FIRE_PROTECTION()
|
||||
* @method static Enchantment FLAME()
|
||||
* @method static Enchantment FORTUNE()
|
||||
* @method static Enchantment FROST_WALKER()
|
||||
* @method static Enchantment INFINITY()
|
||||
* @method static KnockbackEnchantment KNOCKBACK()
|
||||
* @method static Enchantment MENDING()
|
||||
@ -131,6 +132,16 @@ final class VanillaEnchantments{
|
||||
fn(int $level) : int => 10 * $level,
|
||||
30
|
||||
));
|
||||
|
||||
self::register("FROST_WALKER", new Enchantment(
|
||||
KnownTranslationFactory::enchantment_frostwalker(),
|
||||
Rarity::RARE,
|
||||
0,
|
||||
0,
|
||||
2,
|
||||
fn(int $level) : int => 10 * $level,
|
||||
15
|
||||
));
|
||||
self::register("AQUA_AFFINITY", new Enchantment(
|
||||
KnownTranslationFactory::enchantment_waterWorker(),
|
||||
Rarity::RARE,
|
||||
|
@ -215,7 +215,10 @@ class InGamePacketHandler extends PacketHandler{
|
||||
if($inputFlags !== $this->lastPlayerAuthInputFlags){
|
||||
$this->lastPlayerAuthInputFlags = $inputFlags;
|
||||
|
||||
$sneaking = $this->resolveOnOffInputFlags($inputFlags, PlayerAuthInputFlags::START_SNEAKING, PlayerAuthInputFlags::STOP_SNEAKING);
|
||||
$sneaking = $packet->hasFlag(PlayerAuthInputFlags::SNEAKING);
|
||||
if($this->player->isSneaking() === $sneaking){
|
||||
$sneaking = null;
|
||||
}
|
||||
$sprinting = $this->resolveOnOffInputFlags($inputFlags, PlayerAuthInputFlags::START_SPRINTING, PlayerAuthInputFlags::STOP_SPRINTING);
|
||||
$swimming = $this->resolveOnOffInputFlags($inputFlags, PlayerAuthInputFlags::START_SWIMMING, PlayerAuthInputFlags::STOP_SWIMMING);
|
||||
$gliding = $this->resolveOnOffInputFlags($inputFlags, PlayerAuthInputFlags::START_GLIDING, PlayerAuthInputFlags::STOP_GLIDING);
|
||||
|
@ -111,6 +111,7 @@ use pocketmine\network\mcpe\protocol\AnimatePacket;
|
||||
use pocketmine\network\mcpe\protocol\MovePlayerPacket;
|
||||
use pocketmine\network\mcpe\protocol\SetActorMotionPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\BlockPosition;
|
||||
use pocketmine\network\mcpe\protocol\types\DimensionIds;
|
||||
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataCollection;
|
||||
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataFlags;
|
||||
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataProperties;
|
||||
@ -192,6 +193,10 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
private const TAG_SPAWN_X = "SpawnX"; //TAG_Int
|
||||
private const TAG_SPAWN_Y = "SpawnY"; //TAG_Int
|
||||
private const TAG_SPAWN_Z = "SpawnZ"; //TAG_Int
|
||||
private const TAG_DEATH_WORLD = "DeathLevel"; //TAG_String
|
||||
private const TAG_DEATH_X = "DeathPositionX"; //TAG_Int
|
||||
private const TAG_DEATH_Y = "DeathPositionY"; //TAG_Int
|
||||
private const TAG_DEATH_Z = "DeathPositionZ"; //TAG_Int
|
||||
public const TAG_LEVEL = "Level"; //TAG_String
|
||||
public const TAG_LAST_KNOWN_XUID = "LastKnownXUID"; //TAG_String
|
||||
|
||||
@ -274,6 +279,8 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
|
||||
private bool $respawnLocked = false;
|
||||
|
||||
private ?Position $deathPosition = null;
|
||||
|
||||
//TODO: Abilities
|
||||
protected bool $autoJump = true;
|
||||
protected bool $allowFlight = false;
|
||||
@ -396,6 +403,9 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
if(($world = $this->server->getWorldManager()->getWorldByName($nbt->getString(self::TAG_SPAWN_WORLD, ""))) instanceof World){
|
||||
$this->spawnPosition = new Position($nbt->getInt(self::TAG_SPAWN_X), $nbt->getInt(self::TAG_SPAWN_Y), $nbt->getInt(self::TAG_SPAWN_Z), $world);
|
||||
}
|
||||
if(($world = $this->server->getWorldManager()->getWorldByName($nbt->getString(self::TAG_DEATH_WORLD, ""))) instanceof World){
|
||||
$this->deathPosition = new Position($nbt->getInt(self::TAG_DEATH_X), $nbt->getInt(self::TAG_DEATH_Y), $nbt->getInt(self::TAG_DEATH_Z), $world);
|
||||
}
|
||||
}
|
||||
|
||||
public function getLeaveMessage() : Translatable|string{
|
||||
@ -1037,6 +1047,30 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
}
|
||||
}
|
||||
|
||||
public function getDeathPosition() : ?Position{
|
||||
if($this->deathPosition !== null && !$this->deathPosition->isValid()){
|
||||
$this->deathPosition = null;
|
||||
}
|
||||
return $this->deathPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Vector3|Position|null $pos
|
||||
*/
|
||||
public function setDeathPosition(?Vector3 $pos) : void{
|
||||
if($pos !== null){
|
||||
if($pos instanceof Position && $pos->world !== null){
|
||||
$world = $pos->world;
|
||||
}else{
|
||||
$world = $this->getWorld();
|
||||
}
|
||||
$this->deathPosition = new Position($pos->x, $pos->y, $pos->z, $world);
|
||||
}else{
|
||||
$this->deathPosition = null;
|
||||
}
|
||||
$this->networkPropertiesDirty = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Position
|
||||
*/
|
||||
@ -1476,6 +1510,10 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function canEat() : bool{
|
||||
return $this->isCreative() || parent::canEat();
|
||||
}
|
||||
|
||||
public function canBreathe() : bool{
|
||||
return $this->isCreative() || parent::canBreathe();
|
||||
}
|
||||
@ -2337,6 +2375,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
unset($this->cursorInventory);
|
||||
unset($this->craftingGrid);
|
||||
$this->spawnPosition = null;
|
||||
$this->deathPosition = null;
|
||||
$this->blockBreakHandler = null;
|
||||
parent::destroyCycles();
|
||||
}
|
||||
@ -2378,6 +2417,13 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
$nbt->setInt(self::TAG_SPAWN_Z, $spawn->getFloorZ());
|
||||
}
|
||||
|
||||
if($this->deathPosition !== null && $this->deathPosition->isValid()){
|
||||
$nbt->setString(self::TAG_DEATH_WORLD, $this->deathPosition->getWorld()->getFolderName());
|
||||
$nbt->setInt(self::TAG_DEATH_X, $this->deathPosition->getFloorX());
|
||||
$nbt->setInt(self::TAG_DEATH_Y, $this->deathPosition->getFloorY());
|
||||
$nbt->setInt(self::TAG_DEATH_Z, $this->deathPosition->getFloorZ());
|
||||
}
|
||||
|
||||
$nbt->setInt(self::TAG_GAME_MODE, GameModeIdMap::getInstance()->toId($this->gamemode));
|
||||
$nbt->setLong(self::TAG_FIRST_PLAYED, (int) $this->firstPlayed->format('Uv'));
|
||||
$nbt->setLong(self::TAG_LAST_PLAYED, (int) floor(microtime(true) * 1000));
|
||||
@ -2397,6 +2443,8 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
//main inventory and drops the rest on the ground.
|
||||
$this->removeCurrentWindow();
|
||||
|
||||
$this->setDeathPosition($this->getPosition());
|
||||
|
||||
$ev = new PlayerDeathEvent($this, $this->getDrops(), $this->getXpDropAmount(), null);
|
||||
$ev->call();
|
||||
|
||||
@ -2525,6 +2573,17 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
|
||||
$properties->setPlayerFlag(PlayerMetadataFlags::SLEEP, $this->sleeping !== null);
|
||||
$properties->setBlockPos(EntityMetadataProperties::PLAYER_BED_POSITION, $this->sleeping !== null ? BlockPosition::fromVector3($this->sleeping) : new BlockPosition(0, 0, 0));
|
||||
|
||||
if($this->deathPosition !== null && $this->deathPosition->world === $this->location->world){
|
||||
$properties->setBlockPos(EntityMetadataProperties::PLAYER_DEATH_POSITION, BlockPosition::fromVector3($this->deathPosition));
|
||||
//TODO: this should be updated when dimensions are implemented
|
||||
$properties->setInt(EntityMetadataProperties::PLAYER_DEATH_DIMENSION, DimensionIds::OVERWORLD);
|
||||
$properties->setByte(EntityMetadataProperties::PLAYER_HAS_DIED, 1);
|
||||
}else{
|
||||
$properties->setBlockPos(EntityMetadataProperties::PLAYER_DEATH_POSITION, new BlockPosition(0, 0, 0));
|
||||
$properties->setInt(EntityMetadataProperties::PLAYER_DEATH_DIMENSION, DimensionIds::OVERWORLD);
|
||||
$properties->setByte(EntityMetadataProperties::PLAYER_HAS_DIED, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public function sendData(?array $targets, ?array $data = null) : void{
|
||||
|
Loading…
x
Reference in New Issue
Block a user