PocketMine-MP 5.23.3 git-f7687af337d001ddbcc47b8e773f014a33faa662
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
47use function assert;
48use function atan2;
49use function ceil;
50use function count;
51use function sqrt;
52use const M_PI;
53use const PHP_INT_MAX;
54
55abstract class Projectile extends Entity{
56 private const TAG_STUCK_ON_BLOCK_POS = "StuckToBlockPos";
57 private const TAG_DAMAGE = "damage"; //TAG_Double
58 private const TAG_TILE_X = "tileX"; //TAG_Int
59 private const TAG_TILE_Y = "tileY"; //TAG_Int
60 private const TAG_TILE_Z = "tileZ"; //TAG_Int
61
62 protected float $damage = 0.0;
63 protected ?Vector3 $blockHit = null;
64
65 public function __construct(Location $location, ?Entity $shootingEntity, ?CompoundTag $nbt = null){
66 parent::__construct($location, $nbt);
67 if($shootingEntity !== null){
68 $this->setOwningEntity($shootingEntity);
69 }
70 }
71
72 public function attack(EntityDamageEvent $source) : void{
73 if($source->getCause() === EntityDamageEvent::CAUSE_VOID){
74 parent::attack($source);
75 }
76 }
77
78 protected function initEntity(CompoundTag $nbt) : void{
79 parent::initEntity($nbt);
80
81 $this->setMaxHealth(1);
82 $this->setHealth(1);
83 $this->damage = $nbt->getDouble(self::TAG_DAMAGE, $this->damage);
84
85 if(($stuckOnBlockPosTag = $nbt->getListTag(self::TAG_STUCK_ON_BLOCK_POS)) !== null){
86 if($stuckOnBlockPosTag->getTagType() !== NBT::TAG_Int || count($stuckOnBlockPosTag) !== 3){
87 throw new SavedDataLoadingException(self::TAG_STUCK_ON_BLOCK_POS . " tag should be a list of 3 TAG_Int");
88 }
89
91 $values = $stuckOnBlockPosTag->getValue();
92
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());
96 }
97 }
98
99 public function canCollideWith(Entity $entity) : bool{
100 return ($entity instanceof Living || $entity instanceof EndCrystal) && !$this->onGround;
101 }
102
103 public function canBeCollidedWith() : bool{
104 return false;
105 }
106
111 public function getBaseDamage() : float{
112 return $this->damage;
113 }
114
118 public function setBaseDamage(float $damage) : void{
119 $this->damage = $damage;
120 }
121
125 public function getResultDamage() : int{
126 return (int) ceil($this->damage);
127 }
128
129 public function saveNBT() : CompoundTag{
130 $nbt = parent::saveNBT();
131
132 $nbt->setDouble(self::TAG_DAMAGE, $this->damage);
133
134 if($this->blockHit !== null){
135 $nbt->setTag(self::TAG_STUCK_ON_BLOCK_POS, new ListTag([
136 new IntTag($this->blockHit->getFloorX()),
137 new IntTag($this->blockHit->getFloorY()),
138 new IntTag($this->blockHit->getFloorZ())
139 ]));
140 }
141
142 return $nbt;
143 }
144
145 protected function applyDragBeforeGravity() : bool{
146 return true;
147 }
148
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;
154 }
155 }
156
157 parent::onNearbyBlockChange();
158 }
159
160 public function hasMovementUpdate() : bool{
161 return $this->blockHit === null && parent::hasMovementUpdate();
162 }
163
164 protected function move(float $dx, float $dy, float $dz) : void{
165 $this->blocksAround = null;
166
167 Timings::$projectileMove->startTiming();
168 Timings::$projectileMoveRayTrace->startTiming();
169
170 $start = $this->location->asVector3();
171 $end = $start->add($dx, $dy, $dz);
172
173 $blockHit = null;
174 $entityHit = null;
175 $hitResult = null;
176
177 $world = $this->getWorld();
178 foreach(VoxelRayTrace::betweenPoints($start, $end) as $vector3){
179 $block = $world->getBlockAt($vector3->x, $vector3->y, $vector3->z);
180
181 $blockHitResult = $this->calculateInterceptWithBlock($block, $start, $end);
182 if($blockHitResult !== null){
183 $end = $blockHitResult->hitVector;
184 $blockHit = $block;
185 $hitResult = $blockHitResult;
186 break;
187 }
188 }
189
190 $entityDistance = PHP_INT_MAX;
191
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){
195 continue;
196 }
197
198 $entityBB = $entity->boundingBox->expandedCopy(0.3, 0.3, 0.3);
199 $entityHitResult = $entityBB->calculateIntercept($start, $end);
200
201 if($entityHitResult === null){
202 continue;
203 }
204
205 $distance = $this->location->distanceSquared($entityHitResult->hitVector);
206
207 if($distance < $entityDistance){
208 $entityDistance = $distance;
209 $entityHit = $entity;
210 $hitResult = $entityHitResult;
211 $end = $entityHitResult->hitVector;
212 }
213 }
214
215 Timings::$projectileMoveRayTrace->stopTiming();
216
217 $this->location = Location::fromObject(
218 $end,
219 $this->location->world,
220 $this->location->yaw,
221 $this->location->pitch
222 );
223 $this->recalculateBoundingBox();
224
225 if($hitResult !== null){
227 $ev = null;
228 if($entityHit !== null){
229 $ev = new ProjectileHitEntityEvent($this, $hitResult, $entityHit);
230 }elseif($blockHit !== null){
231 $ev = new ProjectileHitBlockEvent($this, $hitResult, $blockHit);
232 }else{
233 assert(false, "unknown hit type");
234 }
235
236 if($ev !== null){
237 $ev->call();
238 $this->onHit($ev);
239
240 if($ev instanceof ProjectileHitEntityEvent){
241 $this->onHitEntity($ev->getEntityHit(), $ev->getRayTraceResult());
242 }elseif($ev instanceof ProjectileHitBlockEvent){
243 $this->onHitBlock($ev->getBlockHit(), $ev->getRayTraceResult());
244 }
245 }
246
247 $this->isCollided = $this->onGround = true;
248 $this->motion = Vector3::zero();
249 }else{
250 $this->isCollided = $this->onGround = false;
251 $this->blockHit = null;
252
253 //recompute angles...
254 $f = sqrt(($this->motion->x ** 2) + ($this->motion->z ** 2));
255 $this->setRotation(
256 atan2($this->motion->x, $this->motion->z) * 180 / M_PI,
257 atan2($this->motion->y, $f) * 180 / M_PI
258 );
259 }
260
261 $world->onEntityMoved($this);
262 $this->checkBlockIntersections();
263
264 Timings::$projectileMove->stopTiming();
265 }
266
274 protected function calculateInterceptWithBlock(Block $block, Vector3 $start, Vector3 $end) : ?RayTraceResult{
275 return $block->calculateIntercept($start, $end);
276 }
277
282 protected function onHit(ProjectileHitEvent $event) : void{
283
284 }
285
289 protected function onHitEntity(Entity $entityHit, RayTraceResult $hitResult) : void{
290 $damage = $this->getResultDamage();
291
292 if($damage >= 0){
293 if($this->getOwningEntity() === null){
294 $ev = new EntityDamageByEntityEvent($this, $entityHit, EntityDamageEvent::CAUSE_PROJECTILE, $damage);
295 }else{
296 $ev = new EntityDamageByChildEntityEvent($this->getOwningEntity(), $this, $entityHit, EntityDamageEvent::CAUSE_PROJECTILE, $damage);
297 }
298
299 $entityHit->attack($ev);
300
301 if($this->isOnFire()){
302 $ev = new EntityCombustByEntityEvent($this, $entityHit, 5);
303 $ev->call();
304 if(!$ev->isCancelled()){
305 $entityHit->setOnFire($ev->getDuration());
306 }
307 }
308 }
309
310 $this->flagForDespawn();
311 }
312
316 protected function onHitBlock(Block $blockHit, RayTraceResult $hitResult) : void{
317 $this->blockHit = $blockHit->getPosition()->asVector3();
318 $blockHit->onProjectileHit($this, $hitResult);
319 }
320}
onProjectileHit(Projectile $projectile, RayTraceResult $hitResult)
Definition Block.php:897
setOwningEntity(?Entity $owner)
Definition Entity.php:417
setHealth(float $amount)
Definition Entity.php:593
onHitBlock(Block $blockHit, RayTraceResult $hitResult)
calculateInterceptWithBlock(Block $block, Vector3 $start, Vector3 $end)
onHit(ProjectileHitEvent $event)
setTag(string $name, Tag $tag)
setDouble(string $name, float $value)