1 /* 2 * Copyright (c) 2017-2019 sel-project 3 * 4 * Permission is hereby granted, free of charge, to any person obtaining a copy 5 * of this software and associated documentation files (the "Software"), to deal 6 * in the Software without restriction, including without limitation the rights 7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 * copies of the Software, and to permit persons to whom the Software is 9 * furnished to do so, subject to the following conditions: 10 * 11 * The above copyright notice and this permission notice shall be included in all 12 * copies or substantial portions of the Software. 13 * 14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 * SOFTWARE. 21 * 22 */ 23 /** 24 * Copyright: Copyright (c) 2017-2019 sel-project 25 * License: MIT 26 * Authors: Kripth 27 * Source: $(HTTP github.com/sel-project/selery/source/selery/entity/projectile.d, selery/entity/projectile.d) 28 */ 29 module selery.entity.projectile; 30 31 import std.conv : to; 32 import std.math : abs, sqrt, atan2, PI; 33 34 import selery.about; 35 import selery.block.block : Block, blockInto; 36 import selery.block.blocks : Blocks; 37 import selery.effect; 38 import selery.entity.entity : Entity, Entities; 39 import selery.entity.living : Living; 40 import selery.entity.metadata; 41 import selery.event.world; 42 import selery.math.vector; 43 import selery.player.player : Player; 44 import selery.util.color : Color; 45 import selery.world.world : World; 46 47 static import sul.entities; 48 49 abstract class Projectile : Entity { 50 51 private Living n_shooter; 52 private bool outfromshooter = false; 53 54 public this(World world, EntityPosition position, EntityPosition motion, Living shooter=null) { 55 super(world, position); 56 this.m_motion = motion; 57 this.n_shooter = shooter; 58 if(this.shot) { 59 //this.metadata[DATA_SHOOTER] = this.shooter.id.to!ulong; 60 } 61 } 62 63 public this(World world, Living shooter, float power) { 64 this(world, shooter.position + [0, shooter.eyeHeight, 0], shooter.direction * power, shooter); 65 } 66 67 public override void tick() { 68 super.tick(); 69 70 if(this.position.y < 0) { 71 this.despawn(); 72 return; 73 } 74 75 EntityPosition lastposition = this.position; 76 77 // TODO move in Entity and add bool doMotion 78 79 if(!this.motionless) { 80 81 // add gravity 82 83 84 // update the motion 85 if(this.acceleration != 0) this.motion = this.motion - [0, this.acceleration, 0]; 86 if(this.motion.y.abs > this.terminal_velocity) this.m_motion = EntityPosition(this.motion.x, this.motion.y > 0 ? this.terminal_velocity : -this.terminal_velocity, this.motion.z); 87 88 // move 89 this.move(this.position + this.motion, atan2(this.motion.x, this.motion.z) * 180f / PI, atan2(this.motion.y, sqrt(this.motion.x * this.motion.x + this.motion.z * this.motion.z)) * 180f / PI); 90 91 // apply the drag force 92 if(this.drag != 0) this.motion = this.motion * (1f - this.drag); 93 94 //TODO an entity could travel more that 1 block per tick 95 96 // collisions with entities 97 if(!this.outfromshooter && (!this.shot || !this.shooter.box.intersects(this.n_box))) this.outfromshooter = true; 98 foreach(ref Entity entity ; this.viewers) { 99 if(entity.box.intersects(this.n_box)) { 100 if(!this.shot || entity != this.shooter || this.outfromshooter) { 101 if(this.onCollide(entity)) break; 102 } 103 } 104 } 105 106 // collision with blocks 107 auto min = this.n_box.minimum; 108 auto max = this.n_box.maximum; 109 foreach(int x ; min.x.blockInto..max.x.blockInto+1) { 110 foreach(int y ; min.y.blockInto..max.y.blockInto+1) { 111 foreach(int z ; min.z.blockInto..max.z.blockInto+1) { 112 BlockPosition position = BlockPosition(x, y, z); 113 Block block = this.world[position]; 114 if(block.hasBoundingBox) { 115 block.box.update(position.entityPosition); 116 if(block.box.intersects(this.n_box) && this.onCollide(block, position, 0)) goto BreakCycle; 117 } 118 } 119 } 120 } 121 BreakCycle: 122 123 } 124 125 } 126 127 protected bool onCollide(Entity entity) { 128 return false; 129 } 130 131 protected bool onCollide(Block block, BlockPosition position, uint face) { 132 return false; 133 } 134 135 public @property @safe @nogc bool shot() { 136 return this.shooter !is null; 137 } 138 139 public @property @safe @nogc Living shooter() { 140 return this.n_shooter; 141 } 142 143 } 144 145 class Arrow : Projectile { 146 147 public static immutable float WIDTH = .5f; 148 public static immutable float HEIGHT = .5f; 149 150 public static immutable float FORCE = 2f; 151 152 public static immutable float ACCELERATION = .05f; 153 public static immutable float DRAG = .01f; 154 public static immutable float TERMINAL_VELOCITY = 5f; 155 156 private float power; 157 158 public this(World world, Living shooter, float force) { 159 super(world, shooter, FORCE * force); 160 this.power = force; 161 this.setSize(WIDTH, HEIGHT); 162 this.n_eye_height = HEIGHT / 2f; 163 this.acceleration = ACCELERATION; 164 this.drag = DRAG; 165 this.terminal_velocity = TERMINAL_VELOCITY; 166 } 167 168 public override pure nothrow @property @safe @nogc sul.entities.Entity data() { 169 return Entities.arrow; 170 } 171 172 public override bool onCollide(Entity entity) { 173 if(cast(Living)entity) { 174 //entity.to!Living.attack(new EntityDamagedByChildEvent(entity.to!Living, Damage.PROJECTILE, to!uint(this.power * 10f), this.shooter, this)); 175 this.despawn(); 176 return true; 177 } 178 return false; 179 } 180 181 public override bool onCollide(Block block, BlockPosition position, uint face) { 182 this.motionless = true; 183 return true; 184 } 185 186 } 187 188 class Snowball : Projectile { 189 190 public static immutable float WIDTH = .25f; 191 public static immutable float HEIGHT = .25f; 192 193 public static immutable float FORCE = 1.5f; 194 195 public static immutable float ACCELERATION = .03f; 196 public static immutable float DRAG = .01f; 197 public static immutable float TERMINAL_VELOCITY = 3f; 198 199 public this(World world, Living shooter) { 200 super(world, shooter, FORCE); 201 this.setSize(WIDTH, HEIGHT); 202 this.n_eye_height = HEIGHT / 2f; 203 this.acceleration = ACCELERATION; 204 this.drag = DRAG; 205 this.terminal_velocity = TERMINAL_VELOCITY; 206 } 207 208 public override pure nothrow @property @safe @nogc sul.entities.Entity data() { 209 return Entities.snowball; 210 } 211 212 public override void tick() { 213 super.tick(); 214 this.moved = false; 215 this.motionmoved = false; 216 } 217 218 public override bool onCollide(Entity entity) { 219 if(cast(Living)entity) { 220 //TODO damage blazes (3 points) 221 //entity.to!Living.attack(new EntityDamagedByChildEvent(entity.to!Living, Damage.GENERIC_PROJECTILE, 0, this.shooter, this)); 222 this.despawn(); 223 return true; 224 } 225 return false; 226 } 227 228 } 229 230 class Egg : Projectile { 231 232 public static immutable float WIDTH = .25f; 233 public static immutable float HEIGHT = .25f; 234 235 public static immutable float FORCE = 1.5f; 236 237 public static immutable float ACCELERATION = .03f; 238 public static immutable float DRAG = .01f; 239 public static immutable float TERMINAL_VELOCITY = 3f; 240 241 public this(World world, Living shooter) { 242 super(world, shooter, FORCE); 243 this.setSize(WIDTH, HEIGHT); 244 this.n_eye_height = HEIGHT / 2f; 245 this.acceleration = ACCELERATION; 246 this.drag = DRAG; 247 this.terminal_velocity = TERMINAL_VELOCITY; 248 } 249 250 public override pure nothrow @property @safe @nogc sul.entities.Entity data() { 251 return Entities.egg; 252 } 253 254 public override void tick() { 255 super.tick(); 256 this.moved = false; 257 this.motionmoved = false; 258 } 259 260 public override bool onCollide(Entity entity) { 261 if(cast(Living)entity) { 262 //entity.to!Living.attack(new EntityDamagedByChildEvent(entity.to!Living, Damage.GENERIC_PROJECTILE, 0, this.shooter, this)); 263 //TODO 12.5% of possibiity of spawn a baby chicken 264 this.despawn(); 265 return true; 266 } 267 return false; 268 } 269 270 } 271 272 class Enderpearl : Projectile { 273 274 public static immutable float WIDTH = .25f; 275 public static immutable float HEIGHT = .25f; 276 277 public static immutable float FORCE = 1.5f; 278 279 public static immutable float ACCELERATION = .03f; 280 public static immutable float DRAG = .01f; 281 public static immutable float TERMINAL_VELOCITY = 3f; 282 283 public this(World world, Living shooter) { 284 super(world, shooter, FORCE); 285 this.setSize(WIDTH, HEIGHT); 286 this.n_eye_height = HEIGHT / 2; 287 this.acceleration = ACCELERATION; 288 this.drag = DRAG; 289 this.terminal_velocity = TERMINAL_VELOCITY; 290 } 291 292 public override pure nothrow @property @safe @nogc sul.entities.Entity data() { 293 return Entities.enderpearl; 294 } 295 296 public override bool onCollide(Entity entity) { 297 if(cast(Living)entity) { 298 //entity.to!Living.attack(new EntityDamagedByChildEvent(entity.to!Living, Damage.GENERIC_PROJECTILE, 0, this.shooter, this)); 299 this.onCollide(); 300 return true; 301 } 302 return false; 303 } 304 305 public override bool onCollide(Block block, BlockPosition position, uint face) { 306 this.onCollide(); 307 return true; 308 } 309 310 protected void onCollide() { 311 if(this.shot) { 312 if(cast(Player)this.shooter) { 313 this.shooter.to!Player.teleport(this.position); 314 } else { 315 this.shooter.move(this.position); 316 } 317 //this.shooter.attack(new EntityDamageEvent(this.shooter, Damage.FALL, 5)); 318 } 319 this.despawn(); 320 } 321 322 } 323 324 class Fireball : Projectile { 325 326 public static immutable float WIDTH = 1f; 327 public static immutable float HEIGHT = 1f; 328 329 public static immutable float FORCE = 1.5f; 330 331 public static immutable float ACCELERATION = 0f; 332 public static immutable float DRAG = 0f; 333 public static immutable float TERMINAL_VELOCITY = 1.3f; 334 335 public this(World world, Living shooter) { 336 super(world, shooter, FORCE); 337 this.setSize(WIDTH, HEIGHT); 338 this.n_eye_height = HEIGHT / 2f; 339 this.acceleration = ACCELERATION; 340 this.drag = DRAG; 341 this.terminal_velocity = TERMINAL_VELOCITY; 342 } 343 344 public override pure nothrow @property @safe @nogc sul.entities.Entity data() { 345 return Entities.ghastFireball; 346 } 347 348 public override void tick() { 349 super.tick(); 350 this.moved = false; 351 if(this.ticks > 60) this.despawn(); 352 } 353 354 } 355 356 class SmallFireball : Projectile { 357 358 public static immutable float WIDTH = .3125f; 359 public static immutable float HEIGHT = .3125f; 360 361 public static immutable float FORCE = 1.5f; 362 363 public static immutable float ACCELERATION = 0f; 364 public static immutable float DRAG = 0f; 365 public static immutable float TERMINAL_VELOCITY = 1.3f; 366 367 public this(World world, Living shooter) { 368 super(world, shooter, FORCE); 369 this.setSize(WIDTH, HEIGHT); 370 this.n_eye_height = HEIGHT / 2; 371 this.acceleration = ACCELERATION; 372 this.drag = DRAG; 373 this.terminal_velocity = TERMINAL_VELOCITY; 374 } 375 376 public override pure nothrow @property @safe @nogc sul.entities.Entity data() { 377 return Entities.blazeFireball; 378 } 379 380 public override void tick() { 381 super.tick(); 382 this.moved = false; 383 this.motionmoved = false; 384 if(this.ticks > 60) this.despawn(); 385 } 386 387 public override bool onCollide(Entity entity) { 388 if(cast(Living)entity) { 389 //entity.to!Living.attack(new EntityDamagedByChildEvent(entity.to!Living, Damage.BLAZE_FIREBALL, 5, this.shooter, this)); 390 //TODO burn it with fire 391 this.despawn(); 392 return true; 393 } 394 return false; 395 } 396 397 } 398 399 class ExperienceBottle /*: Projectile*/ { 400 401 public static immutable float WIDTH = .25f; 402 public static immutable float HEIGHT = .25f; 403 404 public static immutable float FORCE = 1f; 405 406 public static immutable ACCELERATION = .05f; 407 public static immutable DRAG = .01f; 408 public static immutable TERMINAL_VELOCITY = 3f; 409 410 } 411 412 class Orb : Projectile { 413 414 public static immutable float WIDTH = .3f; 415 public static immutable float HEIGHT = .3f; 416 417 public static immutable float FORCE = 2f; 418 419 public static immutable float ACCELERATION = .04f; 420 public static immutable float DRAG = .02f; 421 public static immutable float TERMINAL_VELOCITY = 1.96f; 422 423 public this(ref World world, Living shooter) { 424 super(world, shooter, FORCE); 425 this.setSize(WIDTH, HEIGHT); 426 this.n_eye_height = HEIGHT / 2f; 427 this.acceleration = ACCELERATION; 428 this.drag = DRAG; 429 this.terminal_velocity = TERMINAL_VELOCITY; 430 } 431 432 public override pure nothrow @property @safe @nogc sul.entities.Entity data() { 433 return Entities.experienceOrb; 434 } 435 436 } 437 438 class Potion : Projectile { 439 440 public static immutable float WIDTH = .25f; 441 public static immutable float HEIGHT = .25f; 442 443 public static immutable float FORCE = 1f; 444 445 public static immutable float ACCELERATION = .05f; 446 public static immutable float DRAG = .01f; 447 public static immutable float TERMINAL_VELOCITY = 3f; 448 449 private ushort potion; 450 451 public this(World world, Living shooter, ushort potion) { 452 super(world, shooter, FORCE); 453 this.potion = potion; 454 //this.metadata[DATA_POTION_META] = potion; 455 this.setSize(WIDTH, HEIGHT); 456 this.n_eye_height = HEIGHT / 2; 457 this.acceleration = ACCELERATION; 458 this.drag = DRAG; 459 this.terminal_velocity = TERMINAL_VELOCITY; 460 } 461 462 public override pure nothrow @property @safe @nogc sul.entities.Entity data() { 463 return Entities.splashPotion; 464 } 465 466 public override bool onCollide(Entity entity) { 467 if(cast(Living)entity) { 468 this.onCollide(entity.to!Living); 469 return true; 470 } 471 return false; 472 } 473 474 public override bool onCollide(Block block, BlockPosition position, uint face) { 475 this.onCollide(); 476 return true; 477 } 478 479 protected void onCollide(Living collider=null) { 480 /*Effect effect = this.potion.to!ubyte.effect; 481 Color color = effect !is null ? Effect.effectColor(effect.id) : new Color(0, 0, 0); 482 ushort particle = effect !is null && (effect.id == Effects.HEALING || effect.id == Effects.HARMING) ? Particles.MOB_SPELL_INSTANTANEOUS : Particles.MOB_SPELL_AMBIENT; 483 foreach(uint i ; 0..24) { 484 this.world.addParticle(particle, this.position.round.add(this.world.random.next!float / 2f - .5f, 0f, this.world.random.next!float / 2f - .5f), color); 485 } 486 if(effect !is null) { 487 auto radius = this.n_box.grow(4f, 2f); 488 foreach(Living entity ; this.viewers!Living) { 489 if(entity.box.intersects(radius)) { 490 double d = entity.position.distance(this.position); 491 if(d < 16) { 492 double amount = 1 - (collider !is null && entity == collider ? 0 : sqrt(d) / 4); 493 entity.addEffect(new Effect(effect.id, to!uint(amount * (effect.duration / 20) + .5f), effect.level, this.shooter), amount); 494 } 495 } 496 } 497 } 498 this.despawn();*/ 499 } 500 501 } 502 503 class FallingBlock : Projectile { 504 505 public static immutable float WIDTH = .98f; 506 public static immutable float HEIGHT = .98f; 507 508 public static immutable float ACCELERATION = .04f; 509 public static immutable float DRAG = .02f; 510 public static immutable float TERMINAL_VELOCITY = /*1.96f*/10f; 511 512 private Block block; 513 514 public this(World world, Block block, BlockPosition position) { 515 super(world, position.entityPosition + [.5, .0001, .5], EntityPosition(0, 0, 0)); 516 this.setSize(WIDTH, HEIGHT); 517 this.n_eye_height = HEIGHT / 2f; 518 this.acceleration = ACCELERATION; 519 this.drag = DRAG; 520 this.terminal_velocity = TERMINAL_VELOCITY; 521 this.setSize(.98, .98); 522 this.block = block; 523 this.metadata.set!"variant"(block.bedrockId | block.bedrockMeta << 8); 524 this.n_data = block.javaId | block.javaMeta << 12; 525 } 526 527 public override pure nothrow @property @safe @nogc sul.entities.Entity data() { 528 return Entities.fallingBlock; 529 } 530 531 public override @property @safe bool motionless() { 532 return false; 533 } 534 535 public override bool onCollide(Block block, BlockPosition position, uint face) { 536 //TODO check the face 537 BlockPosition pos = position + [0, 1, 0]; 538 if(this.world[pos] == Blocks.air) { 539 this.world[pos] = &this.block; 540 this.despawn(); 541 return true; 542 } 543 return false; 544 } 545 546 }