mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-04-23 00:55:57 +00:00
Implement Cave Vines & Glow Berries (#5424)
This commit is contained in:
parent
d834266635
commit
fa719f37d5
@ -713,8 +713,9 @@ final class BlockTypeIds{
|
||||
public const AZALEA_LEAVES = 10686;
|
||||
public const FLOWERING_AZALEA_LEAVES = 10687;
|
||||
public const REINFORCED_DEEPSLATE = 10688;
|
||||
public const CAVE_VINES = 10689;
|
||||
|
||||
public const FIRST_UNUSED_BLOCK_ID = 10689;
|
||||
public const FIRST_UNUSED_BLOCK_ID = 10690;
|
||||
|
||||
private static int $nextDynamicId = self::FIRST_UNUSED_BLOCK_ID;
|
||||
|
||||
|
191
src/block/CaveVines.php
Normal file
191
src/block/CaveVines.php
Normal file
@ -0,0 +1,191 @@
|
||||
<?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\block;
|
||||
|
||||
use pocketmine\block\utils\SupportType;
|
||||
use pocketmine\data\runtime\RuntimeDataDescriber;
|
||||
use pocketmine\entity\Entity;
|
||||
use pocketmine\event\block\BlockGrowEvent;
|
||||
use pocketmine\item\Fertilizer;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\item\VanillaItems;
|
||||
use pocketmine\math\Facing;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\world\BlockTransaction;
|
||||
use pocketmine\world\sound\GlowBerriesPickSound;
|
||||
use function mt_rand;
|
||||
|
||||
class CaveVines extends Flowable{
|
||||
public const MAX_AGE = 25;
|
||||
|
||||
protected int $age = 0;
|
||||
protected bool $berries = false;
|
||||
protected bool $head = false;
|
||||
|
||||
protected function describeState(RuntimeDataDescriber $w) : void{
|
||||
$w->boundedInt(5, 0, self::MAX_AGE, $this->age);
|
||||
$w->bool($this->berries);
|
||||
$w->bool($this->head);
|
||||
}
|
||||
|
||||
public function hasBerries() : bool{ return $this->berries; }
|
||||
|
||||
/** @return $this */
|
||||
public function setBerries(bool $berries) : self{
|
||||
$this->berries = $berries;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isHead() : bool{ return $this->head; }
|
||||
|
||||
/** @return $this */
|
||||
public function setHead(bool $head) : self{
|
||||
$this->head = $head;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAge() : int{
|
||||
return $this->age;
|
||||
}
|
||||
|
||||
/** @return $this */
|
||||
public function setAge(int $age) : self{
|
||||
if($age < 0 || $age > self::MAX_AGE){
|
||||
throw new \InvalidArgumentException("Age must be in range 0-" . self::MAX_AGE);
|
||||
}
|
||||
$this->age = $age;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function canClimb() : bool{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getLightLevel() : int{
|
||||
return $this->berries ? 14 : 0;
|
||||
}
|
||||
|
||||
private function canBeSupportedBy(Block $block) : bool{
|
||||
return $block->getSupportType(Facing::DOWN)->equals(SupportType::FULL()) || $block->hasSameTypeId($this);
|
||||
}
|
||||
|
||||
public function onNearbyBlockChange() : void{
|
||||
if(!$this->canBeSupportedBy($this->getSide(Facing::UP))){
|
||||
$this->position->getWorld()->useBreakOn($this->position);
|
||||
}
|
||||
}
|
||||
|
||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
if(!$this->canBeSupportedBy($blockReplace->getSide(Facing::UP))){
|
||||
return false;
|
||||
}
|
||||
$this->age = mt_rand(0, self::MAX_AGE);
|
||||
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
|
||||
}
|
||||
|
||||
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
|
||||
if($this->berries){
|
||||
$this->position->getWorld()->dropItem($this->position, $this->asItem());
|
||||
$this->position->getWorld()->addSound($this->position, new GlowBerriesPickSound());
|
||||
|
||||
$this->position->getWorld()->setBlock($this->position, $this->setBerries(false));
|
||||
return true;
|
||||
}
|
||||
if($item instanceof Fertilizer){
|
||||
$ev = new BlockGrowEvent($this, (clone $this)
|
||||
->setBerries(true)
|
||||
->setHead(!$this->getSide(Facing::DOWN)->hasSameTypeId($this))
|
||||
);
|
||||
$ev->call();
|
||||
if($ev->isCancelled()){
|
||||
return false;
|
||||
}
|
||||
$item->pop();
|
||||
$this->position->getWorld()->setBlock($this->position, $ev->getNewState());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function onRandomTick() : void{
|
||||
$head = !$this->getSide(Facing::DOWN)->hasSameTypeId($this);
|
||||
if($head !== $this->head){
|
||||
$this->position->getWorld()->setBlock($this->position, $this->setHead($head));
|
||||
}
|
||||
|
||||
if($this->age < self::MAX_AGE && mt_rand(1, 10) === 1){
|
||||
$growthPos = $this->position->getSide(Facing::DOWN);
|
||||
$world = $growthPos->getWorld();
|
||||
if($world->isInWorld($growthPos->getFloorX(), $growthPos->getFloorY(), $growthPos->getFloorZ())){
|
||||
$block = $world->getBlock($growthPos);
|
||||
if($block->getTypeId() === BlockTypeIds::AIR){
|
||||
$ev = new BlockGrowEvent($block, VanillaBlocks::CAVE_VINES()
|
||||
->setAge($this->age + 1)
|
||||
->setBerries(mt_rand(1, 9) === 1)
|
||||
);
|
||||
|
||||
$ev->call();
|
||||
|
||||
if(!$ev->isCancelled()){
|
||||
$world->setBlock($growthPos, $ev->getNewState());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function ticksRandomly() : bool{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function recalculateCollisionBoxes() : array{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function hasEntityCollision() : bool{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function onEntityInside(Entity $entity) : bool{
|
||||
$entity->resetFallDistance();
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getDropsForCompatibleTool(Item $item) : array{
|
||||
return $this->berries ? [$this->asItem()] : [];
|
||||
}
|
||||
|
||||
public function isAffectedBySilkTouch() : bool{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function asItem() : Item{
|
||||
return VanillaItems::GLOW_BERRIES();
|
||||
}
|
||||
|
||||
public function getSupportType(int $facing) : SupportType{
|
||||
return SupportType::NONE();
|
||||
}
|
||||
}
|
@ -156,6 +156,7 @@ use function mb_strtolower;
|
||||
* @method static CartographyTable CARTOGRAPHY_TABLE()
|
||||
* @method static CarvedPumpkin CARVED_PUMPKIN()
|
||||
* @method static Cauldron CAULDRON()
|
||||
* @method static CaveVines CAVE_VINES()
|
||||
* @method static Chain CHAIN()
|
||||
* @method static ChemicalHeat CHEMICAL_HEAT()
|
||||
* @method static Chest CHEST()
|
||||
@ -1579,6 +1580,8 @@ final class VanillaBlocks{
|
||||
self::register("cake_with_dyed_candle", new CakeWithDyedCandle(new BID(Ids::CAKE_WITH_DYED_CANDLE), "Cake With Dyed Candle", $cakeBreakInfo));
|
||||
|
||||
self::register("hanging_roots", new HangingRoots(new BID(Ids::HANGING_ROOTS), "Hanging Roots", new Info(BreakInfo::instant(ToolType::SHEARS, 1))));
|
||||
|
||||
self::register("cave_vines", new CaveVines(new BID(Ids::CAVE_VINES), "Cave Vines", new Info(BreakInfo::instant())));
|
||||
}
|
||||
|
||||
private static function registerBlocksR18() : void{
|
||||
|
@ -44,6 +44,7 @@ use pocketmine\block\Candle;
|
||||
use pocketmine\block\Carpet;
|
||||
use pocketmine\block\Carrot;
|
||||
use pocketmine\block\CarvedPumpkin;
|
||||
use pocketmine\block\CaveVines;
|
||||
use pocketmine\block\Chain;
|
||||
use pocketmine\block\ChemistryTable;
|
||||
use pocketmine\block\Chest;
|
||||
@ -773,6 +774,17 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{
|
||||
return Writer::create(Ids::CARVED_PUMPKIN)
|
||||
->writeLegacyHorizontalFacing($block->getFacing());
|
||||
});
|
||||
$this->map(Blocks::CAVE_VINES(), function(CaveVines $block) : Writer{
|
||||
//I have no idea why this only has 3 IDs - there are 4 in Java and 4 visually distinct states in Bedrock
|
||||
return Writer::create($block->hasBerries() ?
|
||||
($block->isHead() ?
|
||||
Ids::CAVE_VINES_HEAD_WITH_BERRIES :
|
||||
Ids::CAVE_VINES_BODY_WITH_BERRIES
|
||||
) :
|
||||
Ids::CAVE_VINES
|
||||
)
|
||||
->writeInt(StateNames::GROWING_PLANT_AGE, $block->getAge());
|
||||
});
|
||||
$this->map(Blocks::CHAIN(), function(Chain $block) : Writer{
|
||||
return Writer::create(Ids::CHAIN)
|
||||
->writePillarAxis($block->getAxis());
|
||||
|
@ -25,6 +25,7 @@ namespace pocketmine\data\bedrock\block\convert;
|
||||
|
||||
use pocketmine\block\Bamboo;
|
||||
use pocketmine\block\Block;
|
||||
use pocketmine\block\CaveVines;
|
||||
use pocketmine\block\ChorusFlower;
|
||||
use pocketmine\block\Light;
|
||||
use pocketmine\block\Slab;
|
||||
@ -611,6 +612,24 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
|
||||
return Blocks::CARVED_PUMPKIN()
|
||||
->setFacing($in->readLegacyHorizontalFacing());
|
||||
});
|
||||
$this->map(Ids::CAVE_VINES, function(Reader $in) : CaveVines{
|
||||
return Blocks::CAVE_VINES()
|
||||
->setBerries(false)
|
||||
->setHead(false)
|
||||
->setAge($in->readBoundedInt(StateNames::GROWING_PLANT_AGE, 0, 25));
|
||||
});
|
||||
$this->map(Ids::CAVE_VINES_BODY_WITH_BERRIES, function(Reader $in) : CaveVines{
|
||||
return Blocks::CAVE_VINES()
|
||||
->setBerries(true)
|
||||
->setHead(false)
|
||||
->setAge($in->readBoundedInt(StateNames::GROWING_PLANT_AGE, 0, 25));
|
||||
});
|
||||
$this->map(Ids::CAVE_VINES_HEAD_WITH_BERRIES, function(Reader $in) : CaveVines{
|
||||
return Blocks::CAVE_VINES()
|
||||
->setBerries(true)
|
||||
->setHead(true)
|
||||
->setAge($in->readBoundedInt(StateNames::GROWING_PLANT_AGE, 0, 25));
|
||||
});
|
||||
$this->map(Ids::CHAIN, function(Reader $in) : Block{
|
||||
return Blocks::CHAIN()
|
||||
->setAxis($in->readPillarAxis());
|
||||
|
@ -233,6 +233,7 @@ final class ItemSerializerDeserializerRegistrar{
|
||||
$this->map1to1Item(Ids::GHAST_TEAR, Items::GHAST_TEAR());
|
||||
$this->map1to1Item(Ids::GLASS_BOTTLE, Items::GLASS_BOTTLE());
|
||||
$this->map1to1Item(Ids::GLISTERING_MELON_SLICE, Items::GLISTERING_MELON());
|
||||
$this->map1to1Item(Ids::GLOW_BERRIES, Items::GLOW_BERRIES());
|
||||
$this->map1to1Item(Ids::GLOW_INK_SAC, Items::GLOW_INK_SAC());
|
||||
$this->map1to1Item(Ids::GLOWSTONE_DUST, Items::GLOWSTONE_DUST());
|
||||
$this->map1to1Item(Ids::GOLD_INGOT, Items::GOLD_INGOT());
|
||||
|
42
src/item/GlowBerries.php
Normal file
42
src/item/GlowBerries.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?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\block\Block;
|
||||
use pocketmine\block\VanillaBlocks;
|
||||
|
||||
class GlowBerries extends Food{
|
||||
|
||||
public function getFoodRestore() : int{
|
||||
return 2;
|
||||
}
|
||||
|
||||
public function getSaturationRestore() : float{
|
||||
return 0.4;
|
||||
}
|
||||
|
||||
public function getBlock(?int $clickedFace = null) : Block{
|
||||
return VanillaBlocks::CAVE_VINES();
|
||||
}
|
||||
}
|
@ -301,8 +301,9 @@ final class ItemTypeIds{
|
||||
public const TURTLE_HELMET = 20262;
|
||||
public const MEDICINE = 20263;
|
||||
public const MANGROVE_BOAT = 20264;
|
||||
public const GLOW_BERRIES = 20265;
|
||||
|
||||
public const FIRST_UNUSED_ITEM_ID = 20265;
|
||||
public const FIRST_UNUSED_ITEM_ID = 20266;
|
||||
|
||||
private static int $nextDynamicId = self::FIRST_UNUSED_ITEM_ID;
|
||||
|
||||
|
@ -206,6 +206,7 @@ final class StringToItemParser extends StringToTParser{
|
||||
$result->registerBlock("carrots", fn() => Blocks::CARROTS());
|
||||
$result->registerBlock("carved_pumpkin", fn() => Blocks::CARVED_PUMPKIN());
|
||||
$result->registerBlock("cauldron", fn() => Blocks::CAULDRON());
|
||||
$result->registerBlock("cave_vines", fn() => Blocks::CAVE_VINES());
|
||||
$result->registerBlock("chain", fn() => Blocks::CHAIN());
|
||||
$result->registerBlock("chemical_heat", fn() => Blocks::CHEMICAL_HEAT());
|
||||
$result->registerBlock("chemistry_table", fn() => Blocks::COMPOUND_CREATOR());
|
||||
@ -1275,6 +1276,7 @@ final class StringToItemParser extends StringToTParser{
|
||||
$result->register("ghast_tear", fn() => Items::GHAST_TEAR());
|
||||
$result->register("glass_bottle", fn() => Items::GLASS_BOTTLE());
|
||||
$result->register("glistering_melon", fn() => Items::GLISTERING_MELON());
|
||||
$result->register("glow_berries", fn() => Items::GLOW_BERRIES());
|
||||
$result->register("glow_ink_sac", fn() => Items::GLOW_INK_SAC());
|
||||
$result->register("glowstone_dust", fn() => Items::GLOWSTONE_DUST());
|
||||
$result->register("gold_axe", fn() => Items::GOLDEN_AXE());
|
||||
|
@ -163,6 +163,7 @@ use pocketmine\world\World;
|
||||
* @method static GlassBottle GLASS_BOTTLE()
|
||||
* @method static Item GLISTERING_MELON()
|
||||
* @method static Item GLOWSTONE_DUST()
|
||||
* @method static GlowBerries GLOW_BERRIES()
|
||||
* @method static Item GLOW_INK_SAC()
|
||||
* @method static GoldenApple GOLDEN_APPLE()
|
||||
* @method static Axe GOLDEN_AXE()
|
||||
@ -438,6 +439,7 @@ final class VanillaItems{
|
||||
self::register("ghast_tear", new Item(new IID(Ids::GHAST_TEAR), "Ghast Tear"));
|
||||
self::register("glass_bottle", new GlassBottle(new IID(Ids::GLASS_BOTTLE), "Glass Bottle"));
|
||||
self::register("glistering_melon", new Item(new IID(Ids::GLISTERING_MELON), "Glistering Melon"));
|
||||
self::register("glow_berries", new GlowBerries(new IID(Ids::GLOW_BERRIES), "Glow Berries"));
|
||||
self::register("glow_ink_sac", new Item(new IID(Ids::GLOW_INK_SAC), "Glow Ink Sac"));
|
||||
self::register("glowstone_dust", new Item(new IID(Ids::GLOWSTONE_DUST), "Glowstone Dust"));
|
||||
self::register("gold_ingot", new Item(new IID(Ids::GOLD_INGOT), "Gold Ingot"));
|
||||
|
35
src/world/sound/GlowBerriesPickSound.php
Normal file
35
src/world/sound/GlowBerriesPickSound.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?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;
|
||||
|
||||
class GlowBerriesPickSound implements Sound{
|
||||
|
||||
public function encode(Vector3 $pos) : array{
|
||||
return [LevelSoundEventPacket::nonActorSound(LevelSoundEvent::CAVE_VINES_PICK_BERRIES, $pos, false)];
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
@ -27,6 +27,7 @@ use PHPUnit\Framework\TestCase;
|
||||
use pocketmine\block\BaseBanner;
|
||||
use pocketmine\block\Bed;
|
||||
use pocketmine\block\BlockTypeIds;
|
||||
use pocketmine\block\CaveVines;
|
||||
use pocketmine\block\RuntimeBlockStateRegistry;
|
||||
use pocketmine\block\Skull;
|
||||
use pocketmine\data\bedrock\block\BlockStateDeserializeException;
|
||||
@ -61,9 +62,11 @@ final class BlockSerializerDeserializerTest extends TestCase{
|
||||
}
|
||||
|
||||
//The following are workarounds for differences in blockstate representation in Bedrock vs PM
|
||||
//In these cases, some properties are not stored in the blockstate (but rather in the block entity NBT), but
|
||||
//they do form part of the internal blockstate hash in PM. This leads to inconsistencies when serializing
|
||||
//and deserializing blockstates.
|
||||
//In some cases, some properties are not stored in the blockstate (but rather in the block entity NBT), but
|
||||
//they do form part of the internal blockstate hash in PM. In other cases, PM allows representing states
|
||||
//that don't exist in Bedrock, such as the cave vines head without berries, which is a state that visually
|
||||
//exists in Bedrock, but doesn't have its own ID.
|
||||
//This leads to inconsistencies when serializing and deserializing blockstates which we need to correct for.
|
||||
if(
|
||||
($block instanceof BaseBanner && $newBlock instanceof BaseBanner) ||
|
||||
($block instanceof Bed && $newBlock instanceof Bed)
|
||||
@ -71,6 +74,8 @@ final class BlockSerializerDeserializerTest extends TestCase{
|
||||
$newBlock->setColor($block->getColor());
|
||||
}elseif($block instanceof Skull && $newBlock instanceof Skull){
|
||||
$newBlock->setSkullType($block->getSkullType());
|
||||
}elseif($block instanceof CaveVines && $newBlock instanceof CaveVines && !$block->hasBerries()){
|
||||
$newBlock->setHead($block->isHead());
|
||||
}
|
||||
|
||||
self::assertSame($block->getStateId(), $newBlock->getStateId(), "Mismatch of blockstate for " . $block->getName() . ", " . print_r($block, true) . " vs " . print_r($newBlock, true));
|
||||
|
Loading…
x
Reference in New Issue
Block a user