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 8 commits into
base: minor-next
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.

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;
}

$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