54 private const TAG_STUCK_ON_BLOCK_POS =
"StuckToBlockPos";
55 private const TAG_DAMAGE =
"damage";
56 private const TAG_TILE_X =
"tileX";
57 private const TAG_TILE_Y =
"tileY";
58 private const TAG_TILE_Z =
"tileZ";
60 protected float $damage = 0.0;
61 protected ?
Vector3 $blockHit =
null;
64 parent::__construct($location, $nbt);
65 if($shootingEntity !==
null){
71 if($source->getCause() === EntityDamageEvent::CAUSE_VOID){
72 parent::attack($source);
76 protected function initEntity(
CompoundTag $nbt) :
void{
77 parent::initEntity($nbt);
79 $this->setMaxHealth(1);
81 $this->damage = $nbt->getDouble(self::TAG_DAMAGE, $this->damage);
83 if(($stuckOnBlockPosTag = $nbt->
getListTag(self::TAG_STUCK_ON_BLOCK_POS, IntTag::class)) !==
null){
84 if(count($stuckOnBlockPosTag) !== 3){
88 $values = $stuckOnBlockPosTag->getValue();
90 $this->blockHit =
new Vector3($values[0]->getValue(), $values[1]->getValue(), $values[2]->getValue());
91 }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){
92 $this->blockHit =
new Vector3($tileXTag->getValue(), $tileYTag->getValue(), $tileZTag->getValue());
96 public function canCollideWith(
Entity $entity) :
bool{
97 return ($entity instanceof
Living || $entity instanceof
EndCrystal) && !$this->onGround;
100 public function canBeCollidedWith() :
bool{
109 return $this->damage;
116 $this->damage = $damage;
123 return (int) ceil($this->damage);
127 $nbt = parent::saveNBT();
129 $nbt->
setDouble(self::TAG_DAMAGE, $this->damage);
131 if($this->blockHit !==
null){
133 new IntTag($this->blockHit->getFloorX()),
134 new IntTag($this->blockHit->getFloorY()),
135 new IntTag($this->blockHit->getFloorZ())
142 protected function applyDragBeforeGravity() : bool{
146 public function onNearbyBlockChange() : void{
147 if($this->blockHit !== null && $this->getWorld()->isInLoadedTerrain($this->blockHit)){
148 $blockHit = $this->getWorld()->getBlock($this->blockHit);
149 if(!$blockHit->collidesWithBB($this->getBoundingBox()->expandedCopy(0.001, 0.001, 0.001))){
150 $this->blockHit =
null;
154 parent::onNearbyBlockChange();
158 return $this->blockHit === null && parent::hasMovementUpdate();
161 protected function move(
float $dx,
float $dy,
float $dz) : void{
162 $this->blocksAround = null;
164 Timings::$projectileMove->startTiming();
165 Timings::$projectileMoveRayTrace->startTiming();
167 $start = $this->location->asVector3();
168 $end = $start->add($dx, $dy, $dz);
172 $world = $this->getWorld();
173 foreach(VoxelRayTrace::betweenPoints($start, $end) as $vector3){
174 $block = $world->getBlockAt($vector3->x, $vector3->y, $vector3->z);
176 $blockHitResult = $this->calculateInterceptWithBlock($block, $start, $end);
177 if($blockHitResult !==
null){
178 $end = $blockHitResult->hitVector;
179 $hitResult = [$block, $blockHitResult];
184 $entityDistance = PHP_INT_MAX;
186 $newDiff = $end->subtractVector($start);
187 foreach($world->getCollidingEntities($this->boundingBox->addCoord($newDiff->x, $newDiff->y, $newDiff->z)->expandedCopy(1, 1, 1), $this) as $entity){
188 if($entity->getId() === $this->getOwningEntityId() && $this->ticksLived < 5){
192 $entityBB = $entity->boundingBox->expandedCopy(0.3, 0.3, 0.3);
193 $entityHitResult = $entityBB->calculateIntercept($start, $end);
195 if($entityHitResult ===
null){
199 $distance = $this->location->distanceSquared($entityHitResult->hitVector);
201 if($distance < $entityDistance){
202 $entityDistance = $distance;
203 $hitResult = [$entity, $entityHitResult];
204 $end = $entityHitResult->hitVector;
208 Timings::$projectileMoveRayTrace->stopTiming();
210 $this->location = Location::fromObject(
212 $this->location->world,
213 $this->location->yaw,
214 $this->location->pitch
216 $this->recalculateBoundingBox();
218 if($hitResult !==
null){
219 [$objectHit, $rayTraceResult] = $hitResult;
220 if($objectHit instanceof Entity){
221 $ev =
new ProjectileHitEntityEvent($this, $rayTraceResult, $objectHit);
222 $specificHitFunc = fn() => $this->onHitEntity($objectHit, $rayTraceResult);
224 $ev =
new ProjectileHitBlockEvent($this, $rayTraceResult, $objectHit);
225 $specificHitFunc = fn() => $this->onHitBlock($objectHit, $rayTraceResult);
228 $motionBeforeOnHit = clone $this->motion;
233 $this->isCollided = $this->onGround =
true;
234 if($motionBeforeOnHit->equals($this->motion)){
235 $this->motion = Vector3::zero();
238 $this->isCollided = $this->onGround =
false;
239 $this->blockHit =
null;
242 $f = sqrt(($this->motion->x ** 2) + ($this->motion->z ** 2));
244 atan2($this->motion->x, $this->motion->z) * 180 / M_PI,
245 atan2($this->motion->y, $f) * 180 / M_PI
249 $world->onEntityMoved($this);
250 $this->checkBlockIntersections();
252 Timings::$projectileMove->stopTiming();
263 return $block->calculateIntercept($start, $end);
278 $damage = $this->getResultDamage();
281 $owner = $this->getOwningEntity();
288 $entityHit->attack($ev);
290 if($this->isOnFire()){
293 if(!$ev->isCancelled()){
294 $entityHit->setOnFire($ev->getDuration());
299 if($this->despawnsOnEntityHit()){
300 $this->flagForDespawn();
308 $this->blockHit = $blockHit->getPosition()->asVector3();