• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Spell - Water Explosion 2.1b

Spell: Water Explosion - Creates water explosions around caster dealing x damage, depending on spells level.



Version 1.1 - Added non-selection trigger for dummy's.

Version 1.2 - Paused timer before destroying it and nulled handle variables.

Version 1.3 - Now Fully MUI and code optimized a bit.

Version 1.3b - Fix whit leeks.

Version 1.3c - Only nulled a bit of variables.

Version 1.3d - In filter function replaced local variable to function GetFilterUnit() as I guess it is a bit of leek when using a variable.

Version 1.4 - Many important fixes, first timer activation area affection fix + first cast lag fix.

Version 1.5 - Uber update which gives this spell mass speed update jass->vjass ofc i don't mean its faster because of vjass it can only be slower in vjass if you don't know how it compiles it in jass however i do know and i can say this is the last update of this spell. Note this spell has no leaks and is completely fast as much as possible!

Version 1.6 Final - Last update improved speed decreased code size and make spell more configurable. It was by request of nerovesper to make it more configurable but i did increase speed a lot more and make code less in size. Its now at top speed and its last update i made for this spell.

Version 1.7 - Was a must update to make it 1.24 compatible! as well code is decreased and fast, using JNGPS.

Version 2.0 - Water Explosion Remasterd
- Totally recoded in zinc
- Fixed bug when units enter "water effect" area just after damage is dealt
- Caster now deals damage
- Fixed bug when caster did not deal damage, thats why dummy was used
Seems like UnitDamageTarget might not deal damage when unit casts/channels spell
- Primarely this is all fixed by adding timed area damage and g_once group which makes sure that units are damaged only once
- Now uses mini library

Version 2.1 - Recoded in Warcraft 3 v1.30
- Spell no longer requires JNGP
- Bug fixes, spell now tends to deal single damage per water explosion wave
- Alterd visuals, water shoots are shoot from far away from caster to closer range


Version 2.1b - Fixed desync sound bug




JASS:
// -------------------------------------------------------------------------
//                 *** Spell: Water Explosion
//                 *** Autor: Dark Dragon
//
//
// * Made on Warcraft III v1.30.4
//
// -------------------------------------------------------------------------
//                      *** Installation ***
//
// * Copy this trigger and DD Library to your map
// * Copy abilitie 'Water Explosion' to your map
// * Export and import 'WaterExplosion.mdx' from this map to your map (Credits: JetFangInferno)
// * Modify data below to your will, WEX_RAWCODE must be matched to your 'spell rawcode' in your map
// * Add water explosion to unit you want and edit below spell data to your needs
// * Enjoy!
// -------------------------------------------------------------------------


// -------------------------------------------------------------------------
//                  *** Main WaterExplosion library ***
//! zinc
library WaterExplosion requires DDLib
{

    //********************************************************************************
    //                        Edit following to your wish
    //********************************************************************************


    constant integer    WEX_RAWCODE         = 'WTex';    // Ability Water Explosion
    constant real     EXPLOSION_DELAY     = .5;        // How often explosions are created (in seconds)
    constant integer    WATER_SHOOTS       = 4;         // How much water shoots will happen, water sfx shoots
 constant real    INITIAL_EXPLOSION_OFFSET  = 50.;   // Extra offset to NEXT_EXPLOSION_OFFSET, on first water explosion
 constant real    NEXT_EXPLOSION_OFFSET   = 110.;   // How far does each next cycle explosion occure (linear damage/effect version)
 constant real    WATER_EXPLOSION_DENSITY   = 160.;   // Smaller numbers mean that explosions are closer together, it means how much of perimiter does it use (circular effect version)
 //constant real    EXPLOSION_ENHANCE_PERC   = 120.;   // This is correction of units being picked in percentage of NEXT_EXPLOSION_OFFSET, it means it will alternate area in which units are damaged
 constant boolean   DUMMY_DAMAGE     = false;   // Does dummy or caster deal damage?
 constant boolean   USE_TEXTTAG      = true;   // Enable or disable damage display

 constant integer   PROJECTILE_N     = 12;   // Missiles max number
 constant real    PROJECTILE_SIZE     = 1.3;   // Missiles size
    constant real     PROJECTILE_SPEED      = 750.;   // Missiles speed
    constant real     PROJECTILE_ARC        = 360.;   // Missiles max reached z
    constant real     PROJECTILE_RANGE      = 710.;   // Missiles max travel distance
 constant real     PROJECTILE_INIT_Z      = 0.;   // Missiles offset z
    
        // This are constant defined effects
    constant string    EXPLOSION_DAMAGE_EFFECT      = "Abilities\\Spells\\Other\\CrushingWave\\CrushingWaveDamage.mdl";
    constant string    EXPLOSION_DAMAGE_EFFECT_ATTACH_POINT  = "origin";
    constant real     EXPLOSION_DAMAGE_EFFECT_INTERVAL   = .33;
    constant real     EXPLOSION_DAMAGE_EFFECT_DURATION   = 4.;
    constant string    WATER_EXPLOSION_EFFECT       = "Objects\\Spawnmodels\\Naga\\NagaDeath\\NagaDeath.mdl";
    constant string    MISSILE_EFFECT               = "Abilities\\Weapons\\WaterElementalMissile\\WaterElementalMissile.mdl";
    constant string    MISSILE_SPAWN_EFFECT           = "war3mapImported\\WaterExplosion.mdx";
    constant real     MISSILE_SPAWN_EFFECT_SIZE       = .5;
    constant real     MISSILE_SPAWN_EFFECT_ANIMATION_SPEED  = 200.;

    private keyword waterexp;
 
 // *** Amount of damage dealed per level, if you use more or less than 3 levels, just delete or add additional lines in function below
 function WaterDamage(integer level) -> real {
  real damage[];

  // -----------------------------------------
  // *** edit this variables to your will
  damage[1] = 70.;
  damage[2] = 100.;
  damage[3] = 140.;
  damage[4] = 190.;
  damage[5] = 260.;
  // -----------------------------------------

  return damage[level];
 }

 // *** area of effect, that is max radius that spell will effect
 function WaterAoE(integer level) -> real {
  real aoe[];

  // -----------------------------------------
  // *** edit this variables to your will
  // you can use your custom aoe, but this uses n water explosions formula.
  //                 ˇˇ
  aoe[1] = INITIAL_EXPLOSION_OFFSET + NEXT_EXPLOSION_OFFSET*(4.);
  aoe[2] = INITIAL_EXPLOSION_OFFSET + NEXT_EXPLOSION_OFFSET*(4.);
  aoe[3] = INITIAL_EXPLOSION_OFFSET + NEXT_EXPLOSION_OFFSET*(5.);
  aoe[4] = INITIAL_EXPLOSION_OFFSET + NEXT_EXPLOSION_OFFSET*(5.);
  aoe[5] = INITIAL_EXPLOSION_OFFSET + NEXT_EXPLOSION_OFFSET*(6.);
  //                 ^^
  // -----------------------------------------

  return aoe[level];
 }
 
    function WaterMaxDamage(integer level) -> real {
        real max_damage[];

  // -----------------------------------------
  // *** edit this variables to your will
  // the max damage spell can deal per cast, use value of -1. to make spell have no limit of damage
  //
  max_damage[1] = 500.;
  max_damage[2] = 850.;
  max_damage[3] = 1100.;
  max_damage[4] = 1500.;
  max_damage[5] = 1600.;
  //                   ^^
  // -----------------------------------------

  return max_damage[level];

    }

 // *** Filtrate units to be damaged
 function DamageFilter(unit filterUnit, player casterOwner) -> boolean {
   return  !IsUnitType(filterUnit, UNIT_TYPE_DEAD)     &&
     IsUnitEnemy(filterUnit, casterOwner)       &&
     !IsUnitType(filterUnit, UNIT_TYPE_STRUCTURE)    &&
     !IsUnitType(filterUnit, UNIT_TYPE_MAGIC_IMMUNE)   &&
     !IsUnitType(filterUnit, UNIT_TYPE_ANCIENT)     &&
     !IsUnitType(filterUnit, UNIT_TYPE_FLYING)     &&
     !DDIsUnitWard(filterUnit)        &&
     BlzIsUnitSelectable(filterUnit)       &&
     !BlzIsUnitInvulnerable(filterUnit);
 }

 // ==============================================================================================================
 // *
 // *       *** Non-Editable code below ***
 // *
 // ==============================================================================================================

    //===============================================
    // Do not edit bellow if you don't know jass
    //===============================================


 // * Timed effect on unit
 struct timeffect {
  unit u;
  effect e;
  real timeout;

  static timeffect UnitData[];

  static method AddWaterEffect(unit u) {
   timeffect this;
   integer id = H2ID(u);
 
   if (UnitData[H2ID(u)] == p_null) {
    this = allocate();
    UnitData[id] = this;
    this.u = u;
    this.e = AddSpecialEffectTarget(EXPLOSION_DAMAGE_EFFECT, u, EXPLOSION_DAMAGE_EFFECT_ATTACH_POINT);
 
    DDStartTim(EXPLOSION_DAMAGE_EFFECT_INTERVAL, true, this, static method() {
     timeffect te = DDTimData();
 
     BlzPlaySpecialEffect(te.e, ANIM_TYPE_BIRTH);
     te.timeout -= EXPLOSION_DAMAGE_EFFECT_INTERVAL;
     if (te.timeout <= 0. || IsUnitType(te.u, UNIT_TYPE_DEAD)) {
      DestroyEffect(te.e);
      UnitData[H2ID(te.u)] = p_null;
      te.e = null;
      te.u = null;
      te.destroy();
      DDQuitTim();
     }
    });
   } else {
    this = UnitData[id];
   }
 
   this.timeout = EXPLOSION_DAMAGE_EFFECT_DURATION;
  }
 }

 // *** Group of missiles
 struct nexus {
  ddeffect e[PROJECTILE_N];
  effect se[PROJECTILE_N];
  real dx[PROJECTILE_N], dy[PROJECTILE_N];
  real wz, dist, max_dist;
  real offset_range;

  // *** Calculates missile z motion, default elliptical motion
  method MissileZ() -> real {
   return ((-4.*PROJECTILE_ARC/Pw_2(max_dist)) * Pw_2(dist-offset_range)) + PROJECTILE_ARC;
   //return SquareRoot( Pw_2(PROJECTILE_ARC) * (1. - Pw_2(((dist-offset_range)/(max_dist/2.))-1.)) );
  }
 }

 //--------------------------------------------------
    // *** WaterExplosion main struct ***
    struct waterexp {
        // Objects
        unit   caster;
  unit   dummy;
  real   x, y;
  player   owner;
        integer  level;
        integer  shoots;
        real   radius;
  real  dmg, cdmg, max_dmg;
  fogmodifier fm;

    
    
  // ------------------------------------------
        //     # Methodes
  // ------------------------------------------
    
  // ------------------------------------------
        //     *** Write constructor ***
        static method create() -> waterexp {
            waterexp this = allocate();
        
            // *** Assign members ***
            this.caster   = GetTriggerUnit();
            this.owner    = GetOwningPlayer(this.caster);
            this.level    = GetUnitAbilityLevel(this.caster, WEX_RAWCODE);
            this.radius   = INITIAL_EXPLOSION_OFFSET;
            this.x     = GetUnitX(this.caster);
            this.y     = GetUnitY(this.caster);
            this.shoots   = 0;
   this.cdmg   = 0.;
   this.dmg   = WaterDamage(level);
   this.max_dmg  = WaterMaxDamage(level);
   this.fm    = CreateFogModifierRadius(this.owner, FOG_OF_WAR_VISIBLE, this.x, this.y, WaterAoE(this.level) + NEXT_EXPLOSION_OFFSET, true, false);
   FogModifierStart(this.fm);
 
   static if (DUMMY_DAMAGE) {
    this.dummy = DDLoadDummy();
    SetUnitOwner(dummy, owner, false);
    SetUnitX(this.dummy, this.x);
    SetUnitY(this.dummy, this.y);
   }
        
            return (this);
        }
    


        // ------------------------------------------
        //     *** Write destructor ***
        method destroy() {
   static if (DUMMY_DAMAGE)
    DDRecycleDummy(this.dummy);
 
   FogModifierStop(this.fm);
   DestroyFogModifier(this.fm);
   this.fm = null;
   this.caster = null;
   deallocate();
        }
    }

 
    //===========================================================================
    //               **** Init water expl ****
    //===========================================================================
    function onInit() {
        trigger t = CreateTrigger();
        TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT);
        TriggerAddCondition(t, Condition(function() -> boolean {
            // Main Condition
            if (GetSpellAbilityId() != WEX_RAWCODE)
                // *** Skip remainig code
                return false;
 
            // *** Condition passed
            DDStartTimInst(EXPLOSION_DELAY, true, waterexp.create(), function() {
    // -------------------------------------
    // *** locals ***
    waterexp we = DDTimData();
    nexus n;
    integer i, max_i;
    real rad;
    // -------------------------------------
 
    // ================================================================================================
    //        *** Load water shoots ***
    // ================================================================================================
    if ( we.shoots < WATER_SHOOTS ) {
     // *** Increase shoots ***
     we.shoots += 1;
     n = nexus.create(); // Missiles
     n.dist = we.radius;
     n.max_dist = PROJECTILE_RANGE-we.radius;
     n.offset_range = we.radius+(n.max_dist/2.);
     n.wz = BlzGetUnitZ(we.caster);
 
     // *** Add enum graphics ***
     for(i=0; i < PROJECTILE_N; i+=1) {
      // *** Do calcs ***
      rad = i*(2.*bj_PI/PROJECTILE_N);
      // *** Add missiles ***
      n.dx[I] = Cos(rad);
      n.dy[I] = Sin(rad);
      n.e[I] = ddeffect.create(MISSILE_EFFECT, we.x + PROJECTILE_RANGE * n.dx[I], we.y + PROJECTILE_RANGE * n.dy[I], rad, PROJECTILE_SIZE);
      n.dx[I] *= -DD_INTERVAL*PROJECTILE_SPEED;
      n.dy[I] *= -DD_INTERVAL*PROJECTILE_SPEED;
    
      n.se[I] = AddSpecialEffect(MISSILE_SPAWN_EFFECT, n.e[I].X, n.e[I].Y);
      BlzSetSpecialEffectScale(n.se[I], MISSILE_SPAWN_EFFECT_SIZE);
      BlzSetSpecialEffectTimeScale(n.se[I], MISSILE_SPAWN_EFFECT_ANIMATION_SPEED/100.);
      BlzPlaySpecialEffect(n.se[I], ANIM_TYPE_STAND);
     }
   
     DDStartTim(200./MISSILE_SPAWN_EFFECT_ANIMATION_SPEED, false, n, function() {
      nexus n = DDTimData();
      integer i;
    
      for(i=00; i < PROJECTILE_N; i+=01) {
       DestroyEffect(n.se[I]);
       n.se[I] = null;
      }
    
      if (n.e[00] == p_null)
       n.destroy();
      DDQuitTim();
     });
   
     // ------------------------------------------------------------
     // *** Missile motion
     DDStartTim(DD_INTERVAL, true, n, function() {
      nexus n = DDTimData();
      integer i;
    
      // *** vars change apply ***
      n.dist += DD_INTERVAL*PROJECTILE_SPEED;
    
      // *** check if we still have a way to go ***
      if (n.dist < PROJECTILE_RANGE) {
       // *** Do the loop and move missiles
       for(i=0; i < PROJECTILE_N; i+=1) {
        n.e[I].PositionZ(n.e[I].X + n.dx[I], n.e[I].Y + n.dy[i],
              n.wz + PROJECTILE_INIT_Z + n.MissileZ());
       }
      } else {
       // *** Clear missiles and end motion ***
       for(i=0; i < PROJECTILE_N; i+=1) {
        n.e[i].destroy();
        n.e[i] = p_null;
       }
       if (n.se[00] == null)
        n.destroy();
       DDQuitTim();
      }
     });
    }
  
    // ================================================================================================
    //       *** Start Damage and explosions ***
    // ================================================================================================
  
    // ***
    // *** Increase distance
    // ***
    we.radius += NEXT_EXPLOSION_OFFSET;
    max_i = R2I(2.*we.radius*bj_PI / WATER_EXPLOSION_DENSITY);
  
    // -------------------------------------------
    // Loop ( loads water explosion effects )
    for(i=0; i < max_i; i+=1) {
     rad = (i * 2.*bj_PI) / max_i;
     DestroyEffect( AddSpecialEffect(WATER_EXPLOSION_EFFECT,
             we.x + we.radius * Cos( rad ),
             we.y + we.radius * Sin( rad )) );
    }
  
    // *** Start periodic scan for damage
    DDGroupFilterArea(we.x, we.y, we.radius+NEXT_EXPLOSION_OFFSET, we, function() -> boolean {
     waterexp we = DDGFilterData();
     unit f = GetFilterUnit();
     real life, damage = we.dmg;
     effect e;
   
     // Target unit has to be between target radius and radius - explosion offset
     if (DDHypot(GetUnitX(f)-we.x, GetUnitY(f)-we.y) >= Pw_2(we.radius-NEXT_EXPLOSION_OFFSET) && DamageFilter(f, we.owner)) {
      if (we.max_dmg != -1.) {
       life = GetWidgetLife(f);
     
       if (we.max_dmg-we.cdmg < damage)
        damage = we.max_dmg-we.cdmg;
      
       if (life < damage)
        damage = life;
     
       we.cdmg += damage;
      }
    
      static if (DUMMY_DAMAGE)
       DDSpellDamage(we.dummy, f, we.dmg);
      else
       DDSpellDamage(we.caster, f, we.dmg);
    
      static if (USE_TEXTTAG)
       if (damage >= 1.)
        DDNewTextTagUnit(f, "+"+I2S(R2I(damage)), 4., 10., 90., 88., 20.);
    
      timeffect.AddWaterEffect(f);
     }
     f = null;
     return false;
    });
  
    // ===========================================
    // * End when distance is out of area
    if (we.radius+1. > WaterAoE(we.level)) {
     static if (USE_TEXTTAG)
      DDNewTextTagUnit(we.caster, I2S(R2I(we.cdmg))+" damage dealt!", 5., 27., 84., 10., 20.);
     we.destroy();
     DDQuitTim();
    }
  
    // ===============================
    // ---- End main timer ------
    // ===============================
   });
          
            return false; // End Trigger condition
        }));
 
  // End onInit
    }
 
}

//! endzinc

// ============================================================================================
// Lua generation of objects!
// Change me from /* to // and then back to /* after saving and reopening the map
    // |
    // ˇ
      /*
    
// Credits: PurgeandFire for lua tutorial
//! externalblock extension=lua ObjectMerger $FILENAME$
     //! i -- ===================================================
   
     //! i WATER_EXPLOSION_ABIL_ID               = "WTex"
  
     //! i -- ===================================================
  
     //! i setobjecttype("abilities")
     //! i createobject("AHtc", WATER_EXPLOSION_ABIL_ID)
     //! i makechange(current,"anam", "Water Explosion")
     //! i makechange(current,"alev", "5")
     //! i makechange(current,"abpx", "3")
  //! i makechange(current,"arpx", "3")
  //! i makechange(current,"aart", "ReplaceableTextures\\CommandButtons\\BTNCrushingWave.blp")
  //! i makechange(current,"arar", "ReplaceableTextures\\CommandButtons\\BTNCrushingWave.blp")
  //! i makechange(current,"ahky", "W")
  //! i makechange(current,"arhk", "W")
  //! i makechange(current,"acat", nil)
  //! i makechange(current,"aret", "Learn |cff1a8cd0W|rater Explosion - [|cff0080c0Level "..string.char(37).."d|r]")
  //! i makechange(current,"arut", "|cff008040Deals damage in area around caster.|r|n|n|cff0080ffLevel 1|r -|cff80ffff Each explosion deals 70 damage, up to max 500 damage, in 470 aoe|r. |n|cff0080ffLevel 2|r - |cff80ffffEach explosion deals 100 damage, up to max 850 damage, in 470 aoe|r. |cff80ffff|n|r|cff0080ffLevel 3|r -|cff80ffff Each explosion deals 140 damage, up to max 1100 damage, in 610 aoe|r.|cff80ffff|n|r|cff0080ffLevel 4|r -|cff80ffff Each explosion deals 190 damage, up to max 1500 damage, in 610 aoe|r.|cff80ffff|n|r|cff0080ffLevel 5|r -|cff80ffff Each explosion deals 260 damage, up to max 1600 damage, in 610 aoe|r.")
 
   
     //! i local i = 0
     //! i local dmg = { "70", "100", "140", "190", "260" }
  //! i local maxdmg = { "500", "850", "1100", "1500", "1600" }
  //! i local aoe = { "470", "470", "610", "610", "610" }
     //! i for i=1, 5 do
        //! i local si = tostring(i)
        //! i makechange(current,"Htc1",si,"0.")
        //! i makechange(current,"aare",si,"0.")
        //! i makechange(current,"Htc3",si,"0.")
  //! i makechange(current,"Htc4",si,"0.")
  //! i makechange(current,"abuf",si,nil)
        //! i makechange(current,"ahdu",si,".01")
        //! i makechange(current,"adur",si,".01")
        //! i makechange(current,"amcs",si,tostring(100 + 25*i))
        //! i makechange(current,"atp1",si,"|cff1a8cd0W|rater Explosion - [|cffffcc00Level "..si.."|r]")
  //! i makechange(current,"aub1",si,"|cff1a8cd0Deals "..dmg[i].." damage per explosion, up to max "..maxdmg[i].." damage, in "..aoe[i].." aoe.|r")
 
     //! i end
//! endexternalblock

       */
    // ^
    // |
// Change me from */ to // and then back to */ after saving and reopening the map
// ============================================================================================
[/I][/I][/I][/I][/I][/I][/I][/I][/I][/I][/I][/I][/I][/I][/I][/I][/I][/I][/I]


Credits: JetFangInferno

If you have any future questions ask here.
Have Fun!

~Dark Dragon


Keywords:
Water, Explosion, Spells, any, other, DD, wave, nova
Contents

Spell - Water Explosion (Map)

Reviews
Diablo-dk: [Approved] The spell is MUI and works fine ingame.
Level 7
Joined
Dec 19, 2008
Messages
276
Huh, but why old spell works for me in 1.24?

Don't see reason to update, it uses import .j file now


Ofc you got many usefull functions in you library, but they usefull for people who don't know how to make them or just for lazy jass'ers.
 
Level 15
Joined
Jul 19, 2007
Messages
618
Huh, but why old spell works for me in 1.24?

Don't see reason to update, it uses import .j file now


Ofc you got many usefull functions in you library, but they usefull for people who don't know how to make them or just for lazy jass'ers.

hmm i am not sure what happend with old spell, but i had to make it 1.24+ compatible and fix some old stuff. As well this new one has many more features and so on...

anyway ty for comment and greets!
 
Top