setOwningEntity($shootingEntity); } } public function attack(EntityDamageEvent $source) : void{ if($source->getCause() === EntityDamageEvent::CAUSE_VOID){ parent::attack($source); } } protected function initEntity(CompoundTag $nbt) : void{ parent::initEntity($nbt); $this->setMaxHealth(1); $this->setHealth(1); $this->damage = $nbt->getDouble(self::TAG_DAMAGE, $this->damage); if(($stuckOnBlockPosTag = $nbt->getListTag(self::TAG_STUCK_ON_BLOCK_POS)) !== null){ if($stuckOnBlockPosTag->getTagType() !== NBT::TAG_Int || count($stuckOnBlockPosTag) !== 3){ throw new SavedDataLoadingException(self::TAG_STUCK_ON_BLOCK_POS . " tag should be a list of 3 TAG_Int"); } /** @var IntTag[] $values */ $values = $stuckOnBlockPosTag->getValue(); $this->blockHit = new Vector3($values[0]->getValue(), $values[1]->getValue(), $values[2]->getValue()); }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){ $this->blockHit = new Vector3($tileXTag->getValue(), $tileYTag->getValue(), $tileZTag->getValue()); } } public function canCollideWith(Entity $entity) : bool{ return ($entity instanceof Living || $entity instanceof EndCrystal) && !$this->onGround; } public function canBeCollidedWith() : bool{ return false; } /** * Returns the base damage applied on collision. This is multiplied by the projectile's speed to give a result * damage. */ public function getBaseDamage() : float{ return $this->damage; } /** * Sets the base amount of damage applied by the projectile. */ public function setBaseDamage(float $damage) : void{ $this->damage = $damage; } /** * Returns the amount of damage this projectile will deal to the entity it hits. */ public function getResultDamage() : int{ return (int) ceil($this->damage); } public function saveNBT() : CompoundTag{ $nbt = parent::saveNBT(); $nbt->setDouble(self::TAG_DAMAGE, $this->damage); if($this->blockHit !== null){ $nbt->setTag(self::TAG_STUCK_ON_BLOCK_POS, new ListTag([ new IntTag($this->blockHit->getFloorX()), new IntTag($this->blockHit->getFloorY()), new IntTag($this->blockHit->getFloorZ()) ])); } return $nbt; } protected function applyDragBeforeGravity() : bool{ return true; } public function onNearbyBlockChange() : void{ if($this->blockHit !== null && $this->getWorld()->isInLoadedTerrain($this->blockHit)){ $blockHit = $this->getWorld()->getBlock($this->blockHit); if(!$blockHit->collidesWithBB($this->getBoundingBox()->expandedCopy(0.001, 0.001, 0.001))){ $this->blockHit = null; } } parent::onNearbyBlockChange(); } public function hasMovementUpdate() : bool{ return $this->blockHit === null && parent::hasMovementUpdate(); } protected function move(float $dx, float $dy, float $dz) : void{ $this->blocksAround = null; Timings::$projectileMove->startTiming(); Timings::$projectileMoveRayTrace->startTiming(); $start = $this->location->asVector3(); $end = $start->add($dx, $dy, $dz); $hitResult = null; $world = $this->getWorld(); foreach(VoxelRayTrace::betweenPoints($start, $end) as $vector3){ $block = $world->getBlockAt($vector3->x, $vector3->y, $vector3->z); $blockHitResult = $this->calculateInterceptWithBlock($block, $start, $end); if($blockHitResult !== null){ $end = $blockHitResult->hitVector; $hitResult = [$block, $blockHitResult]; break; } } $entityDistance = PHP_INT_MAX; $newDiff = $end->subtractVector($start); foreach($world->getCollidingEntities($this->boundingBox->addCoord($newDiff->x, $newDiff->y, $newDiff->z)->expand(1, 1, 1), $this) as $entity){ if($entity->getId() === $this->getOwningEntityId() && $this->ticksLived < 5){ continue; } $entityBB = $entity->boundingBox->expandedCopy(0.3, 0.3, 0.3); $entityHitResult = $entityBB->calculateIntercept($start, $end); if($entityHitResult === null){ continue; } $distance = $this->location->distanceSquared($entityHitResult->hitVector); if($distance < $entityDistance){ $entityDistance = $distance; $hitResult = [$entity, $entityHitResult]; $end = $entityHitResult->hitVector; } } Timings::$projectileMoveRayTrace->stopTiming(); $this->location = Location::fromObject( $end, $this->location->world, $this->location->yaw, $this->location->pitch ); $this->recalculateBoundingBox(); if($hitResult !== null){ [$objectHit, $rayTraceResult] = $hitResult; if($objectHit instanceof Entity){ $ev = new ProjectileHitEntityEvent($this, $rayTraceResult, $objectHit); $specificHitFunc = fn() => $this->onHitEntity($objectHit, $rayTraceResult); }else{ $ev = new ProjectileHitBlockEvent($this, $rayTraceResult, $objectHit); $specificHitFunc = fn() => $this->onHitBlock($objectHit, $rayTraceResult); } $ev->call(); $this->onHit($ev); $specificHitFunc(); $this->isCollided = $this->onGround = true; $this->motion = Vector3::zero(); }else{ $this->isCollided = $this->onGround = false; $this->blockHit = null; //recompute angles... $f = sqrt(($this->motion->x ** 2) + ($this->motion->z ** 2)); $this->setRotation( atan2($this->motion->x, $this->motion->z) * 180 / M_PI, atan2($this->motion->y, $f) * 180 / M_PI ); } $world->onEntityMoved($this); $this->checkBlockIntersections(); Timings::$projectileMove->stopTiming(); } /** * Called by move() when raytracing blocks to discover whether the block should be considered as a point of impact. * This can be overridden by other projectiles to allow altering the blocks which are collided with (for example * some projectiles collide with any non-air block). * * @return RayTraceResult|null the result of the ray trace if successful, or null if no interception is found. */ protected function calculateInterceptWithBlock(Block $block, Vector3 $start, Vector3 $end) : ?RayTraceResult{ return $block->calculateIntercept($start, $end); } /** * Called when the projectile hits something. Override this to perform non-target-specific effects when the * projectile hits something. */ protected function onHit(ProjectileHitEvent $event) : void{ } /** * Called when the projectile collides with an Entity. */ protected function onHitEntity(Entity $entityHit, RayTraceResult $hitResult) : void{ $damage = $this->getResultDamage(); if($damage >= 0){ $owner = $this->getOwningEntity(); if($owner === null){ $ev = new EntityDamageByEntityEvent($this, $entityHit, EntityDamageEvent::CAUSE_PROJECTILE, $damage); }else{ $ev = new EntityDamageByChildEntityEvent($owner, $this, $entityHit, EntityDamageEvent::CAUSE_PROJECTILE, $damage); } $entityHit->attack($ev); if($this->isOnFire()){ $ev = new EntityCombustByEntityEvent($this, $entityHit, 5); $ev->call(); if(!$ev->isCancelled()){ $entityHit->setOnFire($ev->getDuration()); } } } $this->flagForDespawn(); } /** * Called when the projectile collides with a Block. */ protected function onHitBlock(Block $blockHit, RayTraceResult $hitResult) : void{ $this->blockHit = $blockHit->getPosition()->asVector3(); $blockHit->onProjectileHit($this, $hitResult); } }