Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Trident #4547

Open
wants to merge 10 commits into
base: minor-next
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,7 @@ private function register1to1ItemMappings() : void{
$this->map1to1Item(Ids::TORCHFLOWER_SEEDS, Items::TORCHFLOWER_SEEDS());
$this->map1to1Item(Ids::TIDE_ARMOR_TRIM_SMITHING_TEMPLATE, Items::TIDE_ARMOR_TRIM_SMITHING_TEMPLATE());
$this->map1to1Item(Ids::TOTEM_OF_UNDYING, Items::TOTEM());
$this->map1to1Item(Ids::TRIDENT, Items::TRIDENT());
$this->map1to1Item(Ids::TROPICAL_FISH, Items::CLOWNFISH());
$this->map1to1Item(Ids::TURTLE_HELMET, Items::TURTLE_HELMET());
$this->map1to1Item(Ids::VEX_ARMOR_TRIM_SMITHING_TEMPLATE, Items::VEX_ARMOR_TRIM_SMITHING_TEMPLATE());
Expand Down
15 changes: 15 additions & 0 deletions src/entity/EntityFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@
use pocketmine\entity\projectile\IceBomb;
use pocketmine\entity\projectile\Snowball;
use pocketmine\entity\projectile\SplashPotion;
use pocketmine\entity\projectile\Trident;
use pocketmine\item\Item;
use pocketmine\item\Trident as TridentItem;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\nbt\NbtException;
Expand Down Expand Up @@ -171,6 +173,19 @@ public function __construct(){
return new SplashPotion(Helper::parseLocation($nbt, $world), null, $potionType, $nbt);
}, ['ThrownPotion', 'minecraft:potion', 'thrownpotion']);

$this->register(Trident::class, function(World $world, CompoundTag $nbt) : Trident{
$itemTag = $nbt->getCompoundTag(Trident::TAG_ITEM);
if($itemTag === null){
throw new SavedDataLoadingException("Expected \"" . Trident::TAG_ITEM . "\" NBT tag not found");
}

$item = Item::nbtDeserialize($itemTag);
if($item->isNull() || !$item instanceof TridentItem){
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason to require this to be a TridentItem? It doesn't look like it needs any specific methods.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consistency, if I do TridentEntity->getItem() I would expect a TridentItem.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It just seems like an unnecessary constraint to me, considering there's no special methods on TridentItem.

throw new SavedDataLoadingException("Trident item is invalid");
}
return new Trident(Helper::parseLocation($nbt, $world), $item, null, $nbt);
}, ['Trident', 'ThrownTrident', 'minecraft:trident']);

$this->register(Squid::class, function(World $world, CompoundTag $nbt) : Squid{
return new Squid(Helper::parseLocation($nbt, $world), $nbt);
}, ['Squid', 'minecraft:squid']);
Expand Down
16 changes: 14 additions & 2 deletions src/entity/projectile/Projectile.php
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ protected function move(float $dx, float $dy, float $dz) : void{
assert(false, "unknown hit type");
}

$motionBeforeOnHit = clone $this->motion;
if($ev !== null){
$ev->call();
$this->onHit($ev);
Expand All @@ -245,7 +246,9 @@ protected function move(float $dx, float $dy, float $dz) : void{
}

$this->isCollided = $this->onGround = true;
$this->motion = Vector3::zero();
if($motionBeforeOnHit->equals($this->motion)){
$this->motion = Vector3::zero();
}
}else{
$this->isCollided = $this->onGround = false;
$this->blockHit = null;
Expand Down Expand Up @@ -307,7 +310,9 @@ protected function onHitEntity(Entity $entityHit, RayTraceResult $hitResult) : v
}
}

$this->flagForDespawn();
if($this->despawnsOnEntityHit()){
$this->flagForDespawn();
}
}

/**
Expand All @@ -317,4 +322,11 @@ protected function onHitBlock(Block $blockHit, RayTraceResult $hitResult) : void
$this->blockHit = $blockHit->getPosition()->asVector3();
$blockHit->onProjectileHit($this, $hitResult);
}

/**
* @deprecated This will be dropped in favor of deciding whether to despawn within `onHitEntity()` method.
*/
protected function despawnsOnEntityHit() : bool{
return true;
}
}
180 changes: 180 additions & 0 deletions src/entity/projectile/Trident.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
<?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\entity\Entity;
use pocketmine\entity\EntitySizeInfo;
use pocketmine\entity\Location;
use pocketmine\event\entity\EntityItemPickupEvent;
use pocketmine\item\Trident as TridentItem;
use pocketmine\math\RayTraceResult;
use pocketmine\math\Vector3;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\network\mcpe\EntityEventBroadcaster;
use pocketmine\network\mcpe\NetworkBroadcastUtils;
use pocketmine\network\mcpe\protocol\types\entity\EntityIds;
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataCollection;
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataFlags;
use pocketmine\player\Player;
use pocketmine\world\sound\TridentHitEntitySound;
use pocketmine\world\sound\TridentHitGroundSound;

class Trident extends Projectile{

public const TAG_ITEM = "Trident"; //TAG_Compound
protected const TAG_SPAWNED_IN_CREATIVE = "isCreative"; //TAG_Byte

public static function getNetworkTypeId() : string{ return EntityIds::THROWN_TRIDENT; }

protected float $damage = 8.0;

protected bool $canCollide = true;

protected bool $spawnedInCreative = false;

public function __construct(
Location $location,
protected TridentItem $item,
?Entity $shootingEntity,
?CompoundTag $nbt = null
){
if($item->isNull()){
throw new \InvalidArgumentException("Trident must have a count of at least 1");
}
parent::__construct($location, $shootingEntity, $nbt);
}

protected function getInitialSizeInfo() : EntitySizeInfo{ return new EntitySizeInfo(0.35, 0.25); }

protected function getInitialDragMultiplier() : float{ return 0.01; }

protected function getInitialGravity() : float{ return 0.1; }

protected function initEntity(CompoundTag $nbt) : void{
parent::initEntity($nbt);

$this->spawnedInCreative = $nbt->getByte(self::TAG_SPAWNED_IN_CREATIVE, 0) === 1;
}

public function saveNBT() : CompoundTag{
$nbt = parent::saveNBT();
$nbt->setTag(self::TAG_ITEM, $this->item->nbtSerialize());
$nbt->setByte(self::TAG_SPAWNED_IN_CREATIVE, $this->spawnedInCreative ? 1 : 0);
return $nbt;
}

protected function onFirstUpdate(int $currentTick) : void{
$owner = $this->getOwningEntity();
$this->spawnedInCreative = $owner instanceof Player && $owner->isCreative();

parent::onFirstUpdate($currentTick);
}

protected function entityBaseTick(int $tickDiff = 1) : bool{
if($this->closed){
return false;
}
//TODO: Loyalty enchantment.

return parent::entityBaseTick($tickDiff);
}

protected function despawnsOnEntityHit() : bool{
return false;
}

protected function onHitEntity(Entity $entityHit, RayTraceResult $hitResult) : void{
parent::onHitEntity($entityHit, $hitResult);

$this->canCollide = false;
$this->broadcastSound(new TridentHitEntitySound());
$this->setMotion(new Vector3($this->motion->x * -0.01, $this->motion->y * -0.1, $this->motion->z * -0.01));
}

protected function onHitBlock(Block $blockHit, RayTraceResult $hitResult) : void{
parent::onHitBlock($blockHit, $hitResult);
$this->canCollide = true;
$this->broadcastSound(new TridentHitGroundSound());
}

public function getItem() : TridentItem{
return clone $this->item;
}

public function setItem(TridentItem $item) : void{
if($item->isNull()){
throw new \InvalidArgumentException("Trident must have a count of at least 1");
}
if($this->item->hasEnchantments() !== $item->hasEnchantments()){
$this->networkPropertiesDirty = true;
}
$this->item = clone $item;
}

public function canCollideWith(Entity $entity) : bool{
return $this->canCollide && $entity->getId() !== $this->ownerId && parent::canCollideWith($entity);
}

public function onCollideWithPlayer(Player $player) : void{
if($this->blockHit !== null){
$this->pickup($player);
}
}

private function pickup(Player $player) : void{
$shouldDespawn = false;

$playerInventory = $player->getInventory();
$ev = new EntityItemPickupEvent($player, $this, $this->getItem(), $playerInventory);
if($player->hasFiniteResources() && !$playerInventory->canAddItem($ev->getItem())){
$ev->cancel();
}
if($this->spawnedInCreative){
$ev->cancel();
$shouldDespawn = true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is going to despawn tridents thrown by creative players if a survival player tries to pick them up, even though no item will be received. Doesn't seem right to me.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's the expected behavior

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expected by who?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure this is a bug in Bedrock. No other items behave like this.
Arrows fired in creative aren't expected to despawn when a survival player steps on them.

Also, from the wiki:

Only the owner of a trident thrown in Creative mode or enchanted with Loyalty can pick it up; other players in Creative and players in Survival mode including its own owner can't pick up the trident thrown in Creative.

I would expect this to work the same way arrows do, with modes NONE/ANY/CREATIVE pickup modes.

}

$ev->call();
if(!$ev->isCancelled()){
$ev->getInventory()?->addItem($ev->getItem());
$shouldDespawn = true;
}

if($shouldDespawn){
//even if the item was not actually picked up, the animation must be displayed.
NetworkBroadcastUtils::broadcastEntityEvent(
$this->getViewers(),
fn(EntityEventBroadcaster $broadcaster, array $recipients) => $broadcaster->onPickUpItem($recipients, $player, $this)
);
$this->flagForDespawn();
}
}

protected function syncNetworkData(EntityMetadataCollection $properties) : void{
parent::syncNetworkData($properties);

$properties->setGenericFlag(EntityMetadataFlags::ENCHANTED, $this->item->hasEnchantments());
}
}
11 changes: 9 additions & 2 deletions src/event/player/PlayerDeathEvent.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@
use pocketmine\block\BlockTypeIds;
use pocketmine\entity\Living;
use pocketmine\entity\object\FallingBlock;
use pocketmine\entity\projectile\Trident;
use pocketmine\event\entity\EntityDamageByBlockEvent;
use pocketmine\event\entity\EntityDamageByChildEntityEvent;
use pocketmine\event\entity\EntityDamageByEntityEvent;
use pocketmine\event\entity\EntityDamageEvent;
use pocketmine\event\entity\EntityDeathEvent;
Expand Down Expand Up @@ -113,10 +115,15 @@ public static function deriveMessage(string $name, ?EntityDamageEvent $deathCaus
}
break;
case EntityDamageEvent::CAUSE_PROJECTILE:
if($deathCause instanceof EntityDamageByEntityEvent){
if($deathCause instanceof EntityDamageByChildEntityEvent){
$e = $deathCause->getDamager();
if($e instanceof Living){
return KnownTranslationFactory::death_attack_arrow($name, $e->getDisplayName());
$child = $deathCause->getChild();
if($child instanceof Trident){
return KnownTranslationFactory::death_attack_trident($name, $e->getDisplayName());
}else{
return KnownTranslationFactory::death_attack_arrow($name, $e->getDisplayName());
}
}
}
break;
Expand Down
3 changes: 2 additions & 1 deletion src/item/ItemTypeIds.php
Original file line number Diff line number Diff line change
Expand Up @@ -328,8 +328,9 @@ private function __construct(){
public const END_CRYSTAL = 20289;
public const ICE_BOMB = 20290;
public const RECOVERY_COMPASS = 20291;
public const TRIDENT = 20292;

public const FIRST_UNUSED_ITEM_ID = 20292;
public const FIRST_UNUSED_ITEM_ID = 20293;

private static int $nextDynamicId = self::FIRST_UNUSED_ITEM_ID;

Expand Down
1 change: 1 addition & 0 deletions src/item/StringToItemParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -1521,6 +1521,7 @@ private static function registerItems(self $result) : void{
$result->register("torchflower_seeds", fn() => Items::TORCHFLOWER_SEEDS());
$result->register("tide_armor_trim_smithing_template", fn() => Items::TIDE_ARMOR_TRIM_SMITHING_TEMPLATE());
$result->register("totem", fn() => Items::TOTEM());
$result->register("trident", fn() => Items::TRIDENT());
$result->register("turtle_helmet", fn() => Items::TURTLE_HELMET());
$result->register("vex_armor_trim_smithing_template", fn() => Items::VEX_ARMOR_TRIM_SMITHING_TEMPLATE());
$result->register("turtle_shell_piece", fn() => Items::SCUTE());
Expand Down
Loading
Loading