diff --git a/src/block/Bell.php b/src/block/Bell.php index 53a6fc7fbb4..55bda65533e 100644 --- a/src/block/Bell.php +++ b/src/block/Bell.php @@ -29,6 +29,7 @@ use pocketmine\block\utils\SupportType; use pocketmine\data\runtime\RuntimeDataDescriber; use pocketmine\entity\projectile\Projectile; +use pocketmine\entity\projectile\WindCharge; use pocketmine\item\Item; use pocketmine\math\AxisAlignedBB; use pocketmine\math\Facing; @@ -134,6 +135,12 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player return false; } + public function onProjectileInteraction(Projectile $projectile) : void{ + if($projectile instanceof WindCharge) { + $this->ring($this->facing); + } + } + public function onProjectileHit(Projectile $projectile, RayTraceResult $hitResult) : void{ $faceHit = Facing::opposite($projectile->getHorizontalFacing()); if($this->isValidFaceToRing($faceHit)){ diff --git a/src/block/Block.php b/src/block/Block.php index dbc269c6301..8a2d9244f2d 100644 --- a/src/block/Block.php +++ b/src/block/Block.php @@ -513,6 +513,15 @@ public function onScheduledUpdate() : void{ } + /** + * Called when this block is updated by a state altering projectile in the world. + * + * @param Projectile $projectile The projectile affecting the block + */ + public function onProjectileInteraction(Projectile $projectile) : void { + + } + /** * Do actions when interacted by Item. Returns if it has done anything * diff --git a/src/block/Button.php b/src/block/Button.php index 73bd1d556bd..1f975830d26 100644 --- a/src/block/Button.php +++ b/src/block/Button.php @@ -25,6 +25,8 @@ use pocketmine\block\utils\AnyFacingTrait; use pocketmine\data\runtime\RuntimeDataDescriber; +use pocketmine\entity\projectile\Projectile; +use pocketmine\entity\projectile\WindCharge; use pocketmine\item\Item; use pocketmine\math\Facing; use pocketmine\math\Vector3; @@ -63,11 +65,7 @@ abstract protected function getActivationTime() : int; public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{ if(!$this->pressed){ - $this->pressed = true; - $world = $this->position->getWorld(); - $world->setBlock($this->position, $this); - $world->scheduleDelayedBlockUpdate($this->position, $this->getActivationTime()); - $world->addSound($this->position->add(0.5, 0.5, 0.5), new RedstonePowerOnSound()); + $this->press(); } return true; @@ -91,4 +89,19 @@ public function onNearbyBlockChange() : void{ private function canBeSupportedAt(Block $block, int $face) : bool{ return $block->getAdjacentSupportType(Facing::opposite($face))->hasCenterSupport(); } + + public function onProjectileInteraction(Projectile $projectile) : void{ + if($projectile instanceof WindCharge) { + $this->press(); + } + } + + public function press() : void { + $this->pressed = true; + + $world = $this->position->getWorld(); + $world->setBlock($this->position, $this); + $world->scheduleDelayedBlockUpdate($this->position, $this->getActivationTime()); + $world->addSound($this->position->add(0.5, 0.5, 0.5), new RedstonePowerOnSound()); + } } diff --git a/src/block/CakeWithCandle.php b/src/block/CakeWithCandle.php index 4479faee103..d08ee0335b9 100644 --- a/src/block/CakeWithCandle.php +++ b/src/block/CakeWithCandle.php @@ -25,11 +25,14 @@ use pocketmine\block\utils\CandleTrait; use pocketmine\entity\Living; +use pocketmine\entity\projectile\Projectile; +use pocketmine\entity\projectile\WindCharge; use pocketmine\item\Item; use pocketmine\math\AxisAlignedBB; use pocketmine\math\Facing; use pocketmine\math\Vector3; use pocketmine\player\Player; +use pocketmine\world\sound\FlintSteelSound; class CakeWithCandle extends BaseCake{ use CandleTrait { @@ -59,6 +62,15 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player return parent::onInteract($item, $face, $clickVector, $player, $returnedItems); } + public function onProjectileInteraction(Projectile $projectile) : void{ + if($projectile instanceof WindCharge && $this->lit) { + + $world = $this->position->getWorld(); + $world->setBlock($this->position, $this->setLit(false)); + $world->addSound($this->position, new FlintSteelSound()); + } + } + public function getDropsForCompatibleTool(Item $item) : array{ return [$this->getCandle()->asItem()]; } diff --git a/src/block/Candle.php b/src/block/Candle.php index 7f22641e118..a77b7c63a15 100644 --- a/src/block/Candle.php +++ b/src/block/Candle.php @@ -26,6 +26,8 @@ use pocketmine\block\utils\CandleTrait; use pocketmine\block\utils\SupportType; use pocketmine\data\runtime\RuntimeDataDescriber; +use pocketmine\entity\projectile\Projectile; +use pocketmine\entity\projectile\WindCharge; use pocketmine\item\Item; use pocketmine\math\Axis; use pocketmine\math\AxisAlignedBB; @@ -34,6 +36,7 @@ use pocketmine\player\Player; use pocketmine\utils\AssumptionFailedError; use pocketmine\world\BlockTransaction; +use pocketmine\world\sound\FlintSteelSound; class Candle extends Transparent{ use CandleTrait { @@ -98,6 +101,16 @@ protected function getCandleIfCompatibleType(Block $block) : ?Candle{ return $block instanceof Candle && $block->hasSameTypeId($this) ? $block : null; } + public function onProjectileInteraction(Projectile $projectile) : void{ + if($projectile instanceof WindCharge && $this->lit) { + + $newCandle = $this->setLit(false); + $world = $this->position->getWorld(); + $world->setBlock($this->position, $newCandle); + $world->addSound($this->position, new FlintSteelSound()); + } + } + public function canBePlacedAt(Block $blockReplace, Vector3 $clickVector, int $face, bool $isClickedBlock) : bool{ $candle = $this->getCandleIfCompatibleType($blockReplace); return $candle !== null ? $candle->count < self::MAX_COUNT : parent::canBePlacedAt($blockReplace, $clickVector, $face, $isClickedBlock); diff --git a/src/block/Door.php b/src/block/Door.php index 82ddaab518b..3c88502203f 100644 --- a/src/block/Door.php +++ b/src/block/Door.php @@ -26,6 +26,8 @@ use pocketmine\block\utils\HorizontalFacingTrait; use pocketmine\block\utils\SupportType; use pocketmine\data\runtime\RuntimeDataDescriber; +use pocketmine\entity\projectile\Projectile; +use pocketmine\entity\projectile\WindCharge; use pocketmine\item\Item; use pocketmine\math\AxisAlignedBB; use pocketmine\math\Facing; @@ -142,17 +144,7 @@ public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Blo } public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{ - $this->open = !$this->open; - - $other = $this->getSide($this->top ? Facing::DOWN : Facing::UP); - $world = $this->position->getWorld(); - if($other instanceof Door && $other->hasSameTypeId($this)){ - $other->open = $this->open; - $world->setBlock($other->position, $other); - } - - $world->setBlock($this->position, $this); - $world->addSound($this->position, new DoorSound()); + $this->toggle(); return true; } @@ -176,4 +168,34 @@ public function getAffectedBlocks() : array{ private function canBeSupportedAt(Block $block) : bool{ return $block->getAdjacentSupportType(Facing::DOWN)->hasEdgeSupport(); } + + public function onProjectileInteraction(Projectile $projectile) : void{ + if($projectile instanceof WindCharge) { + if($this->getTypeId() === BlockTypeIds::IRON_DOOR) { + return; + } + + // This is to avoid calling Door::onProjectileInteraction() twice due to two tiles. + if($this->top) { + return; + } + + $this->toggle(); + } + } + + public function toggle() : void { + $this->open = !$this->open; + + $other = $this->getSide($this->top ? Facing::DOWN : Facing::UP); + $world = $this->position->getWorld(); + + if($other instanceof Door && $other->hasSameTypeId($this)){ + $other->open = $this->open; + $world->setBlock($other->position, $other); + } + + $world->setBlock($this->position, $this); + $world->addSound($this->position, new DoorSound()); + } } diff --git a/src/block/Lever.php b/src/block/Lever.php index d2b98efc34f..f4f0f959a2c 100644 --- a/src/block/Lever.php +++ b/src/block/Lever.php @@ -25,6 +25,8 @@ use pocketmine\block\utils\LeverFacing; use pocketmine\data\runtime\RuntimeDataDescriber; +use pocketmine\entity\projectile\Projectile; +use pocketmine\entity\projectile\WindCharge; use pocketmine\item\Item; use pocketmine\math\Axis; use pocketmine\math\Facing; @@ -91,6 +93,18 @@ public function onNearbyBlockChange() : void{ } public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{ + $this->toggle(); + + return true; + } + + public function onProjectileInteraction(Projectile $projectile) : void{ + if($projectile instanceof WindCharge) { + $this->toggle(); + } + } + + public function toggle() : void { $this->activated = !$this->activated; $world = $this->position->getWorld(); $world->setBlock($this->position, $this); @@ -98,7 +112,6 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $this->position->add(0.5, 0.5, 0.5), $this->activated ? new RedstonePowerOnSound() : new RedstonePowerOffSound() ); - return true; } private function canBeSupportedAt(Block $block, int $face) : bool{ diff --git a/src/block/Trapdoor.php b/src/block/Trapdoor.php index 20b6af2abdc..23c6e078112 100644 --- a/src/block/Trapdoor.php +++ b/src/block/Trapdoor.php @@ -26,6 +26,8 @@ use pocketmine\block\utils\HorizontalFacingTrait; use pocketmine\block\utils\SupportType; use pocketmine\data\runtime\RuntimeDataDescriber; +use pocketmine\entity\projectile\Projectile; +use pocketmine\entity\projectile\WindCharge; use pocketmine\item\Item; use pocketmine\math\AxisAlignedBB; use pocketmine\math\Facing; @@ -85,10 +87,22 @@ public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Blo } public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{ + $this->toggle(); + + return true; + } + + public function onProjectileInteraction(Projectile $projectile) : void{ + if($projectile instanceof WindCharge && $this->getTypeId() !== BlockTypeIds::IRON_TRAPDOOR){ + $this->toggle(); + } + } + + public function toggle() : void { $this->open = !$this->open; + $world = $this->position->getWorld(); $world->setBlock($this->position, $this); $world->addSound($this->position, new DoorSound()); - return true; } } diff --git a/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php b/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php index de9b5ae5e60..f52ef0723c3 100644 --- a/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php +++ b/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php @@ -389,6 +389,7 @@ private function register1to1ItemMappings() : void{ $this->map1to1Item(Ids::WHEAT, Items::WHEAT()); $this->map1to1Item(Ids::WHEAT_SEEDS, Items::WHEAT_SEEDS()); $this->map1to1Item(Ids::WILD_ARMOR_TRIM_SMITHING_TEMPLATE, Items::WILD_ARMOR_TRIM_SMITHING_TEMPLATE()); + $this->map1to1Item(Ids::WIND_CHARGE, Items::WIND_CHARGE()); $this->map1to1Item(Ids::WOODEN_AXE, Items::WOODEN_AXE()); $this->map1to1Item(Ids::WOODEN_HOE, Items::WOODEN_HOE()); $this->map1to1Item(Ids::WOODEN_PICKAXE, Items::WOODEN_PICKAXE()); diff --git a/src/entity/EntityFactory.php b/src/entity/EntityFactory.php index d8d189cffc1..d26306b12e2 100644 --- a/src/entity/EntityFactory.php +++ b/src/entity/EntityFactory.php @@ -44,6 +44,7 @@ use pocketmine\entity\projectile\ExperienceBottle; use pocketmine\entity\projectile\Snowball; use pocketmine\entity\projectile\SplashPotion; +use pocketmine\entity\projectile\WindCharge; use pocketmine\item\Item; use pocketmine\math\Facing; use pocketmine\math\Vector3; @@ -169,6 +170,10 @@ public function __construct(){ return new Villager(Helper::parseLocation($nbt, $world), $nbt); }, ['Villager', 'minecraft:villager']); + $this->register(WindCharge::class, function(World $world, CompoundTag $nbt) : WindCharge { + return new WindCharge(Helper::parseLocation($nbt, $world), null, $nbt); + }, ['Wind Charge', 'minecraft:wind_charge']); + $this->register(Zombie::class, function(World $world, CompoundTag $nbt) : Zombie{ return new Zombie(Helper::parseLocation($nbt, $world), $nbt); }, ['Zombie', 'minecraft:zombie']); diff --git a/src/entity/projectile/WindCharge.php b/src/entity/projectile/WindCharge.php new file mode 100644 index 00000000000..20a2a64656c --- /dev/null +++ b/src/entity/projectile/WindCharge.php @@ -0,0 +1,136 @@ +getDamager()) === null){ + return; + } + + $this->setOwningEntity($entity); + + $this->setMotion($entity->getDirectionVector()->multiply(1.5)); + } + + protected function onHitEntity(Entity $entityHit, RayTraceResult $hitResult) : void{ + if($this->getOwningEntity() === null) { + $ev = new EntityDamageByEntityEvent($this, $entityHit, EntityDamageEvent::CAUSE_PROJECTILE, self::DAMAGE); + } else { + $ev = new EntityDamageByChildEntityEvent($this->getOwningEntity(), $this, $entityHit, EntityDamageEvent::CAUSE_PROJECTILE, self::DAMAGE); + } + + $entityHit->attack($ev); + + $this->flagForDespawn(); + } + + protected function entityBaseTick(int $tickDiff = 1) : bool{ + parent::entityBaseTick($tickDiff); + + if($this->ticksLived >= 6000) { + $this->flagForDespawn(); + } + + return true; + } + + protected function onHit(ProjectileHitEvent $event) : void{ + $source = $this->getLocation(); + + $this->getWorld()->addSound($event->getRayTraceResult()->getHitVector(), new WindChargeBurstSound()); + $this->getWorld()->addParticle($source, new WindExplosionParticle()); + + $bound = $this->getBound($this->getPosition(), self::RADIUS - 1); + for($x = $bound->minX; $x <= $bound->maxX; $x++) { + for($y = $bound->minY; $y <= $bound->maxY; $y++) { + for($z = $bound->minZ; $z <= $bound->maxZ; $z++) { + $block = $this->getWorld()->getBlockAt((int) floor($x), (int) floor($y), (int) floor($z)); + + $block->onProjectileInteraction($this); + } + } + } + + foreach($source->getWorld()->getCollidingEntities($this->getBound($source, self::RADIUS), $this) as $entity){ + + $entityPos = $entity->getPosition(); + $distance = $entityPos->distance($source) / 2.5; + $motion = $entityPos->subtractVector($source)->normalize(); + + $exposure = 1; + if ($entity->isUnderwater()){ + $exposure = 0.5; + } + + $impact = (1.3 - $distance) * $exposure; + if ($impact <= 0) { + continue; + } + + if (round($entityPos->getX(), 1) == round($source->getX(), 1) && round($entityPos->getZ(), 1) == round($source->getZ(), 1)) { + $entity->setMotion($entity->getMotion()->add(0, 0.75 * $exposure, 0)); + + return; + } + + $entity->setMotion($entity->getMotion()->add(0, $impact * 0.4, 0)->addVector($motion->multiply($impact * $exposure))); + } + } + + private function getBound(Vector3 $source, float $radius) : AxisAlignedBB { + return new AxisAlignedBB( + (int) floor($source->x - $radius - 1), + (int) floor($source->y - $radius - 1), + (int) floor($source->z - $radius - 1), + (int) ceil($source->x + $radius + 1), + (int) ceil($source->y + $radius + 1), + (int) ceil($source->z + $radius + 1) + ); + } +} diff --git a/src/item/ItemTypeIds.php b/src/item/ItemTypeIds.php index 66eebed744c..3688663922e 100644 --- a/src/item/ItemTypeIds.php +++ b/src/item/ItemTypeIds.php @@ -324,8 +324,9 @@ private function __construct(){ public const SPIRE_ARMOR_TRIM_SMITHING_TEMPLATE = 20285; public const PITCHER_POD = 20286; public const NAME_TAG = 20287; + public const WIND_CHARGE = 20288; - public const FIRST_UNUSED_ITEM_ID = 20288; + public const FIRST_UNUSED_ITEM_ID = 20289; private static int $nextDynamicId = self::FIRST_UNUSED_ITEM_ID; diff --git a/src/item/StringToItemParser.php b/src/item/StringToItemParser.php index 9f5db6950c6..0843d0d564f 100644 --- a/src/item/StringToItemParser.php +++ b/src/item/StringToItemParser.php @@ -1515,6 +1515,7 @@ private static function registerItems(self $result) : void{ $result->register("wheat", fn() => Items::WHEAT()); $result->register("wheat_seeds", fn() => Items::WHEAT_SEEDS()); $result->register("wild_armor_trim_smithing_template", fn() => Items::WILD_ARMOR_TRIM_SMITHING_TEMPLATE()); + $result->register("wind_charge", fn() => Items::WIND_CHARGE()); $result->register("wooden_axe", fn() => Items::WOODEN_AXE()); $result->register("wooden_hoe", fn() => Items::WOODEN_HOE()); $result->register("wooden_pickaxe", fn() => Items::WOODEN_PICKAXE()); diff --git a/src/item/VanillaItems.php b/src/item/VanillaItems.php index 5115ee48a8b..c1b0b5c20d5 100644 --- a/src/item/VanillaItems.php +++ b/src/item/VanillaItems.php @@ -323,6 +323,7 @@ * @method static Item WHEAT() * @method static WheatSeeds WHEAT_SEEDS() * @method static Item WILD_ARMOR_TRIM_SMITHING_TEMPLATE() + * @method static WindCharge WIND_CHARGE() * @method static Axe WOODEN_AXE() * @method static Hoe WOODEN_HOE() * @method static Pickaxe WOODEN_PICKAXE() @@ -567,6 +568,7 @@ public function isFireProof() : bool{ return true; } self::register("water_bucket", new LiquidBucket(new IID(Ids::WATER_BUCKET), "Water Bucket", Blocks::WATER())); self::register("wheat", new Item(new IID(Ids::WHEAT), "Wheat")); self::register("wheat_seeds", new WheatSeeds(new IID(Ids::WHEAT_SEEDS), "Wheat Seeds")); + self::register("wind_charge", new WindCharge(new IID(Ids::WIND_CHARGE), "Wind Charge")); self::register("writable_book", new WritableBook(new IID(Ids::WRITABLE_BOOK), "Book & Quill")); self::register("written_book", new WrittenBook(new IID(Ids::WRITTEN_BOOK), "Written Book")); diff --git a/src/item/WindCharge.php b/src/item/WindCharge.php new file mode 100644 index 00000000000..784abd53a28 --- /dev/null +++ b/src/item/WindCharge.php @@ -0,0 +1,44 @@ +