1 /*
2  * Copyright (c) 2017-2018 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: 2017-2018 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 }