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/event/world/damage.d, selery/event/world/damage.d) 28 */ 29 module selery.event.world.damage; 30 31 import std.algorithm : max; 32 33 import selery.block.block : Block; 34 import selery.effect; 35 import selery.enchantment; 36 import selery.entity.entity : Entity; 37 import selery.entity.human : Human; 38 import selery.entity.interfaces; 39 import selery.entity.living : Living; 40 import selery.entity.noai : Lightning; 41 import selery.event.event : Cancellable; 42 import selery.event.world.entity : EntityEvent; 43 import selery.event.world.player : PlayerEvent; 44 import selery.item.item : Item; 45 import selery.item.slot : Slot; 46 import selery.item.tool : Tools; 47 import selery.lang : Translation, Translatable; 48 import selery.math.vector; 49 import selery.player.player : Player; 50 import selery.world.world : World; 51 52 private enum Modifiers : size_t { 53 54 none = 0, 55 56 resistance = 1 << 0, 57 falling = 1 << 1, 58 armor = 1 << 2, 59 fire = 1 << 3, 60 blast = 1 << 4, 61 projectile = 1 << 5, 62 63 all = size_t.max 64 65 } 66 67 // damage caused by itself 68 interface EntityDamageEvent : EntityEvent, Cancellable { 69 70 public pure nothrow @property @safe @nogc Entity victim(); 71 72 public pure nothrow @property @safe @nogc float originalDamage(); 73 74 public pure nothrow @property @safe @nogc float damage(); 75 76 public pure nothrow @property @safe @nogc float damage(float damage); 77 78 public pure nothrow @property @safe @nogc bool imminent(); 79 80 public pure nothrow @property @safe @nogc Translation message(); 81 82 public static mixin template Implementation(size_t modifiers) { 83 84 mixin Cancellable.Implementation; 85 86 mixin EntityEvent.Implementation; 87 88 private float n_original_damage; 89 private float m_damage; 90 91 protected Translation n_message; 92 93 protected @safe entityDamage(Entity entity, float damage, Translatable translatable) { 94 this.n_entity = entity; 95 this.n_original_damage = damage; 96 this.calculateDamage(); 97 this.n_message = Translation(translatable, entity.displayName); 98 } 99 100 protected @safe void calculateDamage() { 101 102 static if(modifiers != Modifiers.none) { 103 104 float damage = this.originalDamage; 105 106 if(cast(Living)this.entity) { 107 108 Living victim = cast(Living)this.entity; 109 110 static if(modifiers & Modifiers.resistance) { 111 if(Effect* resistance = (Effects.resistance in victim)) { 112 damage /= 1.2 * (*resistance).levelFromOne; 113 } 114 } 115 116 static if(modifiers & Modifiers.falling) { 117 if(Effect* jumpBoost = (Effects.jumpBoost in victim)) { 118 damage -= (*jumpBoost).levelFromOne; 119 } 120 } 121 122 static if(modifiers & Modifiers.armor) { 123 if(cast(Human)victim) { 124 Human human = cast(Human)victim; 125 126 float protection = human.inventory.protection; 127 damage *= 1f - max(protection / 5f, protection - damage / 2f) / 25f; 128 129 float epf = 0f; 130 foreach(size_t i, Slot slot; human.inventory.armor) { 131 if(!slot.empty) { 132 if(Enchantment* p = (Enchantments.protection in slot.item)) { 133 epf += (*p).level; 134 } 135 static if(modifiers & Modifiers.fire) { 136 if(Enchantment* fireProtection = (Enchantments.fireProtection in slot.item)) { 137 epf += (*fireProtection).level * 2; 138 } 139 } 140 static if(modifiers & Modifiers.blast) { 141 if(Enchantment* blastProtection = (Enchantments.blastProtection in slot.item)) { 142 epf += (*blastProtection).level * 2; 143 } 144 } 145 static if(modifiers & Modifiers.projectile) { 146 if(Enchantment* projectileProtection = (Enchantments.projectileProtection in slot.item)) { 147 epf += (*projectileProtection).level * 2; 148 } 149 } 150 static if(modifiers & Modifiers.falling) { 151 // boots only 152 if(i == 3) { 153 if(Enchantment* featherFalling = (Enchantments.featherFalling in slot.item)) { 154 epf += (*featherFalling).level * 3; 155 } 156 } 157 } 158 if(epf >= 20) { 159 epf = 20f; 160 break; 161 } 162 } 163 } 164 if(epf > 0) { 165 damage *= 1f - epf / 25f; 166 } 167 168 } 169 } 170 171 } 172 173 this.m_damage = damage < 0 ? 0 : damage; 174 175 } else { 176 177 this.m_damage = this.originalDamage; 178 179 } 180 181 } 182 183 public final override pure nothrow @property @safe @nogc Entity victim() { 184 return this.entity; 185 } 186 187 public final override pure nothrow @property @safe @nogc float originalDamage() { 188 return this.n_original_damage; 189 } 190 191 public final override pure nothrow @property @safe @nogc float damage() { 192 return this.m_damage; 193 } 194 195 public final override pure nothrow @property @safe @nogc float damage(float damage) { 196 return this.m_damage = damage; 197 } 198 199 public override pure nothrow @property @safe @nogc bool imminent() { 200 return false; 201 } 202 203 public final override pure nothrow @property @safe @nogc Translation message() { 204 return this.n_message; 205 } 206 207 } 208 209 } 210 211 // damage caused by another entity 212 interface EntityDamageByEntityEvent : EntityDamageEvent { 213 214 public pure nothrow @property @safe @nogc Entity damager(); 215 216 public pure nothrow @property @safe @nogc bool doKnockback(); 217 218 public pure nothrow @property @safe @nogc EntityPosition knockback(); 219 220 public pure nothrow @property @safe @nogc bool isCritical(); 221 222 public static mixin template Implementation(bool impl_entity_damage_event, size_t modifiers) { 223 224 static if(impl_entity_damage_event) { 225 mixin EntityDamageEvent.Implementation!modifiers; 226 } 227 228 private Entity n_damager; 229 protected EntityPosition n_knockback; 230 protected float knockback_modifier = .32; 231 protected bool n_critical; 232 233 protected @safe entityDamageByEntity(Entity victim, Entity damager, float damage, Translatable translatable) { 234 this.entityDamage(victim, this.isCritical ? damage * 1.5 : damage, translatable); 235 this.n_damager = damager; 236 this.n_message.parameters ~= damager.displayName; 237 } 238 239 public pure nothrow @property @safe @nogc Entity damager() { 240 return this.n_damager; 241 } 242 243 public pure nothrow @property @safe @nogc bool doKnockback() { 244 return this.knockback_modifier != 0; 245 } 246 247 public pure nothrow @property @safe @nogc EntityPosition knockback() { 248 return EntityPosition(this.n_knockback.x * this.knockback_modifier, .4, this.n_knockback.z * this.knockback_modifier); 249 } 250 251 public pure nothrow @property @safe @nogc bool isCritical() { 252 return this.n_critical; 253 } 254 255 } 256 257 } 258 259 // void 260 261 class EntityDamageByVoidEvent : EntityDamageEvent { 262 263 mixin EntityDamageEvent.Implementation!(Modifiers.none); 264 265 protected @safe this() {} 266 267 public @safe this(Entity entity) { 268 this.entityDamage(entity, 4, Translatable.all("death.attack.outOfWorld")); 269 } 270 271 public final override pure nothrow @property @safe @nogc bool imminent() { 272 return true; 273 } 274 275 } 276 277 final class EntityPushedIntoVoidEvent : EntityDamageByVoidEvent, EntityDamageByEntityEvent { 278 279 mixin EntityDamageByEntityEvent.Implementation!(false, Modifiers.none); 280 281 public @safe this(Entity victim, Entity damager) { 282 super(); 283 this.entityDamageByEntity(victim, damager, 4, Translatable.all("death.attack.outOfWorld")); // no message for "{0} was pushed out of the world" 284 } 285 286 mixin Cancellable.FinalImplementation; 287 288 } 289 290 // command 291 292 class EntityDamageByCommandEvent : EntityDamageEvent { 293 294 mixin EntityDamageEvent.Implementation!(Modifiers.none); 295 296 public @safe this(Entity entity) { 297 this.entityDamage(entity, 0xDEAD, Translatable.all("death.attack.generic")); 298 } 299 300 public final override pure nothrow @property @safe @nogc bool imminent() { 301 return true; 302 } 303 304 } 305 306 // attack (contact) 307 308 class EntityDamageByEntityAttackEvent : EntityDamageByEntityEvent { 309 310 mixin EntityDamageByEntityEvent.Implementation!(true, Modifiers.resistance | Modifiers.armor); 311 312 public @safe this(Entity victim, Entity damager, float damage) { 313 this.entityDamageByEntity(victim, damager, damage, Translatable.all("death.attack.player")); 314 this.n_knockback = damager.direction; 315 } 316 317 public pure nothrow @property @safe @nogc Item item() { 318 return null; 319 } 320 321 } 322 323 alias EntityAttackedByEntityEvent = EntityDamageByEntityAttackEvent; 324 325 class EntityDamageByPlayerAttackEvent : EntityDamageByEntityAttackEvent, PlayerEvent { 326 327 private Item n_item; 328 329 public @trusted this(Entity victim, Player damager) { 330 float damage = 1; 331 if(!damager.inventory.held.empty) { 332 this.n_item = damager.inventory.held.item; 333 // damage from weapon and weapon's enchantments 334 damage = this.item.attack; 335 if(Enchantment* sharpness = (Enchantments.sharpness in this.item)) damage *= 1.2f * (*sharpness).level; 336 if(cast(Arthropods)this.victim){ if(Enchantment* baneOfArthropods = (Enchantments.baneOfArthropods in this.item)) damage *= 2.5f * (*baneOfArthropods).level; } 337 if(cast(Undead)this.victim){ if(Enchantment* smite = (Enchantments.smite in this.item)) damage *= 2.5f * (*smite).level; } 338 } 339 // effects 340 if(Effect* strength = (Effects.strength in damager)) damage *= 1.3f * (*strength).levelFromOne; 341 if(Effect* weakness = (Effects.weakness in damager)) damage *= .5f * (*weakness).levelFromOne; 342 // critical 343 this.n_critical = damager.falling && !damager.sprinting && damager.vehicle is null && Effects.blindness !in damager; 344 // calculate damage 345 super(victim, damager, damage); 346 // more enchantments 347 if(this.item !is null) { 348 //TODO fire ench 349 // more knockback! 350 if(this.item.toolType == Tools.sword || this.item.toolType == Tools.axe) { 351 this.knockback_modifier = .52; 352 } 353 if(Enchantment* knockback = (Enchantments.knockback in this.item)) { 354 this.knockback_modifier += .6 * (*knockback).level; 355 } 356 } 357 // add weapon's name to args 358 if(this.item !is null && this.item.customName != "") { 359 this.n_message.translatable = Translatable.all("death.attack.player.item"); 360 this.n_message.parameters ~= this.item.customName; 361 } 362 } 363 364 public final override pure nothrow @property @safe @nogc Player player() { 365 return this.playerDamager; 366 } 367 368 public final pure nothrow @property @safe @nogc Player playerDamager() { 369 return cast(Player)this.damager; 370 } 371 372 public final override pure nothrow @property @safe @nogc Item item() { 373 return this.n_item; 374 } 375 376 } 377 378 alias EntityAttackedByPlayerEvent = EntityDamageByPlayerAttackEvent; 379 380 final class PlayerDamageByPlayerAttackEvent : EntityDamageByPlayerAttackEvent { 381 382 public @safe this(Player victim, Player damager) { 383 super(victim, damager); 384 } 385 386 public pure nothrow @property @safe @nogc Player victimPlayer() { 387 return cast(Player)this.victim; 388 } 389 390 } 391 392 alias PlayerAttackedByPlayerEvent = PlayerDamageByPlayerAttackEvent; 393 394 // projectile 395 /* 396 // thrower (damager) can be null if the projectile has been thrown by a plugin or a dispencer 397 abstract class EntityDamageWithProjectileEvent : EntityDamageByEntityEvent {} 398 399 final class EntityDamageWithArrowEvent : EntityDamageWithProjectileEvent {} 400 401 final class EntityPummeledEvent : EntityDamageWithProjectileEvent {} 402 403 class EntityDamageWithFireballEvent : EntityDamageWithProjectileEvent {}*/ 404 405 // suffocation 406 407 final class EntitySuffocationEvent : EntityDamageEvent { 408 409 mixin EntityDamageEvent.Implementation!(Modifiers.none); 410 411 public @safe this(Entity entity) { 412 this.entityDamage(entity, 1, Translatable.all("death.attack.inWall")); 413 } 414 415 } 416 417 // drowning 418 419 class EntityDrowningEvent : EntityDamageEvent { 420 421 mixin EntityDamageEvent.Implementation!(Modifiers.none); 422 423 protected @safe @nogc this() {} 424 425 public @safe this(Entity entity) { 426 this.entityDamage(entity, 1, Translatable.all("death.attack.drown")); 427 } 428 429 } 430 431 final class EntityDrowningEscapingEntityEvent : EntityDrowningEvent { 432 433 mixin EntityDamageByEntityEvent.Implementation!(false, Modifiers.none); 434 435 public @safe this(Entity victim, Entity damager) { 436 this.entityDamageByEntity(victim, damager, 1, Translatable.all("death.attack.drown.player")); 437 } 438 439 } 440 441 // explosion 442 443 class EntityDamageByExplosionEvent : EntityDamageEvent { 444 445 mixin EntityDamageEvent.Implementation!(Modifiers.resistance | Modifiers.armor | Modifiers.blast); 446 447 protected @safe @nogc this() {} 448 449 public @safe this(Entity entity, float damage) { 450 this.entityDamage(entity, damage, Translatable.all("death.attack.explosion")); 451 } 452 453 } 454 455 class EntityDamageByEntityExplosionEvent : EntityDamageByExplosionEvent, EntityDamageByEntityEvent { 456 457 mixin EntityDamageByEntityEvent.Implementation!(false, Modifiers.resistance | Modifiers.armor | Modifiers.blast); 458 459 public @safe this(Entity victim, Entity damager, float damage) { 460 this.entityDamageByEntity(victim, damager, damage, Translatable.all("death.attack.explosion.player")); 461 } 462 463 mixin Cancellable.FinalImplementation; 464 465 } 466 467 //TODO final class EntityDamageByTntExplosionEvent : EntityDamageByEntityExplosionEvent {} 468 469 //TODO final class EntityDamageByCreeperExplosionEvent : EntityDamageByEntityExplosionEvent {} 470 471 // hot stuff (fire and lava) 472 473 interface EntityDamageByHeatEvent : EntityDamageEvent { 474 475 public static mixin template Implementation() { 476 477 mixin EntityDamageEvent.Implementation!(Modifiers.resistance | Modifiers.armor | Modifiers.fire); 478 479 } 480 481 } 482 483 interface EntityDamageByHeatEscapingEntityEvent : EntityDamageByHeatEvent, EntityDamageByEntityEvent { 484 485 public static mixin template Implementation() { 486 487 mixin EntityDamageByEntityEvent.Implementation!(false, Modifiers.resistance | Modifiers.armor | Modifiers.fire); 488 489 mixin Cancellable.FinalImplementation; 490 491 } 492 493 } 494 495 class EntityBurningEvent : EntityDamageByHeatEvent { 496 497 mixin EntityDamageByHeatEvent.Implementation; 498 499 protected @safe @nogc this() {} 500 501 public @safe this(Entity entity) { 502 this.entityDamage(entity, 1, Translatable.all("death.attack.onFire")); 503 } 504 505 } 506 507 final class EntityBurningEscapingEntityEvent : EntityBurningEvent, EntityDamageByHeatEscapingEntityEvent { 508 509 mixin EntityDamageByHeatEscapingEntityEvent.Implementation; 510 511 public @safe this(Entity victim, Entity damager) { 512 this.entityDamageByEntity(victim, damager, 1, Translatable.all("death.attack.onFire.player")); 513 } 514 515 } 516 517 class EntityDamageByFireEvent : EntityDamageByHeatEvent { 518 519 mixin EntityDamageByHeatEvent.Implementation; 520 521 protected @safe @nogc this() {} 522 523 public @safe this(Entity entity) { 524 this.entityDamage(entity, 1, Translatable.all("death.attack.inFire")); 525 } 526 527 } 528 529 final class EntityDamageByFireEscapingEntityEvent : EntityDamageByFireEvent, EntityDamageByHeatEscapingEntityEvent { 530 531 mixin EntityDamageByHeatEscapingEntityEvent.Implementation; 532 533 public @safe this(Entity victim, Entity damager) { 534 this.entityDamageByEntity(victim, damager, 1, Translatable.all("death.attack.inFire.player")); 535 } 536 537 } 538 539 class EntityDamageByLavaEvent : EntityDamageByHeatEvent { 540 541 mixin EntityDamageByHeatEvent.Implementation; 542 543 protected @safe @nogc this() {} 544 545 public @safe this(Entity entity) { 546 this.entityDamage(entity, 4, Translatable.all("death.attack.lava")); 547 } 548 549 } 550 551 final class EntityDamageByLavaEscapingEntityEvent : EntityDamageByLavaEvent, EntityDamageByHeatEscapingEntityEvent { 552 553 mixin EntityDamageByHeatEscapingEntityEvent.Implementation; 554 555 public @safe this(Entity victim, Entity damager) { 556 this.entityDamageByEntity(victim, damager, 4, Translatable.all("death.attack.lava.player")); 557 } 558 559 } 560 561 class EntityDamageByMagmaBlockEvent : EntityDamageByHeatEvent { 562 563 mixin EntityDamageByHeatEvent.Implementation; 564 565 protected @safe @nogc this() {} 566 567 public @safe this(Entity entity) { 568 this.entityDamage(entity, 4, Translatable.all("death.attack.magmaBlock")); 569 } 570 571 } 572 573 final class EntityDamageByMagmaBlockEscapingEntityEvent : EntityDamageByMagmaBlockEvent, EntityDamageByHeatEscapingEntityEvent { 574 575 mixin EntityDamageByHeatEscapingEntityEvent.Implementation; 576 577 public @safe this(Entity victim, Entity damager) { 578 this.entityDamageByEntity(victim, damager, 4, Translatable.all("death.attack.magmaBlock.player")); 579 } 580 581 } 582 583 // magic 584 585 class EntityDamageByMagicEvent : EntityDamageEvent { 586 587 mixin EntityDamageEvent.Implementation!(Modifiers.none); 588 589 protected @safe @nogc this() {} 590 591 public @safe this(Entity entity, float damage) { 592 this.entityDamage(entity, damage, Translatable.all("death.attack.magic")); 593 } 594 595 } 596 597 final class EntityDamageWithMagicEvent : EntityDamageByMagicEvent, EntityDamageByEntityEvent { 598 599 mixin EntityDamageByEntityEvent.Implementation!(false, Modifiers.none); 600 601 public @safe this(Entity victim, Entity damager, float damage) { 602 this.entityDamageByEntity(victim, damager, damage, Translatable.all("death.attack.indirectMagic")); 603 } 604 605 mixin Cancellable.FinalImplementation; 606 607 } 608 609 // poison 610 611 final class EntityDamageByPoisonEvent : EntityDamageEvent { 612 613 mixin EntityDamageEvent.Implementation!(Modifiers.none); 614 615 public @safe this(Entity entity) { 616 this.entityDamage(entity, 1, Translatable.init); // no message (can't die poisoned) 617 } 618 619 } 620 621 // wither 622 623 final class EntityDamageByWitherEffectEvent : EntityDamageEvent { 624 625 mixin EntityDamageEvent.Implementation!(Modifiers.none); 626 627 public @safe this(Entity entity) { 628 this.entityDamage(entity, 1, Translatable.all("death.attack.wither")); 629 } 630 631 } 632 633 // lightning 634 635 final class EntityStruckByLightningEvent : EntityDamageEvent { 636 637 mixin EntityDamageEvent.Implementation!(Modifiers.resistance | Modifiers.armor); 638 639 private Lightning n_lightning; 640 641 public @safe this(Entity entity, Lightning lightning) { 642 this.entityDamage(entity, 5, Translatable.all("death.attack.lightning")); 643 this.n_lightning = lightning; 644 } 645 646 public pure nothrow @property @safe @nogc Lightning lightning() { 647 return this.n_lightning; 648 } 649 650 public pure nothrow @property @safe @nogc EntityPosition position() { 651 return this.lightning.position; 652 } 653 654 } 655 656 // thorns 657 658 final class EntityDamageByThornsEvent : EntityDamageByEntityEvent { 659 660 mixin EntityDamageByEntityEvent.Implementation!(true, Modifiers.resistance | Modifiers.armor); 661 662 public @safe this(Entity victim, Entity damager, float damage) { 663 this.entityDamageByEntity(victim, damager, damage, Translatable.all("death.attack.thorns")); 664 } 665 666 } 667 668 // starvation 669 670 final class EntityStarveEvent : EntityDamageEvent { 671 672 mixin EntityDamageEvent.Implementation!(Modifiers.none); 673 674 public @safe this(Entity entity) { 675 this.entityDamage(entity, 1, Translatable.all("death.attack.starve")); 676 } 677 678 } 679 680 // falling block (is the block already placed?) 681 682 class EntitySquashedByFallingBlockEvent : EntityDamageEvent { 683 684 mixin EntityDamageEvent.Implementation!(Modifiers.armor); 685 686 private Block n_block; 687 688 public @safe this(Entity entity, Block block, float damage, const Translatable message=Translatable.all("death.attack.fallingBlock")) { 689 this.entityDamage(entity, damage, message); 690 } 691 692 } 693 694 /*final class EntitySquashedByAnvilEvent : EntitySquashedByFallingBlockEvent { 695 696 public @safe this(Entity entity, Blocks.Anvil anvil, float damage) { 697 super(entity, anvil, damage, Translatable.all("death.attack.anvil")); 698 } 699 700 }*/ 701 702 // cactus 703 704 class EntityDamageByCactusEvent : EntityDamageEvent { 705 706 mixin EntityDamageEvent.Implementation!(Modifiers.resistance | Modifiers.armor); 707 708 protected @safe @nogc this() {} 709 710 public @safe this(Entity entity) { 711 this.entityDamage(entity, 1, Translatable.all("death.attack.cactus")); 712 } 713 714 } 715 716 final class EntityDamageByCactusEscapingEntityEvent : EntityDamageByCactusEvent, EntityDamageByEntityEvent { 717 718 mixin EntityDamageByEntityEvent.Implementation!(false, Modifiers.resistance | Modifiers.armor); 719 720 public @safe this(Entity victim, Entity damager) { 721 this.entityDamageByEntity(victim, damager, 1, Translatable.all("death.attack.cactus.player")); 722 } 723 724 mixin Cancellable.FinalImplementation; 725 726 } 727 728 // falling 729 730 class EntityFallDamageEvent : EntityDamageEvent { 731 732 mixin EntityDamageEvent.Implementation!(Modifiers.falling); 733 734 protected @safe @nogc this() {} 735 736 public @safe this(Entity entity, float damage) { 737 this.entityDamage(entity, damage, Translatable.all("death.attack.fall")); 738 } 739 740 } 741 742 final class EntityDoomedToFallEvent : EntityFallDamageEvent, EntityDamageByEntityEvent { 743 744 mixin EntityDamageByEntityEvent.Implementation!(false, Modifiers.falling); 745 746 public @safe this(Entity victim, Entity damager, float damage) { 747 this.entityDamageByEntity(victim, damager, damage, Translatable.all("death.fell.assist")); 748 } 749 750 mixin Cancellable.FinalImplementation; 751 752 }