22declare(strict_types=1);
24namespace pocketmine\entity\projectile;
55 private const TAG_STUCK_ON_BLOCK_POS =
"StuckToBlockPos";
56 private const TAG_DAMAGE =
"damage";
57 private const TAG_TILE_X =
"tileX";
58 private const TAG_TILE_Y =
"tileY";
59 private const TAG_TILE_Z =
"tileZ";
61 protected float $damage = 0.0;
62 protected ?
Vector3 $blockHit =
null;
65 parent::__construct($location, $nbt);
66 if($shootingEntity !==
null){
72 if($source->getCause() === EntityDamageEvent::CAUSE_VOID){
73 parent::attack($source);
77 protected function initEntity(
CompoundTag $nbt) :
void{
78 parent::initEntity($nbt);
80 $this->setMaxHealth(1);
82 $this->damage = $nbt->getDouble(self::TAG_DAMAGE, $this->damage);
84 if(($stuckOnBlockPosTag = $nbt->
getListTag(self::TAG_STUCK_ON_BLOCK_POS)) !==
null){
85 if($stuckOnBlockPosTag->getTagType() !== NBT::TAG_Int || count($stuckOnBlockPosTag) !== 3){
90 $values = $stuckOnBlockPosTag->getValue();
92 $this->blockHit =
new Vector3($values[0]->getValue(), $values[1]->getValue(), $values[2]->getValue());
93 }elseif(($tileXTag = $nbt->
getTag(self::TAG_TILE_X)) instanceof
IntTag && ($tileYTag = $nbt->
getTag(self::TAG_TILE_Y)) instanceof
IntTag && ($tileZTag = $nbt->
getTag(self::TAG_TILE_Z)) instanceof
IntTag){
94 $this->blockHit =
new Vector3($tileXTag->getValue(), $tileYTag->getValue(), $tileZTag->getValue());
98 public function canCollideWith(
Entity $entity) :
bool{
99 return $entity instanceof
Living && !$this->onGround;
102 public function canBeCollidedWith() :
bool{
111 return $this->damage;
118 $this->damage = $damage;
125 return (int) ceil($this->damage);
129 $nbt = parent::saveNBT();
131 $nbt->
setDouble(self::TAG_DAMAGE, $this->damage);
133 if($this->blockHit !==
null){
135 new IntTag($this->blockHit->getFloorX()),
136 new IntTag($this->blockHit->getFloorY()),
137 new IntTag($this->blockHit->getFloorZ())
144 protected function applyDragBeforeGravity() : bool{
148 public function onNearbyBlockChange() : void{
149 if($this->blockHit !== null && $this->getWorld()->isInLoadedTerrain($this->blockHit)){
150 $blockHit = $this->getWorld()->getBlock($this->blockHit);
151 if(!$blockHit->collidesWithBB($this->getBoundingBox()->expandedCopy(0.001, 0.001, 0.001))){
152 $this->blockHit =
null;
156 parent::onNearbyBlockChange();
160 return $this->blockHit === null && parent::hasMovementUpdate();
163 protected function move(
float $dx,
float $dy,
float $dz) : void{
164 $this->blocksAround = null;
166 Timings::$projectileMove->startTiming();
167 Timings::$projectileMoveRayTrace->startTiming();
169 $start = $this->location->asVector3();
170 $end = $start->add($dx, $dy, $dz);
176 $world = $this->getWorld();
177 foreach(VoxelRayTrace::betweenPoints($start, $end) as $vector3){
178 $block = $world->getBlockAt($vector3->x, $vector3->y, $vector3->z);
180 $blockHitResult = $this->calculateInterceptWithBlock($block, $start, $end);
181 if($blockHitResult !==
null){
182 $end = $blockHitResult->hitVector;
184 $hitResult = $blockHitResult;
189 $entityDistance = PHP_INT_MAX;
191 $newDiff = $end->subtractVector($start);
192 foreach($world->getCollidingEntities($this->boundingBox->addCoord($newDiff->x, $newDiff->y, $newDiff->z)->expand(1, 1, 1), $this) as $entity){
193 if($entity->getId() === $this->getOwningEntityId() && $this->ticksLived < 5){
197 $entityBB = $entity->boundingBox->expandedCopy(0.3, 0.3, 0.3);
198 $entityHitResult = $entityBB->calculateIntercept($start, $end);
200 if($entityHitResult ===
null){
204 $distance = $this->location->distanceSquared($entityHitResult->hitVector);
206 if($distance < $entityDistance){
207 $entityDistance = $distance;
208 $entityHit = $entity;
209 $hitResult = $entityHitResult;
210 $end = $entityHitResult->hitVector;
214 Timings::$projectileMoveRayTrace->stopTiming();
216 $this->location = Location::fromObject(
218 $this->location->world,
219 $this->location->yaw,
220 $this->location->pitch
222 $this->recalculateBoundingBox();
224 if($hitResult !==
null){
227 if($entityHit !==
null){
228 $ev =
new ProjectileHitEntityEvent($this, $hitResult, $entityHit);
229 }elseif($blockHit !==
null){
230 $ev =
new ProjectileHitBlockEvent($this, $hitResult, $blockHit);
232 assert(
false,
"unknown hit type");
239 if($ev instanceof ProjectileHitEntityEvent){
240 $this->onHitEntity($ev->getEntityHit(), $ev->getRayTraceResult());
241 }elseif($ev instanceof ProjectileHitBlockEvent){
242 $this->onHitBlock($ev->getBlockHit(), $ev->getRayTraceResult());
246 $this->isCollided = $this->onGround =
true;
247 $this->motion = Vector3::zero();
249 $this->isCollided = $this->onGround =
false;
250 $this->blockHit =
null;
253 $f = sqrt(($this->motion->x ** 2) + ($this->motion->z ** 2));
255 atan2($this->motion->x, $this->motion->z) * 180 / M_PI,
256 atan2($this->motion->y, $f) * 180 / M_PI
260 $world->onEntityMoved($this);
261 $this->checkBlockIntersections();
263 Timings::$projectileMove->stopTiming();
274 return $block->calculateIntercept($start, $end);
289 $damage = $this->getResultDamage();
292 if($this->getOwningEntity() ===
null){
298 $entityHit->attack($ev);
300 if($this->isOnFire()){
303 if(!$ev->isCancelled()){
304 $entityHit->setOnFire($ev->getDuration());
309 $this->flagForDespawn();
316 $this->blockHit = $blockHit->getPosition()->asVector3();
onProjectileHit(Projectile $projectile, RayTraceResult $hitResult)
setOwningEntity(?Entity $owner)
onHitBlock(Block $blockHit, RayTraceResult $hitResult)
calculateInterceptWithBlock(Block $block, Vector3 $start, Vector3 $end)
setBaseDamage(float $damage)
onHit(ProjectileHitEvent $event)
setTag(string $name, Tag $tag)
setDouble(string $name, float $value)