PocketMine-MP 5.35.1 git-e32e836dad793a3a3c8ddd8927c00e112b1e576a
Loading...
Searching...
No Matches
Projectile.php
1<?php
2
3/*
4 *
5 * ____ _ _ __ __ _ __ __ ____
6 * | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
7 * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
8 * | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
9 * |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
10 *
11 * This program is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU Lesser General Public License as published by
13 * the Free Software Foundation, either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * @author PocketMine Team
17 * @link http://www.pocketmine.net/
18 *
19 *
20 */
21
22declare(strict_types=1);
23
24namespace pocketmine\entity\projectile;
25
46use function atan2;
47use function ceil;
48use function count;
49use function sqrt;
50use const M_PI;
51use const PHP_INT_MAX;
52
53abstract class Projectile extends Entity{
54 private const TAG_STUCK_ON_BLOCK_POS = "StuckToBlockPos";
55 private const TAG_DAMAGE = "damage"; //TAG_Double
56 private const TAG_TILE_X = "tileX"; //TAG_Int
57 private const TAG_TILE_Y = "tileY"; //TAG_Int
58 private const TAG_TILE_Z = "tileZ"; //TAG_Int
59
60 protected float $damage = 0.0;
61 protected ?Vector3 $blockHit = null;
62
63 public function __construct(Location $location, ?Entity $shootingEntity, ?CompoundTag $nbt = null){
64 parent::__construct($location, $nbt);
65 if($shootingEntity !== null){
66 $this->setOwningEntity($shootingEntity);
67 }
68 }
69
70 public function attack(EntityDamageEvent $source) : void{
71 if($source->getCause() === EntityDamageEvent::CAUSE_VOID){
72 parent::attack($source);
73 }
74 }
75
76 protected function initEntity(CompoundTag $nbt) : void{
77 parent::initEntity($nbt);
78
79 $this->setMaxHealth(1);
80 $this->setHealth(1);
81 $this->damage = $nbt->getDouble(self::TAG_DAMAGE, $this->damage);
82
83 if(($stuckOnBlockPosTag = $nbt->getListTag(self::TAG_STUCK_ON_BLOCK_POS, IntTag::class)) !== null){
84 if(count($stuckOnBlockPosTag) !== 3){
85 throw new SavedDataLoadingException(self::TAG_STUCK_ON_BLOCK_POS . " tag should be a list of 3 TAG_Int");
86 }
87
88 $values = $stuckOnBlockPosTag->getValue();
89
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());
93 }
94 }
95
96 public function canCollideWith(Entity $entity) : bool{
97 return ($entity instanceof Living || $entity instanceof EndCrystal) && !$this->onGround;
98 }
99
100 public function canBeCollidedWith() : bool{
101 return false;
102 }
103
108 public function getBaseDamage() : float{
109 return $this->damage;
110 }
111
115 public function setBaseDamage(float $damage) : void{
116 $this->damage = $damage;
117 }
118
122 public function getResultDamage() : int{
123 return (int) ceil($this->damage);
124 }
125
126 public function saveNBT() : CompoundTag{
127 $nbt = parent::saveNBT();
128
129 $nbt->setDouble(self::TAG_DAMAGE, $this->damage);
130
131 if($this->blockHit !== null){
132 $nbt->setTag(self::TAG_STUCK_ON_BLOCK_POS, new ListTag([
133 new IntTag($this->blockHit->getFloorX()),
134 new IntTag($this->blockHit->getFloorY()),
135 new IntTag($this->blockHit->getFloorZ())
136 ]));
137 }
138
139 return $nbt;
140 }
141
142 protected function applyDragBeforeGravity() : bool{
143 return true;
144 }
145
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;
151 }
152 }
153
154 parent::onNearbyBlockChange();
155 }
156
157 public function hasMovementUpdate() : bool{
158 return $this->blockHit === null && parent::hasMovementUpdate();
159 }
160
161 protected function move(float $dx, float $dy, float $dz) : void{
162 $this->blocksAround = null;
163
164 Timings::$projectileMove->startTiming();
165 Timings::$projectileMoveRayTrace->startTiming();
166
167 $start = $this->location->asVector3();
168 $end = $start->add($dx, $dy, $dz);
169
170 $hitResult = null;
171
172 $world = $this->getWorld();
173 foreach(VoxelRayTrace::betweenPoints($start, $end) as $vector3){
174 $block = $world->getBlockAt($vector3->x, $vector3->y, $vector3->z);
175
176 $blockHitResult = $this->calculateInterceptWithBlock($block, $start, $end);
177 if($blockHitResult !== null){
178 $end = $blockHitResult->hitVector;
179 $hitResult = [$block, $blockHitResult];
180 break;
181 }
182 }
183
184 $entityDistance = PHP_INT_MAX;
185
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){
189 continue;
190 }
191
192 $entityBB = $entity->boundingBox->expandedCopy(0.3, 0.3, 0.3);
193 $entityHitResult = $entityBB->calculateIntercept($start, $end);
194
195 if($entityHitResult === null){
196 continue;
197 }
198
199 $distance = $this->location->distanceSquared($entityHitResult->hitVector);
200
201 if($distance < $entityDistance){
202 $entityDistance = $distance;
203 $hitResult = [$entity, $entityHitResult];
204 $end = $entityHitResult->hitVector;
205 }
206 }
207
208 Timings::$projectileMoveRayTrace->stopTiming();
209
210 $this->location = Location::fromObject(
211 $end,
212 $this->location->world,
213 $this->location->yaw,
214 $this->location->pitch
215 );
216 $this->recalculateBoundingBox();
217
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);
223 }else{
224 $ev = new ProjectileHitBlockEvent($this, $rayTraceResult, $objectHit);
225 $specificHitFunc = fn() => $this->onHitBlock($objectHit, $rayTraceResult);
226 }
227
228 $motionBeforeOnHit = clone $this->motion;
229 $ev->call();
230 $this->onHit($ev);
231 $specificHitFunc();
232
233 $this->isCollided = $this->onGround = true;
234 if($motionBeforeOnHit->equals($this->motion)){
235 $this->motion = Vector3::zero();
236 }
237 }else{
238 $this->isCollided = $this->onGround = false;
239 $this->blockHit = null;
240
241 //recompute angles...
242 $f = sqrt(($this->motion->x ** 2) + ($this->motion->z ** 2));
243 $this->setRotation(
244 atan2($this->motion->x, $this->motion->z) * 180 / M_PI,
245 atan2($this->motion->y, $f) * 180 / M_PI
246 );
247 }
248
249 $world->onEntityMoved($this);
250 $this->checkBlockIntersections();
251
252 Timings::$projectileMove->stopTiming();
253 }
254
262 protected function calculateInterceptWithBlock(Block $block, Vector3 $start, Vector3 $end) : ?RayTraceResult{
263 return $block->calculateIntercept($start, $end);
264 }
265
270 protected function onHit(ProjectileHitEvent $event) : void{
271
272 }
273
277 protected function onHitEntity(Entity $entityHit, RayTraceResult $hitResult) : void{
278 $damage = $this->getResultDamage();
279
280 if($damage >= 0){
281 $owner = $this->getOwningEntity();
282 if($owner === null){
283 $ev = new EntityDamageByEntityEvent($this, $entityHit, EntityDamageEvent::CAUSE_PROJECTILE, $damage);
284 }else{
285 $ev = new EntityDamageByChildEntityEvent($owner, $this, $entityHit, EntityDamageEvent::CAUSE_PROJECTILE, $damage);
286 }
287
288 $entityHit->attack($ev);
289
290 if($this->isOnFire()){
291 $ev = new EntityCombustByEntityEvent($this, $entityHit, 5);
292 $ev->call();
293 if(!$ev->isCancelled()){
294 $entityHit->setOnFire($ev->getDuration());
295 }
296 }
297 }
298
299 if($this->despawnsOnEntityHit()){
300 $this->flagForDespawn();
301 }
302 }
303
307 protected function onHitBlock(Block $blockHit, RayTraceResult $hitResult) : void{
308 $this->blockHit = $blockHit->getPosition()->asVector3();
309 $blockHit->onProjectileHit($this, $hitResult);
310 }
311
315 protected function despawnsOnEntityHit() : bool{
316 return true;
317 }
318}
onProjectileHit(Projectile $projectile, RayTraceResult $hitResult)
Definition Block.php:902
setOwningEntity(?Entity $owner)
Definition Entity.php:421
setHealth(float $amount)
Definition Entity.php:597
onHitBlock(Block $blockHit, RayTraceResult $hitResult)
calculateInterceptWithBlock(Block $block, Vector3 $start, Vector3 $end)
onHit(ProjectileHitEvent $event)
setTag(string $name, Tag $tag)
getListTag(string $name, string $tagClass=Tag::class)
setDouble(string $name, float $value)