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/effect.d, selery/effect.d)
28  */
29 module selery.effect;
30 
31 import selery.about : tick_t;
32 import selery.entity.human : Human;
33 import selery.entity.living : Living;
34 import selery.event.world.damage;
35 import selery.event.world.entity : EntityHealEvent;
36 import selery.player.player : isPlayerInstance;
37 
38 static import sul.effects;
39 public import sul.effects : Effects;
40 
41 class Effect {
42 	
43 	public enum tick_t UNLIMITED = int.max / 20;
44 	
45 	public static Effect fromId(sul.effects.Effect effect, Living victim, ubyte level, tick_t duration, Living attacker=null) {
46 		switch(effect.java.id) {
47 			case Effects.speed.java.id: return new SpeedChange(effect, victim, level, duration, attacker);
48 			case Effects.slowness.java.id: return new SpeedChange(effect, victim, level, duration, attacker);
49 			case Effects.instantHealth.java.id: return new InstantHealth(effect, victim, level, attacker);
50 			case Effects.instantDamage.java.id: return new InstantDamage(effect, victim, level, attacker);
51 			case Effects.regeneration.java.id: return new Regeneration(effect, victim, level, duration, attacker);
52 			case Effects.invisibility.java.id: return new Invisibility(effect, victim, level, duration, attacker);
53 			case Effects.hunger.java.id: return new Hunger(effect, victim, level, duration, attacker);
54 			case Effects.poison.java.id: return new Poison(effect, victim, level, duration, attacker);
55 			case Effects.wither.java.id: return new Wither(effect, victim, level, duration, attacker);
56 				//TODO health boost
57 				//TODO absorption
58 			case Effects.saturation.java.id: return new Saturation(effect, victim, level, duration, attacker);
59 			case Effects.levitation.java.id: return new Levitation(effect, victim, level, duration, attacker);
60 			default: return new Effect(effect, victim, level, duration, attacker);
61 		}
62 	}
63 	
64 	public static Effect fromId(sul.effects.Effect effect, Living victim, ubyte level, Living attacker=null) {
65 		return fromId(effect, victim, level, 30, attacker);
66 	}
67 	
68 	//TODO from string
69 	
70 	//TODO from java
71 	
72 	//TODO from pocket
73 	
74 	public const sul.effects.Effect effect;
75 	
76 	private Living n_victim;
77 	private Living n_attacker;
78 	
79 	public immutable ubyte level;
80 	public immutable uint levelFromOne;
81 	
82 	public immutable tick_t duration;
83 	protected tick_t ticks = 0;
84 	
85 	public this(sul.effects.Effect effect, Living victim, ubyte level, tick_t duration, Living attacker) {
86 		this.effect = effect;
87 		this.n_victim = victim;
88 		this.n_attacker = attacker;
89 		this.level = level;
90 		this.levelFromOne = 1 + level;
91 		this.duration = duration * 20;
92 	}
93 	
94 	public final pure nothrow @property @safe @nogc ubyte id() {
95 		return this.effect.java;
96 	}
97 	
98 	public final pure nothrow @property @safe @nogc Living victim() {
99 		return this.n_victim;
100 	}
101 	
102 	public final pure nothrow @property @safe @nogc Living attacker() {
103 		return this.n_attacker;
104 	}
105 	
106 	public pure nothrow @property @safe @nogc bool instant() {
107 		return false;
108 	}
109 	
110 	// called after the effect is added
111 	public void onStart() {}
112 	
113 	// called after the effect id removed
114 	public void onStop() {}
115 	
116 	public void tick() {
117 		this.ticks++;
118 	}
119 	
120 	public final pure nothrow @property @safe @nogc bool finished() {
121 		return this.ticks >= this.duration;
122 	}
123 	
124 	public bool opEquals(sul.effects.Effect e) {
125 		return this.id == e.java.id;
126 	}
127 	
128 	alias effect this;
129 	
130 }
131 
132 class SpeedChange : Effect {
133 	
134 	public this(sul.effects.Effect effect, Living victim, ubyte level, tick_t duration, Living attacker) {
135 		super(effect, victim, level, duration, attacker);
136 	}
137 	
138 	public override void onStart() {
139 		this.victim.recalculateSpeed();
140 	}
141 	
142 	public override void onStop() {
143 		this.victim.recalculateSpeed();
144 	}
145 	
146 }
147 
148 abstract class InstantEffect : Effect {
149 	
150 	public this(sul.effects.Effect, Living victim, ubyte level, Living attacker) {
151 		super(effect, victim, level, 0, attacker);
152 	}
153 	
154 	public final override pure nothrow @property @safe @nogc bool instant() {
155 		return true;
156 	}
157 	
158 	public override void onStart() {
159 		this.apply();
160 	}
161 	
162 	protected abstract void apply();
163 	
164 }
165 
166 class InstantHealth : InstantEffect {
167 	
168 	public this(sul.effects.Effect, Living victim, ubyte level, Living attacker) {
169 		super(effect, victim, level, attacker);
170 	}
171 	
172 	protected override void apply() {
173 		//TODO heal entity (or damage undeads)
174 	}
175 	
176 }
177 
178 class InstantDamage : InstantEffect {
179 	
180 	public this(sul.effects.Effect, Living victim, ubyte level, Living attacker) {
181 		super(effect, victim, level, attacker);
182 	}
183 	
184 	protected override void apply() {
185 		//TODO damage entity (or heal undeads)
186 	}
187 	
188 }
189 
190 abstract class RepetitionEffect(tick_t[] repetitions) : Effect {
191 	
192 	protected immutable tick_t repeat;
193 	
194 	public this(sul.effects.Effect effect, Living victim, ubyte level, tick_t duration, Living attacker) {
195 		super(effect, victim, level, duration, attacker);
196 		static if(repetitions.length > 1) {
197 			this.repeat = level < repetitions.length ? repetitions[level] : repetitions[$-1];
198 		} else {
199 			this.repeat = repetitions[0];
200 		}
201 	}
202 	
203 	public override void tick() {
204 		super.tick();
205 		if(this.ticks % this.repeat == 0) {
206 			this.onRepeat();
207 		}
208 	}
209 	
210 	public abstract void onRepeat();
211 	
212 }
213 
214 class Regeneration : RepetitionEffect!([50, 25, 12, 6, 3, 1]) {
215 	
216 	public this(sul.effects.Effect effect, Living victim, ubyte level, tick_t duration, Living attacker) {
217 		super(effect, victim, level, duration, attacker);
218 	}
219 	
220 	public override void onRepeat() {
221 		this.victim.heal(new EntityHealEvent(this.victim, 1));
222 	}
223 	
224 }
225 
226 class Invisibility : Effect {
227 	
228 	private bool invisible, show_nametag;
229 	
230 	public this(sul.effects.Effect effect, Living victim, ubyte level, tick_t duration, Living attacker) {
231 		super(effect, victim, level, duration, attacker);
232 	}
233 	
234 	public override void onStart() {
235 		this.invisible = this.victim.invisible;
236 		this.show_nametag = this.victim.showNametag;
237 		this.victim.invisible = true;
238 		this.victim.showNametag = false;
239 	}
240 	
241 	public override void onStop() {
242 		this.victim.invisible = this.invisible;
243 		this.victim.showNametag = this.show_nametag;
244 	}
245 	
246 }
247 
248 class Hunger : Effect {
249 	
250 	private Human human;
251 	private immutable float exhaustion;
252 	
253 	public this(sul.effects.Effect effect, Living victim, ubyte level, tick_t duration, Living attacker) {
254 		super(effect, victim, level, duration, attacker);
255 		this.human = cast(Human)victim; //TODO assert this
256 		this.exhaustion = .005f * levelFromOne;
257 	}
258 	
259 	public override void tick() {
260 		super.tick();
261 		this.human.exhaust(this.exhaustion);
262 	}
263 	
264 }
265 
266 class Poison : RepetitionEffect!([25, 12, 6, 3, 1]) {
267 	
268 	public this(sul.effects.Effect effect, Living victim, ubyte level, tick_t duration, Living attacker) {
269 		super(effect, victim, level, duration, attacker);
270 	}
271 	
272 	public override void onRepeat() {
273 		if(this.victim.health > 1) {
274 			this.victim.attack(new EntityDamageByPoisonEvent(this.victim)); //TODO thrown by player
275 		}
276 	}
277 	
278 }
279 
280 class Wither : RepetitionEffect!([40, 20, 10, 5, 2, 1]) {
281 	
282 	public this(sul.effects.Effect effect, Living victim, ubyte level, tick_t duration, Living attacker) {
283 		super(effect, victim, level, duration, attacker);
284 	}
285 	
286 	public override void onRepeat() {
287 		this.victim.attack(new EntityDamageByWitherEffectEvent(this.victim)); //TODO thrown by player
288 	}
289 	
290 }
291 
292 class Saturation : Effect {
293 	
294 	private Human human;
295 	private immutable uint food;
296 	private immutable float saturation;
297 	
298 	public this(sul.effects.Effect effect, Living victim, ubyte level, tick_t duration, Living attacker) {
299 		super(effect, victim, level, duration, attacker);
300 		this.human = cast(Human)victim; //TODO assert this
301 		this.food = this.levelFromOne;
302 		this.saturation = this.levelFromOne * 2;
303 	}
304 	
305 	public override void tick() {
306 		super.tick();
307 		if(this.human.hunger < 20) this.human.hunger = this.human.hunger + this.food;
308 		this.human.saturate(this.saturation);
309 	}
310 	
311 }
312 
313 class Levitation : Effect {
314 	
315 	private void delegate() apply;
316 	private immutable double distance;
317 	
318 	public this(sul.effects.Effect effect, Living victim, ubyte level, tick_t duration, Living attacker) {
319 		super(effect, victim, level, duration, attacker);
320 		this.distance = .9 * this.levelFromOne / 20;
321 		if(!isPlayerInstance(victim)) {
322 			this.apply = &this.move;
323 		} else {
324 			this.apply = &this.doNothing;
325 		}
326 	}
327 	
328 	public override void tick() {
329 		super.tick();
330 		this.apply();
331 	}
332 	
333 	private void move() {
334 		//TODO check flying and underwater
335 		//TODO do not move into a block
336 		this.victim.move(this.victim.position + [0, this.distance, 0]);
337 	}
338 	
339 	private void doNothing() {}
340 	
341 }