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("damage", $this->damage); (function() use ($nbt) : void{ if(($tileXTag = $nbt->getTag("tileX")) instanceof IntTag and ($tileYTag = $nbt->getTag("tileY")) instanceof IntTag and ($tileZTag = $nbt->getTag("tileZ")) instanceof IntTag){ $blockPos = new Vector3($tileXTag->getValue(), $tileYTag->getValue(), $tileZTag->getValue()); }else{ return; } if(($blockIdTag = $nbt->getTag("blockId")) instanceof IntTag){ $blockId = $blockIdTag->getValue(); }else{ return; } if(($blockDataTag = $nbt->getTag("blockData")) instanceof ByteTag){ $blockData = $blockDataTag->getValue(); }else{ return; } $this->blockHit = BlockFactory::getInstance()->get($blockId, $blockData); $this->blockHit->position($this->getWorld(), $blockPos->getFloorX(), $blockPos->getFloorY(), $blockPos->getFloorZ()); })(); } public function canCollideWith(Entity $entity) : bool{ return $entity instanceof Living and !$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->motion->length() * $this->damage); } public function saveNBT() : CompoundTag{ $nbt = parent::saveNBT(); $nbt->setDouble("damage", $this->damage); if($this->blockHit !== null){ $pos = $this->blockHit->getPosition(); $nbt->setInt("tileX", $pos->x); $nbt->setInt("tileY", $pos->y); $nbt->setInt("tileZ", $pos->z); //we intentionally use different ones to PC because we don't have stringy IDs $nbt->setInt("blockId", $this->blockHit->getId()); $nbt->setByte("blockData", $this->blockHit->getMeta()); } return $nbt; } protected function applyDragBeforeGravity() : bool{ return true; } public function onNearbyBlockChange() : void{ if($this->blockHit !== null and $this->getWorld()->isInLoadedTerrain($this->blockHit->getPosition()) and !$this->blockHit->isSameState($this->getWorld()->getBlock($this->blockHit->getPosition()))){ $this->blockHit = null; } parent::onNearbyBlockChange(); } public function hasMovementUpdate() : bool{ return $this->blockHit === null and parent::hasMovementUpdate(); } protected function move(float $dx, float $dy, float $dz) : void{ $this->blocksAround = null; Timings::$entityMove->startTiming(); $start = $this->location->asVector3(); $end = $start->add($dx, $dy, $dz); $blockHit = null; $entityHit = null; $hitResult = null; foreach(VoxelRayTrace::betweenPoints($start, $end) as $vector3){ $block = $this->getWorld()->getBlockAt($vector3->x, $vector3->y, $vector3->z); $blockHitResult = $this->calculateInterceptWithBlock($block, $start, $end); if($blockHitResult !== null){ $end = $blockHitResult->hitVector; $blockHit = $block; $hitResult = $blockHitResult; break; } } $entityDistance = PHP_INT_MAX; $newDiff = $end->subtractVector($start); foreach($this->getWorld()->getCollidingEntities($this->boundingBox->addCoord($newDiff->x, $newDiff->y, $newDiff->z)->expand(1, 1, 1), $this) as $entity){ if($entity->getId() === $this->getOwningEntityId() and $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; $entityHit = $entity; $hitResult = $entityHitResult; $end = $entityHitResult->hitVector; } } $this->location = Location::fromObject( $end, $this->location->world, $this->location->yaw, $this->location->pitch ); $this->recalculateBoundingBox(); if($hitResult !== null){ /** @var ProjectileHitEvent|null $ev */ $ev = null; if($entityHit !== null){ $ev = new ProjectileHitEntityEvent($this, $hitResult, $entityHit); }elseif($blockHit !== null){ $ev = new ProjectileHitBlockEvent($this, $hitResult, $blockHit); }else{ assert(false, "unknown hit type"); } if($ev !== null){ $ev->call(); $this->onHit($ev); if($ev instanceof ProjectileHitEntityEvent){ $this->onHitEntity($ev->getEntityHit(), $ev->getRayTraceResult()); }elseif($ev instanceof ProjectileHitBlockEvent){ $this->onHitBlock($ev->getBlockHit(), $ev->getRayTraceResult()); } } $this->isCollided = $this->onGround = true; $this->motion = new Vector3(0, 0, 0); }else{ $this->isCollided = $this->onGround = false; $this->blockHit = null; //recompute angles... $f = sqrt(($this->motion->x ** 2) + ($this->motion->z ** 2)); $this->location->yaw = (atan2($this->motion->x, $this->motion->z) * 180 / M_PI); $this->location->pitch = (atan2($this->motion->y, $f) * 180 / M_PI); } $this->getWorld()->onEntityMoved($this); $this->checkBlockIntersections(); Timings::$entityMove->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){ if($this->getOwningEntity() === null){ $ev = new EntityDamageByEntityEvent($this, $entityHit, EntityDamageEvent::CAUSE_PROJECTILE, $damage); }else{ $ev = new EntityDamageByChildEntityEvent($this->getOwningEntity(), $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 = clone $blockHit; } }