• 🏆 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!

Ex Hydratia [Paladon]

-=Ex Hydratia=-
by (c)Paladon

The spell "Ex Hydratia" is coded in vJass.
It needs the JassNewGenPack in order to be modified/used properly.
You can get the JNGP here: Click
Of course, this spell is MUI and such stuff.

Description:
Dehydrates a target. For each interval of the dehydration,
the target takes 30 damage. The hero uses the gained
water to form a shield, increasing the armour by 1 for
each interval absorbed. As soon as the hero absorbed
6 intervals, he completely absorbs the shield of water
and regenerates 15 hitpoints and mana per absorbed
interval actively used to form the shield.

Level 1 - Dehydrates the target for 3 intervals.
Level 2 - Dehydrates the target for 4 intervals.
Level 3 - Dehydrates the target for 5 intervals.

Import
To import this spell into your very own map, you need to work with the JNGP named above.
For this spell, you need the "Ey Hydratia" trigger (below the Readme)
as well as the DUMMY.mdx out of the Import Editor, the WaterEssence.mdx and the Dummy unit out of the Object Editor.
Furthermore, you need the spells "Ex Hydratia [Spell]" and "Ex Hydratia [Armour]" which are located in the Object Editor as well.

Import and copy all these things into your map and adjust the settings in the header of the
"Ex Hydratia" trigger to your own needs.

Imported Resources
DUMMY.mdx
WaterEssence.mdx
~a modified "WaterElementalMissile" (without the long light blue beam)

Other stuff
The usual ownership conditions apply.
~Paladon

JASS:
//!*********************************EX HYDRATIA [Paladon]******************************!\\
//**************************************************************************************\\
scope ExHydratia initializer SetEvent
    native UnitAlive takes unit id returns boolean
    globals
    //!**********************************THE SETTINGS*****************************!\\
    //*****************************************************************************\\
    // General Settings
        private constant integer SPELL_ID  = 'A000' // Spell's rawcode
        private constant integer EFFECT_ID = 'A001' // Armour effect's rawcode
        private constant integer DUMMY_ID  = 'n000' // Dummy's rawcode
        private constant real    TIMER     = 0.01   // Interval of the timer
        
        private constant real    INTERVAL_TIMER         = 0.5 // Time between the intervals of dehydration
        private constant integer INIT_DEHYDRATION_COUNT = 3 // Intervals (Level 1)
        private constant integer INCR_DEHYDRATION_COUNT = 1 // Intervals added per each level (Level 2 and higher)
        
        private constant string SFX_WATER_MISSILE      = "war3mapImported\\WaterEssence.mdl" // Model of the missiles
        private constant string SFX_DEHYDRATION_EFFECT = "Abilities\\Spells\\Other\\CrushingWave\\CrushingWaveDamage.mdl" // Model of the "death" animation displayed upon creation of the missiles
        private constant string SFX_HEAL_EFFECT        = "Abilities\\Spells\\NightElf\\Blink\\BlinkTarget.mdl" // Model of the "death" animation displayed upon impact on enemied units
        
        private constant string DUMMY_ATTACH = "chest"  // Attachment point of the effects on the "Dummy" model (Missile)
        private constant string ENEMY_ATTACH = "origin" // Attachment point of the effects on the enemy (Dehydration)
        private constant string HEAL_ATTACH  = "origin" // Attachment point of the effects on your hero (Heal)
        
        // Orbital Settings
        private constant real ORBITAL_SPEED_MIN  = 5.55 // Defines the minimal orbital speed
        private constant real ORBITAL_SPEED_MAX  = 7.55 // Defines the maximal orbital speed
        
        private constant real ORBITAL_ANGLE_SPIN_MAX  = -0.65 // Defines the minimal speed of the spin of the orbital movement 
        private constant real ORBITAL_ANGLE_SPIN_MIN  = 0.65  // Defines the maximal speed of the spin of the orbital movement 
        
        private constant real ORBITAL_SIZE_MIN   = 110.   // Defines the minimal size of the orbital movement of a single orb
        private constant real ORBITAL_SIZE_MAX   = 150.   // Defines the maximal size of the orbital movement of a single orb
        
        private constant real ORBITAL_HEIGHT_MIN = 0.    // Defines partially the height of the orbital (minimal).
        private constant real ORBITAL_HEIGHT_MAX = 60.    // Defines partially the height of the orbital (maximal).
        //! I recommend playing around with these values for ground units to find something fitting. For air units, i recommend for the height simply "0".
        
        // Damage / Heal Settings
        private constant real INIT_DAMAGE_DEHYDRATION = 30. // Defines the damage dealt per dehydration interval (lvl 1)
        private constant real INCR_DAMAGE_DEHYDRATION = 0.  // Defines the damage added per level (lvl 2 and up) for each dehydration interval
        
        private constant real INIT_HEAL_HITPOINTS = 15. // Defines the amount of hitpoints healed per hydration counter (see below) by reaching the limit (lvl 1)
        private constant real INCR_HEAL_HITPOINTS = 0.  // Defines the amount of hitpoints healed added for level 2 and higher
        
        private constant real INIT_HEAL_MANA      = 15. // Defines the amount of mana healed per hydration counter (see below) by reaching the limit (lvl 1)
        private constant real INCR_HEAL_MANA      = 0.  // Defines the amount of mana healed added for level 2 and higher
        
        // Misc Settings
        private constant integer TRIGGER_HYDRATION_COUNTER = 6   // Defines the "amount" of water needed to trigger the hydration
        private constant real    MISSILE_SPEED           = 5.5 // The speed of a released missile outside the orbital
        
        private constant attacktype ATTACK_TYPE = ATTACK_TYPE_NORMAL   // The attacktype of the damage dealt
        private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_NORMAL   // The damagetype of the damage dealt
        private constant weapontype WEAPON_TYPE = null                 // The weapontype of the damage dealt
         
//!*******************************END OF THE SETTINGS*************************!\\
//**Do not modify anything below this line unless you know what you are doing**\\
//!***************************************************************************!\\
        private real array R
    endglobals

    private struct S
        private unit    hero
        private unit    missile
        private unit    target
        private integer state
        private integer level
        
        private real d1 // Distance reference
        private real angle // Not always used as angle but in cases of reference as the corresponding rad/deg
        private real t1 // The first time counter
        private real t2 // The second time counter
        
        private real x // coordinate for the orbital
        private real y // coordinate for the orbital
        private real z // coordinate for the orbital
        
        private real OSp // Orbital Speed
        private real OAS // Orbital Angle Spin
        private real OSi // Orbital Size
        private real OHe // Orbital Height
        private real OEx // Orbital Value Reference (also used for directional purposes)
        
        private effect sfx
        
        private static S array indx
        private static integer counter = 0
        private static timer   time    = CreateTimer()
            
            static method Execution takes nothing returns nothing
                local S d
                local integer i = 0
                local integer l = 0
                local integer a = 0
                local integer b = 0
                local unit u
                loop
                    exitwhen i >= S.counter
                    set d = S.indx[i]
                    
                    if d.state == 0 then
                        // Remove & Recycle
                        call DestroyEffect(d.sfx)
                        call RemoveUnit(d.missile)
                        set  d.hero    = null
                        set  d.missile = null
                        set  d.target  = null
                        set  d.sfx     = null
                        call d.destroy()                        
                        set  S.counter = S.counter - 1
                        set  S.indx[i] = d.indx[S.counter]                        
                        set  i = i - 1     
                    elseif d.state == 2 or d.state == 4 then
                        // Launch and move the missile
                        
                        // X/Y Movement
                        if d.state == 4 then
                            set R[0] = GetUnitX(d.target)
                            set R[1] = GetUnitY(d.target)
                        else
                            set R[0] = d.x
                            set R[1] = d.y
                        endif
                        set R[2] = GetUnitX(d.missile)
                        set R[3] = GetUnitY(d.missile)
                        set R[4] = Atan2(R[1]-R[3],R[0]-R[2])   
                        call SetUnitX(d.missile,R[2] + MISSILE_SPEED * Cos(R[4]))
                        call SetUnitY(d.missile,R[3] + MISSILE_SPEED * Sin(R[4]))
                        call SetUnitFacing(d.missile,R[4]*bj_RADTODEG)
                        
                        // Z Movement
                        set R[0] = R[2] - R[0]
                        set R[1] = R[3] - R[1]
                        set R[2] = SquareRoot(R[0]*R[0]+R[1]*R[1])
                        if d.state == 4 then
                            set R[0] = GetUnitFlyHeight(d.target)
                        else
                            set R[0] = d.z
                        endif
                        set R[1] = GetUnitFlyHeight(d.missile)
                        set R[0] = R[0]-R[1]
                        set R[0] = R[0]/(R[2]/MISSILE_SPEED)
                        call SetUnitFlyHeight(d.missile,R[1]+R[0],0)

                        if R[2] <= MISSILE_SPEED then // Missile reached target
                            if d.state == 2 then
                                set d.state = 3
                                set l = GetUnitAbilityLevel(d.hero,EFFECT_ID)
                                if l == 0 then
                                    call UnitAddAbility(d.hero,EFFECT_ID)
                                elseif l+1 >= TRIGGER_HYDRATION_COUNTER then // Trigger Heal
                                    call UnitRemoveAbility(d.hero,EFFECT_ID)
                                    call DestroyEffect(AddSpecialEffectTarget(SFX_HEAL_EFFECT,d.hero,HEAL_ATTACH))
                                    set u = d.hero
                                    set a = d
                                    set b = 0
                                    loop
                                        exitwhen b >= S.counter
                                        set d = S.indx[b]
                                        if d.hero == u and d.state == 3 then
                                            set d.state   = 4
                                            set d.target  = u
                                        endif
                                        set b = b + 1
                                    endloop
                                    set d = a
                                else // Simple increase
                                    call SetUnitAbilityLevel(d.hero,EFFECT_ID,l+1)
                                endif
                            else // Heal effect, state = 4
                                call SetWidgetLife(d.hero,INIT_HEAL_HITPOINTS + (INCR_HEAL_HITPOINTS * d.level) + GetWidgetLife(d.hero))
                                call SetUnitState(d.hero, UNIT_STATE_MANA,INIT_HEAL_MANA + (INCR_HEAL_MANA * d.level) + GetUnitState(d.hero,UNIT_STATE_MANA))
                                set d.state = 0
                            endif
                        elseif not UnitAlive(d.hero) then
                            set d.state = 0
                        endif
                        
                    elseif d.state == 1 then // Creates a missile and triggers dehydration on the enemy
                        set d.t1 = d.t1 + TIMER
                        if not UnitAlive(d.target) or not UnitAlive(d.hero) then
                            set d.state = 0
                        elseif d.t1 >= d.t2 then
                            set d.t1 = 0
                            // init single dehydration
                            set R[0] = GetUnitX(d.target)
                            set R[1] = GetUnitY(d.target)
                            set d.missile = CreateUnit(GetOwningPlayer(d.hero),DUMMY_ID,R[0],R[1],0)
                            call UnitAddAbility(d.missile,'Arav')
                            call UnitRemoveAbility(d.missile,'Arav')  
                            
                            // The following 4 lines are necessary since an initial setting cannot work
                            call SetUnitPathing(d.missile,false)
                            call SetUnitX(d.missile,R[0])
                            call SetUnitY(d.missile,R[1])
                            call SetUnitFlyHeight(d.missile,GetUnitFlyHeight(d.target),0)
                            
                            call DestroyEffect(AddSpecialEffectTarget(SFX_DEHYDRATION_EFFECT,d.target,ENEMY_ATTACH))
                            call UnitDamageTarget(d.hero,d.target,INIT_DAMAGE_DEHYDRATION + (INCR_DAMAGE_DEHYDRATION * d.level), true, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
                            
                            // Prepares the data for the movement and orbital shape
                            set d.sfx = AddSpecialEffectTarget(SFX_WATER_MISSILE,d.missile,DUMMY_ATTACH)
                            set d.OAS = GetRandomReal(ORBITAL_ANGLE_SPIN_MIN,ORBITAL_ANGLE_SPIN_MAX)*bj_DEGTORAD
                            set d.state = 2
                        endif
                    endif
                    if d.state == 1 or d.state == 2 or d.state == 3 then
                        set d.angle = d.angle + d.OAS
                        set d.d1    = d.d1 + d.OSp
                        set R[0]    = (d.d1/d.OEx)*bj_DEGTORAD
                        set R[1]    = d.OSi*Sin(R[0])
                        set R[2]    = d.angle*bj_DEGTORAD
                        set d.x = GetUnitX(d.hero)+R[1]*Cos(R[2])
                        set d.y = GetUnitY(d.hero)+R[1]*Sin(R[2])
                        set d.z = (d.OSi-d.OHe)*Cos(R[0])+d.OHe+GetUnitFlyHeight(d.hero)
                        if d.state == 3 then
                            call SetUnitX(d.missile,d.x)
                            call SetUnitY(d.missile,d.y)
                            call SetUnitFlyHeight(d.missile,d.z,0) 
                            call SetUnitFacing(d.missile,d.angle)
                        endif
                        // Since the orbs move with a circular movement, we can reset the angle.
                        if d.angle >= 360. then
                            set d.angle = d.angle - 360.
                        endif

                        if not UnitAlive(d.hero) then
                            set d.state = 0
                        endif
                    endif
                    set i = i + 1
                endloop
                set u = null
                set i = 0
                if S.counter == 0 then
                    call PauseTimer(S.time)
                endif 
            endmethod
            
            static method SetStruct takes unit hero, unit target, real amount, real specific, real time, integer level returns nothing
                // This method mainly inits the orbital movement data and determines the movement shape
                local S d = S.allocate()
                set d.hero   = hero
                set d.target = target
                set d.level  = level
                set d.state  = 1
                set d.d1     = 0
                set d.angle  = (360/amount) * specific
                set d.t1     = 0
                set d.t2     = time
                set d.OSp    = GetRandomReal(ORBITAL_SPEED_MIN,ORBITAL_SPEED_MAX)
                set d.OSi    = GetRandomReal(ORBITAL_SIZE_MIN,ORBITAL_SIZE_MAX)
                set d.OAS    = 5. // constant to create the "waterwave" effect when the missile is moving
                set d.OHe    = GetRandomReal(ORBITAL_HEIGHT_MIN,ORBITAL_HEIGHT_MAX)
                set d.OEx    = d.OSi/90
                if S.counter == 0 then
                    call TimerStart(S.time,TIMER,true,function S.Execution)
                endif
                set S.indx[S.counter] = d
                set S.counter = S.counter + 1
            endmethod
            
            static method Dehydrate takes nothing returns nothing
                local unit    u = GetTriggerUnit()
                local integer l = GetUnitAbilityLevel(u,SPELL_ID)-1
                set R[0] = 0
                set R[1] = INIT_DEHYDRATION_COUNT + (INCR_DEHYDRATION_COUNT * l)
                loop
                    exitwhen R[0] == R[1]
                    call S.SetStruct(u,GetSpellTargetUnit(),R[1],R[0],INTERVAL_TIMER*R[0],l)
                    set R[0] = R[0] + 1
                endloop
                set u = null
            endmethod
    endstruct
    
    private function Trigger takes nothing returns boolean
        if GetSpellAbilityId() == SPELL_ID then
            call S.Dehydrate()
        endif
        return true
    endfunction
    
    private function SetEvent takes nothing returns nothing
        local trigger t = CreateTrigger()
        
        // Preloading the armour effect
        local unit u = CreateUnit(Player(15),DUMMY_ID,0,0,0)
        call UnitAddAbility(u,EFFECT_ID)
        call UnitRemoveAbility(u,EFFECT_ID)
        call RemoveUnit(u)
        
        // Trigger settings
        call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(t, Condition(function Trigger))
        
        // Preloading effects
        call Preload(SFX_WATER_MISSILE)
        call Preload(SFX_DEHYDRATION_EFFECT)
        call Preload(SFX_HEAL_EFFECT)
        call PreloadStart()
        set u = null
    endfunction
endscope

Keywords:
Water, Sea, Ocean, Naga, Element, Blue, Sky, Magic, Spell, Buff, Heal, Life, Mana, Atlantis, Riff, Ice, Essence, Arcane, War, Mage, Caster, Summon,
Contents

Ex Hydratia (Map)

Reviews
17:57, 4th Jan 2010 The_Reborn_Devil: The coding looks very good and the effects are very nice. Keep up the good work ^^ Status: Approved Rating: Recommended

Moderator

M

Moderator

17:57, 4th Jan 2010
The_Reborn_Devil:
The coding looks very good and the effects are very nice.
Keep up the good work ^^

Status: Approved
Rating: Recommended
 
Level 22
Joined
Nov 14, 2008
Messages
3,256
Uses a part of the orbital code to determine the shape of the shield :p
Sadly, the orbital shape is not exactly visible with the current model used, but if you change the missile's model, you'll see the shape ;)

haha couldn't you just skip the water idea and just change to another element and pick a better model so you can see the "shield" better XD
 
Level 14
Joined
Nov 18, 2007
Messages
1,084
Seems like an interesting spell.
I've only checked the code you posted online and I noticed some things:

  • Instead of using SetUnitState(d.hero, UNIT_STATE_LIFE, HitpointHeal + GetUnitState(d.hero,UNIT_STATE_LIFE)), you could have used SetWidgetLife(d.hero,HitpointHeal+GetWidgetLife(d.hero)This applies to other instances of SetUnitState when you're using it for UNIT_STATE_LIFE.
  • I think you can use the native UnitAlive to check if a unit's alive or not with jasshelper. Just put native UnitAlive takes unit id returns boolean at the top of your code and use that function instead.
  • Why did you do set R[2] = GetUnitAbilityLevel(u,SpellID) - 1? Couldn't you have just used GetUnitAbilityLevel(u,SpellID) - 1 by itself?
  • Am I being blind or did you never used "integer level" in your SetStruct?
  • Not a big concern but for local unit u = CreateUnit(Player(0),DummyID,0,0,0), you may want to use Player(15) instead because it removes the credit that Player(0) would get from "Units produced."
  • Another very small issue is that you may want to capitalize all global variables that are constants.
I'll edit this post when I download and test it for myself.

Edit: The effects were visually appealing. The idea itself was also creative.

I rechecked the code and I have a few more comments.

  • I think you should just use null for Weapon Type.
  • This is not absolutely necessary but you may want to make some of the effects of the spell, such as the healing and damaging effects, configurable by level.
Other than that, I think this spell was very cool, both visually and in its execution, and clever with the movements of the orb.
 
Last edited:
Level 14
Joined
Nov 18, 2007
Messages
1,084
Good to see that you fixed most of the issues that I mentioned.
There's just a little minor issues left:

  • When you preload the DUMMY_ID unit, you probably should use Player(15). Otherwise, Player(0) might have a small random vision spot in the minimap and will have an extra unit accounted for in the score screen.
  • Instead of having
    JASS:
    private constant real INIT_HEAL_HITPOINTS = 15.
    and
    JASS:
     private constant real INCR_HEAL_HITPOINTS = 0.
    make a
    JASS:
    private function HealHitpoints takes integer lvl returns real
       return 15.
    endfunction
    In your healing function, replace the INIT_HEAL_HITPOINTS + (INCR_HEAL_HITPOINTS * d.level) + GetWidgetLife(d.hero) with HealHitpoints(d.level) + GetWidgetLife(d.hero). This goes the same for the damaging and mana effects.
  • The WEAPON_TYPE variable doesn't seem to be really necessary. But I guess that's up to the user's choice.
  • You forgot to some variables to null. (The unit u variable in Dehydrate method and the special effect sfx variable used by the S struct.)
Other than that, the code is well-done.
 
Level 25
Joined
Jun 5, 2008
Messages
2,572
JASS:
private function HealHitpoints takes integer lvl returns real
   return 15
endfunction

Actualy i saw a lot of times suggestions that the spell makers use functions like this instead of variables, but i think it is far more user friendly and customizable the way it is :p

Also WEAPON_TYPE variable is there also for easy change of the spell because if you haven't forgoten this is for public purpose, it should be easily customizable, it shouldn't be strict with the effects and the spell should be easily configurable.

At least i think so, or are we starting to make these spells for ourselves? :p
 
Top