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 }