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 || $entity instanceof
EndCrystal) && !$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);
174 $world = $this->getWorld();
175 foreach(VoxelRayTrace::betweenPoints($start, $end) as $vector3){
176 $block = $world->getBlockAt($vector3->x, $vector3->y, $vector3->z);
178 $blockHitResult = $this->calculateInterceptWithBlock($block, $start, $end);
179 if($blockHitResult !==
null){
180 $end = $blockHitResult->hitVector;
181 $hitResult = [$block, $blockHitResult];
186 $entityDistance = PHP_INT_MAX;
188 $newDiff = $end->subtractVector($start);
189 foreach($world->getCollidingEntities($this->boundingBox->addCoord($newDiff->x, $newDiff->y, $newDiff->z)->expand(1, 1, 1), $this) as $entity){
190 if($entity->getId() === $this->getOwningEntityId() && $this->ticksLived < 5){
194 $entityBB = $entity->boundingBox->expandedCopy(0.3, 0.3, 0.3);
195 $entityHitResult = $entityBB->calculateIntercept($start, $end);
197 if($entityHitResult ===
null){
201 $distance = $this->location->distanceSquared($entityHitResult->hitVector);
203 if($distance < $entityDistance){
204 $entityDistance = $distance;
205 $hitResult = [$entity, $entityHitResult];
206 $end = $entityHitResult->hitVector;
210 Timings::$projectileMoveRayTrace->stopTiming();
212 $this->location = Location::fromObject(
214 $this->location->world,
215 $this->location->yaw,
216 $this->location->pitch
218 $this->recalculateBoundingBox();
220 if($hitResult !==
null){
221 [$objectHit, $rayTraceResult] = $hitResult;
222 if($objectHit instanceof Entity){
223 $ev =
new ProjectileHitEntityEvent($this, $rayTraceResult, $objectHit);
224 $specificHitFunc = fn() => $this->onHitEntity($objectHit, $rayTraceResult);
226 $ev =
new ProjectileHitBlockEvent($this, $rayTraceResult, $objectHit);
227 $specificHitFunc = fn() => $this->onHitBlock($objectHit, $rayTraceResult);
234 $this->isCollided = $this->onGround =
true;
235 $this->motion = Vector3::zero();
237 $this->isCollided = $this->onGround =
false;
238 $this->blockHit =
null;
241 $f = sqrt(($this->motion->x ** 2) + ($this->motion->z ** 2));
243 atan2($this->motion->x, $this->motion->z) * 180 / M_PI,
244 atan2($this->motion->y, $f) * 180 / M_PI
248 $world->onEntityMoved($this);
249 $this->checkBlockIntersections();
251 Timings::$projectileMove->stopTiming();
262 return $block->calculateIntercept($start, $end);
277 $damage = $this->getResultDamage();
280 $owner = $this->getOwningEntity();
287 $entityHit->attack($ev);
289 if($this->isOnFire()){
292 if(!$ev->isCancelled()){
293 $entityHit->setOnFire($ev->getDuration());
298 $this->flagForDespawn();
305 $this->blockHit = $blockHit->getPosition()->asVector3();