Files
PocketMine-MP/src/entity/projectile/Arrow.php
Dylan K. Taylor 657c07b1a3 Remove dependencies on hotbar and offhand inventory to get equipment
It's always grated at me that the call chain to get the held item is so long
2025-08-31 00:06:56 +01:00

208 lines
6.3 KiB
PHP

<?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\animation\ArrowShakeAnimation;
use pocketmine\entity\Entity;
use pocketmine\entity\EntitySizeInfo;
use pocketmine\entity\Location;
use pocketmine\event\entity\EntityItemPickupEvent;
use pocketmine\event\entity\ProjectileHitEvent;
use pocketmine\item\VanillaItems;
use pocketmine\math\RayTraceResult;
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\ArrowHitSound;
use function ceil;
use function mt_rand;
use function sqrt;
class Arrow extends Projectile{
public function getNetworkTypeId() : string{ return EntityIds::ARROW; }
public const PICKUP_NONE = 0;
public const PICKUP_ANY = 1;
public const PICKUP_CREATIVE = 2;
private const TAG_PICKUP = "pickup"; //TAG_Byte
public const TAG_CRIT = "crit"; //TAG_Byte
private const TAG_LIFE = "life"; //TAG_Short
protected float $damage = 2.0;
protected int $pickupMode = self::PICKUP_ANY;
protected float $punchKnockback = 0.0;
protected int $collideTicks = 0;
protected bool $critical = false;
public function __construct(Location $location, ?Entity $shootingEntity, bool $critical, ?CompoundTag $nbt = null){
parent::__construct($location, $shootingEntity, $nbt);
$this->setCritical($critical);
}
protected function getInitialSizeInfo() : EntitySizeInfo{ return new EntitySizeInfo(0.25, 0.25); }
protected function getInitialDragMultiplier() : float{ return 0.01; }
protected function getInitialGravity() : float{ return 0.05; }
protected function initEntity(CompoundTag $nbt) : void{
parent::initEntity($nbt);
$this->pickupMode = $nbt->getByte(self::TAG_PICKUP, self::PICKUP_ANY);
$this->critical = $nbt->getByte(self::TAG_CRIT, 0) === 1;
$this->collideTicks = $nbt->getShort(self::TAG_LIFE, $this->collideTicks);
}
public function saveNBT() : CompoundTag{
$nbt = parent::saveNBT();
$nbt->setByte(self::TAG_PICKUP, $this->pickupMode);
$nbt->setByte(self::TAG_CRIT, $this->critical ? 1 : 0);
$nbt->setShort(self::TAG_LIFE, $this->collideTicks);
return $nbt;
}
public function isCritical() : bool{
return $this->critical;
}
public function setCritical(bool $value = true) : void{
$this->critical = $value;
$this->networkPropertiesDirty = true;
}
public function getResultDamage() : int{
$base = (int) ceil($this->motion->length() * parent::getResultDamage());
if($this->isCritical()){
return ($base + mt_rand(0, (int) ($base / 2) + 1));
}else{
return $base;
}
}
public function getPunchKnockback() : float{
return $this->punchKnockback;
}
public function setPunchKnockback(float $punchKnockback) : void{
$this->punchKnockback = $punchKnockback;
}
protected function entityBaseTick(int $tickDiff = 1) : bool{
if($this->closed){
return false;
}
$hasUpdate = parent::entityBaseTick($tickDiff);
if($this->blockHit !== null){
$this->collideTicks += $tickDiff;
if($this->collideTicks > 1200){
$this->flagForDespawn();
$hasUpdate = true;
}
}else{
$this->collideTicks = 0;
}
return $hasUpdate;
}
protected function onHit(ProjectileHitEvent $event) : void{
$this->setCritical(false);
$this->broadcastSound(new ArrowHitSound());
}
protected function onHitBlock(Block $blockHit, RayTraceResult $hitResult) : void{
parent::onHitBlock($blockHit, $hitResult);
$this->broadcastAnimation(new ArrowShakeAnimation($this, 7));
}
protected function onHitEntity(Entity $entityHit, RayTraceResult $hitResult) : void{
parent::onHitEntity($entityHit, $hitResult);
if($this->punchKnockback > 0){
$horizontalSpeed = sqrt($this->motion->x ** 2 + $this->motion->z ** 2);
if($horizontalSpeed > 0){
$multiplier = $this->punchKnockback * 0.6 / $horizontalSpeed;
$entityHit->setMotion($entityHit->getMotion()->add($this->motion->x * $multiplier, 0.1, $this->motion->z * $multiplier));
}
}
}
public function getPickupMode() : int{
return $this->pickupMode;
}
public function setPickupMode(int $pickupMode) : void{
$this->pickupMode = $pickupMode;
}
public function onCollideWithPlayer(Player $player) : void{
if($this->blockHit === null){
return;
}
$item = VanillaItems::ARROW();
$playerInventory = match(true){
!$player->hasFiniteResources() => null, //arrows are not picked up in creative
$player->getOffHandItem()->canStackWith($item) && $player->getOffHandInventory()->canAddItem($item) => $player->getOffHandInventory(),
$player->getInventory()->canAddItem($item) => $player->getInventory(),
default => null
};
$ev = new EntityItemPickupEvent($player, $this, $item, $playerInventory);
if($player->hasFiniteResources() && $playerInventory === null){
$ev->cancel();
}
if($this->pickupMode === self::PICKUP_NONE || ($this->pickupMode === self::PICKUP_CREATIVE && !$player->isCreative())){
$ev->cancel();
}
$ev->call();
if($ev->isCancelled()){
return;
}
NetworkBroadcastUtils::broadcastEntityEvent(
$this->getViewers(),
fn(EntityEventBroadcaster $broadcaster, array $recipients) => $broadcaster->onPickUpItem($recipients, $player, $this)
);
$ev->getInventory()?->addItem($ev->getItem());
$this->flagForDespawn();
}
protected function syncNetworkData(EntityMetadataCollection $properties) : void{
parent::syncNetworkData($properties);
$properties->setGenericFlag(EntityMetadataFlags::CRITICAL, $this->critical);
}
}