56 private const TAG_STUCK_ON_BLOCK_POS =
"StuckToBlockPos";
57 private const TAG_DAMAGE =
"damage";
58 private const TAG_TILE_X =
"tileX";
59 private const TAG_TILE_Y =
"tileY";
60 private const TAG_TILE_Z =
"tileZ";
62 protected float $damage = 0.0;
63 protected ?
Vector3 $blockHit =
null;
66 parent::__construct($location, $nbt);
67 if($shootingEntity !==
null){
73 if($source->getCause() === EntityDamageEvent::CAUSE_VOID){
74 parent::attack($source);
78 protected function initEntity(
CompoundTag $nbt) :
void{
79 parent::initEntity($nbt);
81 $this->setMaxHealth(1);
83 $this->damage = $nbt->getDouble(self::TAG_DAMAGE, $this->damage);
85 if(($stuckOnBlockPosTag = $nbt->
getListTag(self::TAG_STUCK_ON_BLOCK_POS)) !==
null){
86 if($stuckOnBlockPosTag->getTagType() !== NBT::TAG_Int || count($stuckOnBlockPosTag) !== 3){
91 $values = $stuckOnBlockPosTag->getValue();
93 $this->blockHit =
new Vector3($values[0]->getValue(), $values[1]->getValue(), $values[2]->getValue());
94 }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){
95 $this->blockHit =
new Vector3($tileXTag->getValue(), $tileYTag->getValue(), $tileZTag->getValue());
99 public function canCollideWith(
Entity $entity) :
bool{
100 return ($entity instanceof
Living || $entity instanceof
EndCrystal) && !$this->onGround;
103 public function canBeCollidedWith() :
bool{
112 return $this->damage;
119 $this->damage = $damage;
126 return (int) ceil($this->damage);
130 $nbt = parent::saveNBT();
132 $nbt->
setDouble(self::TAG_DAMAGE, $this->damage);
134 if($this->blockHit !==
null){
136 new IntTag($this->blockHit->getFloorX()),
137 new IntTag($this->blockHit->getFloorY()),
138 new IntTag($this->blockHit->getFloorZ())
145 protected function applyDragBeforeGravity() : bool{
149 public function onNearbyBlockChange() : void{
150 if($this->blockHit !== null && $this->getWorld()->isInLoadedTerrain($this->blockHit)){
151 $blockHit = $this->getWorld()->getBlock($this->blockHit);
152 if(!$blockHit->collidesWithBB($this->getBoundingBox()->expandedCopy(0.001, 0.001, 0.001))){
153 $this->blockHit =
null;
157 parent::onNearbyBlockChange();
161 return $this->blockHit === null && parent::hasMovementUpdate();
164 protected function move(
float $dx,
float $dy,
float $dz) : void{
165 $this->blocksAround = null;
167 Timings::$projectileMove->startTiming();
168 Timings::$projectileMoveRayTrace->startTiming();
170 $start = $this->location->asVector3();
171 $end = $start->add($dx, $dy, $dz);
177 $world = $this->getWorld();
178 foreach(VoxelRayTrace::betweenPoints($start, $end) as $vector3){
179 $block = $world->getBlockAt($vector3->x, $vector3->y, $vector3->z);
181 $blockHitResult = $this->calculateInterceptWithBlock($block, $start, $end);
182 if($blockHitResult !==
null){
183 $end = $blockHitResult->hitVector;
185 $hitResult = $blockHitResult;
190 $entityDistance = PHP_INT_MAX;
192 $newDiff = $end->subtractVector($start);
193 foreach($world->getCollidingEntities($this->boundingBox->addCoord($newDiff->x, $newDiff->y, $newDiff->z)->expand(1, 1, 1), $this) as $entity){
194 if($entity->getId() === $this->getOwningEntityId() && $this->ticksLived < 5){
198 $entityBB = $entity->boundingBox->expandedCopy(0.3, 0.3, 0.3);
199 $entityHitResult = $entityBB->calculateIntercept($start, $end);
201 if($entityHitResult ===
null){
205 $distance = $this->location->distanceSquared($entityHitResult->hitVector);
207 if($distance < $entityDistance){
208 $entityDistance = $distance;
209 $entityHit = $entity;
210 $hitResult = $entityHitResult;
211 $end = $entityHitResult->hitVector;
215 Timings::$projectileMoveRayTrace->stopTiming();
217 $this->location = Location::fromObject(
219 $this->location->world,
220 $this->location->yaw,
221 $this->location->pitch
223 $this->recalculateBoundingBox();
225 if($hitResult !==
null){
228 if($entityHit !==
null){
229 $ev =
new ProjectileHitEntityEvent($this, $hitResult, $entityHit);
230 }elseif($blockHit !==
null){
231 $ev =
new ProjectileHitBlockEvent($this, $hitResult, $blockHit);
233 assert(
false,
"unknown hit type");
240 if($ev instanceof ProjectileHitEntityEvent){
241 $this->onHitEntity($ev->getEntityHit(), $ev->getRayTraceResult());
242 }elseif($ev instanceof ProjectileHitBlockEvent){
243 $this->onHitBlock($ev->getBlockHit(), $ev->getRayTraceResult());
247 $this->isCollided = $this->onGround =
true;
248 $this->motion = Vector3::zero();
250 $this->isCollided = $this->onGround =
false;
251 $this->blockHit =
null;
254 $f = sqrt(($this->motion->x ** 2) + ($this->motion->z ** 2));
256 atan2($this->motion->x, $this->motion->z) * 180 / M_PI,
257 atan2($this->motion->y, $f) * 180 / M_PI
261 $world->onEntityMoved($this);
262 $this->checkBlockIntersections();
264 Timings::$projectileMove->stopTiming();
275 return $block->calculateIntercept($start, $end);
290 $damage = $this->getResultDamage();
293 if($this->getOwningEntity() ===
null){
299 $entityHit->attack($ev);
301 if($this->isOnFire()){
304 if(!$ev->isCancelled()){
305 $entityHit->setOnFire($ev->getDuration());
310 $this->flagForDespawn();
317 $this->blockHit = $blockHit->getPosition()->asVector3();