• 🏆 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!
  • 🏆 Hive's 6th HD Modeling Contest: Mechanical is now open! Design and model a mechanical creature, mechanized animal, a futuristic robotic being, or anything else your imagination can tinker with! 📅 Submissions close on June 30, 2024. Don't miss this opportunity to let your creativity shine! Enter now and show us your mechanical masterpiece! 🔗 Click here to enter!

[JASS] MUI Death Coil

Status
Not open for further replies.
Level 4
Joined
Nov 27, 2012
Messages
85
Hello, haven't touched the editor in a while so I'm a little dusty. I've never had to create a MUI spell before because I usually just use local variables, so it's something out of my knowledge. Just asking for a hand to get started.

I have this custom triggered Death Coil, (so that it can heal non-Undead units)
Basically the animation is substituted with a unit that deals the damage when this unit touches the target. Problem is when it's cast a 2nd time, the existing unit is replaced with the new unit.

Is hash table the way to go? What's indexing?
Link to good tutorial?

Trigger to initiate the cast
JASS:
function Trig_Death_Coil_Conditions takes nothing returns boolean
    return GetSpellAbilityId()=='DanW' or GetSpellAbilityId()=='DanE' or GetSpellAbilityId()=='DanR' 
endfunction

function Trig_Death_Coil_Precast_Actions takes nothing returns nothing
    set udg_unit_deathcoil[0]=GetTriggerUnit()
    set udg_unit_deathcoil[1]=GetSpellTargetUnit()
    
    if IsUnitAlly(udg_unit_deathcoil[1],GetOwningPlayer(udg_unit_deathcoil[0]))==false then
        if IsUnitType(udg_unit_deathcoil[1],UNIT_TYPE_MAGIC_IMMUNE)==true then
            call IssueImmediateOrder(udg_unit_deathcoil[0],"stop")                                                                      
        endif
    endif
endfunction

function Trig_Death_Coil_Actions takes nothing returns nothing
    set udg_unit_deathcoil[2]=CreateUnit(GetOwningPlayer(udg_unit_deathcoil[0]),'e006',GetUnitX(udg_unit_deathcoil[0]),GetUnitY(udg_unit_deathcoil[0]),GetUnitFacing(udg_unit_deathcoil[0]))
    set udg_integer_deathcoil=GetUnitAbilityLevel(udg_unit_deathcoil[0],GetSpellAbilityId())

    call UnitApplyTimedLife(udg_unit_deathcoil[2],'BTLF',3.00)
    call AttachSoundToUnit(gg_snd_DeathCoilMissileLaunch1,udg_unit_deathcoil[0])
    call SetSoundVolume(gg_snd_DeathCoilMissileLaunch1,127)
    call StartSound(gg_snd_DeathCoilMissileLaunch1)
  //  call KillSoundWhenDone(gg_snd_DeathCoilMissileLaunch1)

    call TriggerRegisterUnitInRange(gg_trg_Death_Coil_Hit,udg_unit_deathcoil[2],50.00,null)
    call EnableTrigger(gg_trg_Death_Coil_Timer)
    call EnableTrigger(gg_trg_Death_Coil_Hit)
    call EnableTrigger(gg_trg_Death_Coil_Target_Dies)
endfunction
//===========================================================================
function InitTrig_Death_Coil takes nothing returns nothing
    local trigger t=CreateTrigger()
    local integer i=0
    
    set gg_trg_Death_Coil=CreateTrigger()
    loop
        exitwhen i==bj_MAX_PLAYER_SLOTS 
        call TriggerRegisterPlayerUnitEvent(gg_trg_Death_Coil,Player(i),EVENT_PLAYER_UNIT_SPELL_CAST,null)
        call TriggerRegisterPlayerUnitEvent(t,Player(i),EVENT_PLAYER_UNIT_SPELL_EFFECT,null)
        set i=i+1
    endloop
                                                                           
    call TriggerAddCondition(gg_trg_Death_Coil,Condition(function Trig_Death_Coil_Conditions))
    call TriggerAddAction(gg_trg_Death_Coil,function Trig_Death_Coil_Precast_Actions)
    
    call TriggerAddCondition(t,Condition(function Trig_Death_Coil_Conditions))
    call TriggerAddAction(t,function Trig_Death_Coil_Actions)
    
    set t=null
endfunction

Trigger to move the Death Coil animation towards target
JASS:
function Trig_Death_Coil_Timer_Actions takes nothing returns nothing
    local real xx=GetUnitX(udg_unit_deathcoil[2])    
    local real yy=GetUnitY(udg_unit_deathcoil[2])
    local real X=xx+10.00*Cos(bj_RADTODEG*Atan2(GetUnitY(udg_unit_deathcoil[1])-yy,GetUnitX(udg_unit_deathcoil[1])-xx)*bj_DEGTORAD)
    local real Y=yy+10.00*Sin(bj_RADTODEG*Atan2(GetUnitY(udg_unit_deathcoil[1])-yy,GetUnitX(udg_unit_deathcoil[1])-xx)*bj_DEGTORAD)
    
    if GetUnitAbilityLevel(udg_unit_deathcoil[1],'Bcyc') > 0  then     
        call TriggerExecute(gg_trg_Death_Coil_Target_Dies)
    else     
        call SetUnitPosition(udg_unit_deathcoil[2],X,Y)
    endif
endfunction
//===========================================================================
function InitTrig_Death_Coil_Timer takes nothing returns nothing
    set gg_trg_Death_Coil_Timer=CreateTrigger()
    call DisableTrigger(gg_trg_Death_Coil_Timer)
    call TriggerRegisterTimerEvent(gg_trg_Death_Coil_Timer,0.01,true)                              
    call TriggerAddAction(gg_trg_Death_Coil_Timer,function Trig_Death_Coil_Timer_Actions)
endfunction


Trigger to apply the damage
JASS:
function Trig_Death_Coil_Hit_Conditions takes nothing returns boolean
    return GetTriggerUnit()==udg_unit_deathcoil[1]
endfunction

function Trig_Death_Coil_Hit_Actions takes nothing returns nothing
    call DestroyEffect(AddSpecialEffectTarget("Abilities\\Spells\\Undead\\DeathCoil\\DeathCoilSpecialArt.mdl",udg_unit_deathcoil[1],"origin"))
    if IsUnitAlly(udg_unit_deathcoil[1],GetOwningPlayer(udg_unit_deathcoil[0]))==true then 
        call SetUnitState(udg_unit_deathcoil[1],UNIT_STATE_LIFE,GetUnitStateSwap(UNIT_STATE_LIFE,udg_unit_deathcoil[1])+(I2R(udg_integer_deathcoil)*200.00))
    else
        if IsUnitType(udg_unit_deathcoil[1],UNIT_TYPE_MAGIC_IMMUNE)==false then
            call UnitDamageTarget(udg_unit_deathcoil[0],udg_unit_deathcoil[1],I2R(udg_integer_deathcoil)*100.00,true,true,ATTACK_TYPE_NORMAL,DAMAGE_TYPE_UNIVERSAL,null)
        endif
    endif
          
    call RemoveUnit(udg_unit_deathcoil[2])
    set udg_unit_deathcoil[1]=null
    call DisableTrigger(gg_trg_Death_Coil_Timer)
    call DisableTrigger(gg_trg_Death_Coil_Target_Dies)
    call DisableTrigger(GetTriggeringTrigger())
endfunction
//===========================================================================
function InitTrig_Death_Coil_Hit takes nothing returns nothing
    set gg_trg_Death_Coil_Hit=CreateTrigger()
    call DisableTrigger(gg_trg_Death_Coil_Hit)
    call TriggerAddCondition(gg_trg_Death_Coil_Hit,Condition(function Trig_Death_Coil_Hit_Conditions))
    call TriggerAddAction(gg_trg_Death_Coil_Hit,function Trig_Death_Coil_Hit_Actions)

endfunction

Trigger in the case the target dies or else, it will remove the death coil animation
JASS:
function Trig_Death_Coil_Target_Dies_Conditions takes nothing returns boolean
    return GetTriggerUnit()==udg_unit_deathcoil[1]
endfunction

function Trig_Death_Coil_Target_Dies_Actions takes nothing returns nothing    
    call RemoveUnit(udg_unit_deathcoil[2])
    call DestroyEffect(AddSpecialEffect("Abilities\\Spells\\Undead\\DeathCoil\\DeathCoilSpecialArt.mdl",GetUnitX(udg_unit_deathcoil[2]),GetUnitY(udg_unit_deathcoil[2])))
    
    set udg_unit_deathcoil[1]=null
    call DisableTrigger(gg_trg_Death_Coil_Timer)
    call DisableTrigger(gg_trg_Death_Coil_Hit)
    call DisableTrigger(GetTriggeringTrigger())
endfunction
//===========================================================================
function InitTrig_Death_Coil_Target_Dies takes nothing returns nothing
    local integer i=0
    
    set gg_trg_Death_Coil_Target_Dies=CreateTrigger()
    call DisableTrigger(gg_trg_Death_Coil_Target_Dies)
    loop
        exitwhen i==bj_MAX_PLAYER_SLOTS
        call TriggerRegisterPlayerUnitEvent(gg_trg_Death_Coil_Target_Dies,Player(i),EVENT_PLAYER_UNIT_DEATH,null)
        set i=i+1
    endloop
    call TriggerAddCondition(gg_trg_Death_Coil_Target_Dies,Condition(function Trig_Death_Coil_Target_Dies_Conditions))
    call TriggerAddAction(gg_trg_Death_Coil_Target_Dies,function Trig_Death_Coil_Target_Dies_Actions)
endfunction
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
The idea of an MUI spell is to think in two things that are related to your spell.
1, Universal (constant) data of your spell.
2, Relative data of an instance of your spell.

The first one is pretty simple, it is the name of your spell, the ability id, the order id/string, the time it takes to cast it, etc.

The second one changes for each cast like the caster, the target, the level of the spell, the damage of the spell, the speed of the spell, etc.

For the first one, you can use global variables or even a hastable to store them.
For the second one, you will probably need a hashtable or global variable arrays or local variables if you can keep them until the spell ends.
If the spell is completely instant, you can even use global variables without arrays, but that can come later.

First of all, it is important to understand to which category your data beloings.
So, I have my data "Target location". That will belong to 2.
And when I have "Targets allowed", that will belong to 1.
But when I have "Missile model", it gets a bit more interesting.
It is supposed to be 2, but ussually people don't have the need to have it in 2, so they place it in 1.

For how to store the data of 2, you first have to find out what could be a "primary key" to store the data on.
If the spell can only be cast once at a time per unit, the unit handle id or unit user data (custom value) if you have a unit indexer would fit perfectly.
If the spell can be active multiple times per unit, you would have to find another way to do it.
But in any case, you use that integer as index of your arrays/hashtable to load/store (use) your data.
Also, keep in mind that you would have to be able to load that integer at any given time from any data that you have available.
When you have your unit, you can load the custom value.
But when you only have your target, then not. So you would have to find another way to load the integer.
You could do it by storing the integer in an array using the custom data of the target as index, but then only one spell can be used on a unit... (You might overwrite the old spell instance, but that does not count for all spells.)
Some become real nasty when trying to do nice things, but it will all end up quite nice if you got everything working right.

Then, there is only one thing left that can cause issues.
The fact that you have to use all spell instances.
When you have a periodic timer, you have to loop over your entire list of spell instances and run them one at a time.
The same counts for all other events that are related to the spell in general rather than related to an instance of the spell.

There is no "Steps to make your spell MUI" just like that because there are a few things that can relate to anything that we know and dont know (map dependent).
But it is worth to check out some tutorials in the tutorial section:
MUI Spells Using Artificial Waits
MUI Triggers with Waits
Hashtables and MUI
Visualize: Dynamic Indexing

Things that you could also consider looking into are missile systems, buff systems and stuff like that.
In your case, I would definately consider using a missile system... one that is stable, has high performance and has proper settings to make it do what it is supposed to do... so yes, you will have to filter some out that are not properly structured.

Also, the only difference between arrays and hashtables is that arrays may be more readable... unless you make really insane variable names like I do.
They are slightly faster (performance-wise).
But hashtables can use an index higher than 8190... up to 2^31-1.
Also, hashtables can use 2 (or more if you are smart) indexes to store data relative to multiple things.
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
Why wouldnt that work?

return <boolean>

GetSpellAbilityId()=='DanW' or GetSpellAbilityId()=='DanE' or GetSpellAbilityId()=='DanR'
is a proper boolean, so can be used in boolean returns.

or is used for boolean statements, not necessarily for if statements.
 
Level 40
Joined
Dec 14, 2005
Messages
10,532
There are two viable ways to store data here:

  • Hash tables (the idiomatic way to do it in JASS)
  • Structs (the idiomatic way to do it in vJass)

I strongly suggest just using vJass and structs since your solution will end up being considerably cleaner. By the way, basically any nontrivial vJass code, including this, uses TimerUtils, so you should download that and put it in your map.

The layout for more or less any projectile-based spell using this style can be broken down as follows. You can more or less view this as a "fill-in-the-blanks" template.

JASS:
scope MySpell initializer init
//We can set up any useful constants at the top
globals
    integer SPELL_ID = 'xxxx'
    //The ID of a dummy suitable for use as a projectile.
    //You'll want dummy.mdx or similar
    integer DUMMY_ID = 'yyyy'
    ... //Any other constants you want to define go here
endglobals

//This is where we'll store any data that we need between iterations
private struct SpellData
    timer t //The timer associated with this spell
    unit caster //We usually want this
    unit projectile //We probably at least need a dummy to move around
    effect sfx //And the special effect attached to it
    ... //More fields here

    //I generally recommend against methods in structs,
    //but the following ones will make everything more readable
    static method create takes nothing returns SpellData
        local SpellData sd = SpellData.allocate()
        sd.t = NewTimer()
        return sd
    endmethod

    static method createFromTimer takes timer t returns SpellData
        return GetTimerData(t)
    endmethod

    method onDestroy takes nothing returns nothing
        call DestroyEffect(this.sfx)
        call SetUnitExploded(this.projectile,true)
        call KillUnit(this.projectile)
        call PauseTimer(this.t)
        call ReleaseTimer(this.t)
        set this.sfx = null
        set this.projectile = null
        set this.caster = null
    endmethod
endstruct

//This function is responsible for whatever the projectile
//does when it collides with the target.
//In principle this could be part of the if statement in Callback,
//but the extra function makes the code a lot cleaner
private function Finish takes SpellData sd returns nothing
    ... //Do whatever needs to be done
    //Don't deallocate sd, that's Callback's job!
endfunction

//Your timer callback is where the periodic effects such as movement
//should happen
private function Callback takes nothing returns nothing
    local SpellData sd = SpellData.createFromTimer(GetExpiredTimer())

    //We probably will always want to check
    //whether the projectile has reached its target
    if ... then
        call Finish(sd)
        call sd.destroy()
    else //This is where we'll handle any looping actions
        ... //Move the projectile and such
    endif
endfunction

//This is where the setup for your projectile happens
private function Actions takes nothing returns nothing
    local SpellData sd = SpellData.create()
    sd.caster = GetTriggerUnit()
    sd.projectile = CreateUnit(GetOwningPlayer(sd.caster),DUMMY_ID, ...)
    sd.sfx = AddSpecialEffectTarget(...,sd.projectile)
    ... //Any other setup goes here
    //Start the timer. This should be a repeating timer for projectile spells
    call TimerStart(sd.t,...,function Callback)
endfunction

//Everything from here down is basically the same for every spell
//Probably don't need to change this
private function Conditions takes nothing returns boolean
    return GetSpellAbilityId() == SPELL_ID
endfunction

private function init takes nothing returns nothing
    local trigger t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(t,Condition(function Conditions))
    call TriggerAddAction(t,function Actions)
endfunction
endscope
 
Last edited:
Level 4
Joined
Nov 27, 2012
Messages
85
JASS:
return GetSpellAbilityId()=='DanW' or GetSpellAbilityId()=='DanE' or GetSpellAbilityId()=='DanR'

Won't work. or is used for if. You can set a random integer and check if it's 1, do the first, if it's 2, then do the second thing, Etc.

my posted triggers are complete and working
 
Level 4
Joined
Nov 27, 2012
Messages
85
There are two viable ways to store data here:

  • Hash tables (the idiomatic way to do it in JASS)
  • Structs (the idiomatic way to do it in vJass)

I strongly suggest just using vJass and structs since your solution will end up being considerably cleaner. By the way, basically any nontrivial vJass code, including this, uses TimerUtils, so you should download that and put it in your map.

The layout for more or less any projectile-based spell using this style can be broken down as follows. You can more or less view this as a "fill-in-the-blanks" template.

JASS:
scope MySpell initializer init
//We can set up any useful constants at the top
globals
    integer SPELL_ID = 'xxxx'
    //The ID of a dummy suitable for use as a projectile.
    //You'll want dummy.mdx or similar
    integer DUMMY_ID = 'yyyy'
    ... //Any other constants you want to define go here
endglobals

//This is where we'll store any data that we need between iterations
private struct SpellData
    timer t //The timer associated with this spell
    unit caster //We usually want this
    unit projectile //We probably at least need a dummy to move around
    effect sfx //And the special effect attached to it
    ... //More fields here

    //I generally recommend against methods in structs,
    //but the following ones will make everything more readable
    static method create takes nothing returns SpellData
        local SpellData sd = SpellData.allocate()
        sd.t = NewTimer()
        return sd
    endmethod

    static method createFromTimer takes timer t returns SpellData
        return GetTimerData(t)
    endmethod

    method onDestroy takes nothing returns nothing
        call DestroyEffect(this.sfx)
        call SetUnitExploded(this.projectile,true)
        call KillUnit(this.projectile)
        call ReleaseTimer(this.t)
        set this.sfx = null
        set this.projectile = null
        set this.caster = null
    endmethod
endstruct

//This function is responsible for whatever the projectile
//does when it collides with the target.
//In principle this could be part of the if statement in Callback,
//but the extra function makes the code a lot cleaner
private function Finish takes SpellData sd returns nothing
    ... //Do whatever needs to be done
    //Don't deallocate sd, that's Callback's job!
endfunction

//Your timer callback is where the periodic effects such as movement
//should happen
private function Callback takes nothing returns nothing
    local SpellData sd = SpellData.createFromTimer(GetExpiredTimer())

    //We probably will always want to check
    //whether the projectile has reached its target
    if ... then
        call Finish(sd)
        call sd.destroy()
    else //This is where we'll handle any looping actions
        ... //Move the projectile and such
    endif
endfunction

//This is where the setup for your projectile happens
private function Actions takes nothing returns nothing
    local SpellData sd = SpellData.create()
    sd.caster = GetTriggerUnit()
    sd.projectile = CreateUnit(GetOwningPlayer(sd.caster),DUMMY_ID, ...)
    sd.sfx = AddSpecialEffectTarget(...,sd.projectile)
    ... //Any other setup goes here
    //Start the timer. This should be a repeating timer for projectile spells
    call TimerStart(sd.t,...,function Callback)
endfunction

//Everything from here down is basically the same for every spell
//Probably don't need to change this
private function Conditions takes nothing returns boolean
    return GetSpellAbilityId() == SPELL_ID
endfunction

private function init takes nothing returns nothing
    local trigger t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(t,Condition(function Conditions))
    call TriggerAddAction(t,function Actions)
endfunction
endscope

I should have mentioned, for some reason, I can't get Newgen + vJass working. Made a post about it a few years ago, re-installed countless times, tried everything. Maybe I'll try again,
Appreciate the responses WietLoland PurplePoot
 
Level 40
Joined
Dec 14, 2005
Messages
10,532
Non-vJass version:

JASS:
//We unfortunately can't define globals like this in non-vJass,
//so this is more of a guide for which globals to make. You might want
//to inline SPELL_ID and DUMMY_ID rather than having udg_ globals.
globals
    integer SPELL_ID = 'xxxx'
    //The ID of a dummy suitable for use as a projectile.
    //You'll want dummy.mdx or similar
    integer DUMMY_ID = 'yyyy'
    hashtable SpellHash = InitHashtable() //This can be shared by all spells
    ... //Any other constants you want to define go here
endglobals

//Your timer callback is where the periodic effects such as movement
//should happen
function MySpell_Callback takes nothing returns nothing
    local integer id = GetHandleId(GetExpiredTimer())
    //In non-vJass we need to load everything at once
    //Let's store the projectile at 0
    local unit proj = LoadUnitHandle(SpellHash,id,0)
    //Load more stuff

    //We probably will always want to check
    //whether the projectile has reached its target
    if ... then
        ... //Do end-of-spell stuff
        //Store the effect at 1
        call DestroyEffect(LoadEffectHandle(SpellHash,id,1))
        call SetUnitExploded(proj,true)
        call KillUnit(proj)
        call FlushChildHashtable(SpellHash,id)
        call PauseTimer(GetExpiredTimer())
        call DestroyTimer(GetExpiredTimer())
    else //This is where we'll handle any looping actions
        ... //Move the projectile and such
    endif
    set proj = null
    //set all other handles to null
endfunction

//This is where the setup for your projectile happens
function MySpell_Actions takes nothing returns nothing
    local timer t = CreateTimer()
    local integer id = GetHandleId(t)
    local unit proj = null
    call StoreHandle(SpellHash,id,2,GetTriggerUnit())
    proj = CreateUnit(GetOwningPlayer(sd.caster),DUMMY_ID, ...)
    call StoreHandle(SpellHash,id,0,proj)
    call StoreHandle(SpellHash,id,1,AddSpecialEffectTarget(...,sd.projectile))
    ... //Any other setup goes here
    //Start the timer. This should be a repeating timer for projectile spells
    call TimerStart(t,...,function MySpell_Callback)
    set t = null
    set proj = null
endfunction

//Everything from here down is basically the same for every spell
//Probably don't need to change this
function MySpell_Conditions takes nothing returns boolean
    return GetSpellAbilityId() == SPELL_ID
endfunction

function InitTrig_MySpell takes nothing returns nothing
    local trigger t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(t,Condition(function MySpell_Conditions))
    call TriggerAddAction(t,function MySpell_Actions)
endfunction
 
Level 4
Joined
Nov 27, 2012
Messages
85
hm my if/then/else actions aren't working properly

debug message "test2" does indeed fire infinitely, but the unit isn't removed
debug message "test" won't even if proj and target are indeed within given range

seems like proj isn't being called correctly


JASS:
function MySpell_Callback takes nothing returns nothing
    local integer id = GetHandleId(GetExpiredTimer())
    local unit proj = LoadUnitHandle(udg_hashtable_deathcoil,id,0)
    local unit target = LoadUnitHandle(udg_hashtable_deathcoil,id,3)

    if IsUnitInRange(proj,target,1000) then
        call BJDebugMsg("test")
    else
        call RemoveUnit(proj)  <=============== error line
        call BJDebugMsg("test2")
    endif
    
    set proj = null
    set target = null
endfunction
                
function Trig_Death_Coil_Actions takes nothing returns nothing
    local timer t = CreateTimer()
    local integer id = GetHandleId(t)
    local unit proj = null
    local unit target = GetSpellTargetUnit()
        
    call SaveUnitHandle(udg_hashtable_deathcoil,id,2,GetTriggerUnit())
    set proj = CreateUnit(GetOwningPlayer(GetTriggerUnit()),'e006',GetUnitX(GetTriggerUnit()),GetUnitY(GetTriggerUnit()),GetUnitFacing(GetTriggerUnit()))
    
    call SaveUnitHandle(udg_hashtable_deathcoil,id,0,proj)  
    call SaveUnitHandle(udg_hashtable_deathcoil,id,3,target)
    call TimerStart(t,0.01,true,function MySpell_Callback)
    
    set t = null
    set proj = null
endfunction

function Trig_Death_Coil_Conditions takes nothing returns boolean
    return GetSpellAbilityId()=='DanW' or GetSpellAbilityId()=='DanE' or GetSpellAbilityId()=='DanR' 
endfunction

function InitTrig_Death_Coil takes nothing returns nothing
    local integer i=0
    
    set gg_trg_Death_Coil=CreateTrigger()
    loop
        exitwhen i==bj_MAX_PLAYER_SLOTS
        call TriggerRegisterPlayerUnitEvent(gg_trg_Death_Coil,Player(i),EVENT_PLAYER_UNIT_SPELL_EFFECT,null)
        set i=i+1
    endloop

    call TriggerAddCondition(gg_trg_Death_Coil,Condition(function Trig_Death_Coil_Conditions))
    call TriggerAddAction(gg_trg_Death_Coil,function Trig_Death_Coil_Actions)
endfunction
 
Last edited:
Level 4
Joined
Nov 27, 2012
Messages
85
I've had issues with JNGP as I mentioned. I don't understand why it's not working?
I've verified it's not loading the unit handles, why?
 
Chances are, you forgot to initialize the hashtable!

Hashtable globals are initialized to 'null' by default. In order to use them, you have to assign the variable to a new hashtable, or an existing hashtable. In your spell, it is convenient to do this within your "initialization" function (InitTrig):
JASS:
function InitTrig_Death_Coil takes nothing returns nothing
    local integer i=0
    
    // add this line
    set udg_hashtable_deathcoil = InitHashtable()
    set gg_trg_Death_Coil=CreateTrigger()
    loop
        exitwhen i==bj_MAX_PLAYER_SLOTS
        call TriggerRegisterPlayerUnitEvent(gg_trg_Death_Coil,Player(i),EVENT_PLAYER_UNIT_SPELL_EFFECT,null)
        set i=i+1
    endloop

    call TriggerAddCondition(gg_trg_Death_Coil,Condition(function Trig_Death_Coil_Conditions))
    call TriggerAddAction(gg_trg_Death_Coil,function Trig_Death_Coil_Actions)
endfunction

P.S. As for getting JassNewGenPack setup, feel free to create a thread in this forum:
http://www.hiveworkshop.com/forums/warcraft-editing-tools-277/
I'll try to help you out with whatever issues you have. Being able to take advantage of vJASS will make life so much easier. :)
 
Level 4
Joined
Nov 27, 2012
Messages
85
awesome, that was it. now I need to figure out why it keeps moving my unit to the center of the map

here is the finished trigger, in case it helps anyone in the future.

and attached in a map with unit and spell models

edit: trigger in map is slightly different, below is the updated

JASS:
function Death_Coil_Hit_Conditions takes nothing returns boolean
    local integer id=GetHandleId(GetTriggeringTrigger())
    local unit target=LoadUnitHandle(udg_hashtable_deathcoil,id,2)
    
    return GetTriggerUnit()==target
    
    set target=null
endfunction

function Death_Coil_Target_Dies_Actions takes nothing returns nothing   
    local integer id=GetHandleId(GetTriggeringTrigger())  
    local integer i2=LoadInteger(udg_hashtable_deathcoil,id,6)
    local integer i3=LoadInteger(udg_hashtable_deathcoil,id,7)  
    local trigger coiltimer=LoadTriggerHandle(udg_hashtable_deathcoil,id,3)  
    local trigger coilhit=LoadTriggerHandle(udg_hashtable_deathcoil,id,4)
    local unit proj=LoadUnitHandle(udg_hashtable_deathcoil,id,1)
     
    call RemoveUnit(proj)
    call DestroyEffect(AddSpecialEffect("Abilities\\Spells\\Undead\\DeathCoil\\DeathCoilSpecialArt.mdl",GetUnitX(proj),GetUnitY(proj)))
    
    call FlushChildHashtable(udg_hashtable_deathcoil,id)  
    call FlushChildHashtable(udg_hashtable_deathcoil,i2)   
    call FlushChildHashtable(udg_hashtable_deathcoil,i3)
    
    set coiltimer=null
    set coilhit=null
    set proj=null
    call DestroyTrigger(coiltimer)
    call DestroyTrigger(coilhit)
    call DestroyTrigger(GetTriggeringTrigger())
endfunction

function Death_Coil_Hit_Actions takes nothing returns nothing
    local integer id=GetHandleId(GetTriggeringTrigger())    
    local trigger coiltimer=LoadTriggerHandle(udg_hashtable_deathcoil,id,3)  
    local trigger coildies=LoadTriggerHandle(udg_hashtable_deathcoil,id,4)
    local integer i=LoadInteger(udg_hashtable_deathcoil,id,5)
    local integer i2=LoadInteger(udg_hashtable_deathcoil,id,6)
    local integer i3=LoadInteger(udg_hashtable_deathcoil,id,7)
    local unit u=LoadUnitHandle(udg_hashtable_deathcoil,id,0)
    local unit proj=LoadUnitHandle(udg_hashtable_deathcoil,id,1)
    local unit target=LoadUnitHandle(udg_hashtable_deathcoil,id,2)
    
    call DestroyEffect(AddSpecialEffectTarget("Abilities\\Spells\\Undead\\DeathCoil\\DeathCoilSpecialArt.mdl",target,"origin"))
    if IsUnitAlly(target,GetOwningPlayer(u))==true then 
        call SetUnitState(target,UNIT_STATE_LIFE,GetUnitStateSwap(UNIT_STATE_LIFE,target)+(I2R(i)*200.00))
    else
        if IsUnitType(target,UNIT_TYPE_MAGIC_IMMUNE)==false then
            call UnitDamageTarget(u,target,I2R(i)*100.00,true,true,ATTACK_TYPE_NORMAL,DAMAGE_TYPE_UNIVERSAL,null)
        endif
    endif
    
    call FlushChildHashtable(udg_hashtable_deathcoil,id)  
    call FlushChildHashtable(udg_hashtable_deathcoil,i2)   
    call FlushChildHashtable(udg_hashtable_deathcoil,i3)
          
    call RemoveUnit(proj)
    set coiltimer=null
    set coildies=null
    set u=null
    set proj=null
    set target=null
    call DestroyTrigger(coiltimer) 
    call DestroyTrigger(coildies)  
    call DestroyTrigger(GetTriggeringTrigger())
endfunction

function Death_Coil_Timer takes nothing returns nothing  
    local integer id=GetHandleId(GetTriggeringTrigger())    
    local trigger coildies=LoadTriggerHandle(udg_hashtable_deathcoil,id,4)
    local unit proj=LoadUnitHandle(udg_hashtable_deathcoil,id,1)
    local unit target=LoadUnitHandle(udg_hashtable_deathcoil,id,2)
    local real x=GetUnitX(target)
    local real y=GetUnitY(target)
    local real xx=GetUnitX(proj)    
    local real yy=GetUnitY(proj)
    local real X=xx+10.00*Cos(bj_RADTODEG*Atan2(y-yy,x-xx)*bj_DEGTORAD)
    local real Y=yy+10.00*Sin(bj_RADTODEG*Atan2(y-yy,x-xx)*bj_DEGTORAD)
    
    if GetUnitAbilityLevel(target,'Bcyc') > 0  then     
        call TriggerExecute(coildies)
    else     
        call SetUnitPosition(proj,X,Y)
    endif
    
    set coildies=null
    set proj=null
    set target=null
endfunction
              
function Trig_Death_Coil_Actions takes nothing returns nothing
    local trigger coiltimer=CreateTrigger()
    local trigger coilhit=CreateTrigger()
    local trigger coildies=CreateTrigger()
    local integer id=GetHandleId(coiltimer)
    local integer i
    local unit u=GetTriggerUnit()
    local unit proj=CreateUnit(GetOwningPlayer(u),'e006',GetUnitX(u),GetUnitY(u),GetUnitFacing(u))
    local unit target=GetSpellTargetUnit()
                          
    call UnitApplyTimedLife(proj,'BTLF',2.5)
                              
    call SaveUnitHandle(udg_hashtable_deathcoil,id,0,u) 
    call SaveUnitHandle(udg_hashtable_deathcoil,id,1,proj) 
    call SaveUnitHandle(udg_hashtable_deathcoil,id,2,target) 
    call SaveTriggerHandle(udg_hashtable_deathcoil,id,4,coildies)
    
    call TriggerRegisterTimerEvent(coiltimer,0.01,true) 
    call TriggerAddAction(coiltimer,function Death_Coil_Timer)
                                                    
    set id=GetHandleId(coilhit)     
    set i=GetUnitAbilityLevel(u,GetSpellAbilityId())                 
    call SaveUnitHandle(udg_hashtable_deathcoil,id,0,u)
    call SaveUnitHandle(udg_hashtable_deathcoil,id,1,proj)
    call SaveUnitHandle(udg_hashtable_deathcoil,id,2,target)
    call SaveTriggerHandle(udg_hashtable_deathcoil,id,3,coiltimer)
    call SaveTriggerHandle(udg_hashtable_deathcoil,id,4,coildies) 
    call SaveInteger(udg_hashtable_deathcoil,id,5,i)
    set i=GetHandleId(coiltimer)
    call SaveInteger(udg_hashtable_deathcoil,id,6,i)
    set i=GetHandleId(coildies)
    call SaveInteger(udg_hashtable_deathcoil,id,7,i)
    
    call TriggerRegisterUnitInRange(coilhit,proj,50.00,null)    
    call TriggerAddCondition(coilhit,Condition(function Death_Coil_Hit_Conditions))
    call TriggerAddAction(coilhit,function Death_Coil_Hit_Actions)
                                                   
    set id=GetHandleId(coildies) 
    call SaveUnitHandle(udg_hashtable_deathcoil,id,1,proj)
    call SaveUnitHandle(udg_hashtable_deathcoil,id,2,target)
    call SaveTriggerHandle(udg_hashtable_deathcoil,id,3,coiltimer)
    call SaveTriggerHandle(udg_hashtable_deathcoil,id,4,coilhit)   
    set i=GetHandleId(coiltimer)
    call SaveInteger(udg_hashtable_deathcoil,id,6,i)
    set i=GetHandleId(coilhit)
    call SaveInteger(udg_hashtable_deathcoil,id,7,i)
    
    call TriggerRegisterPlayerUnitEvent(coildies,GetOwningPlayer(target),EVENT_PLAYER_UNIT_DEATH,null)
    call TriggerAddCondition(coildies,Condition(function Death_Coil_Hit_Conditions))      
    call TriggerAddAction(coildies,function Death_Coil_Target_Dies_Actions)
    
    set coiltimer=null
    set coilhit=null
    set coildies=null
    set u=null
    set proj=null
    set target=null
endfunction

function Trig_Death_Coil_Conditions takes nothing returns boolean
    return GetSpellAbilityId()=='DanW' or GetSpellAbilityId()=='DanE' or GetSpellAbilityId()=='DanR' 
endfunction

function Trig_Death_Coil_Precast_Actions takes nothing returns nothing    
    if IsUnitAlly(GetSpellTargetUnit(),GetOwningPlayer(GetTriggerUnit()))==false then
        if IsUnitType(GetSpellTargetUnit(),UNIT_TYPE_MAGIC_IMMUNE)==true then
            call IssueImmediateOrder(GetTriggerUnit(),"stop")                                                                      
        endif
    endif
endfunction
//===========================================================================
function InitTrig_Death_Coil takes nothing returns nothing
    local trigger t=CreateTrigger()
    local integer i=0
                                  
    set udg_hashtable_deathcoil=InitHashtable()
    set gg_trg_Death_Coil=CreateTrigger()
    loop
        exitwhen i==bj_MAX_PLAYER_SLOTS 
        call TriggerRegisterPlayerUnitEvent(gg_trg_Death_Coil,Player(i),EVENT_PLAYER_UNIT_SPELL_CAST,null)
        call TriggerRegisterPlayerUnitEvent(t,Player(i),EVENT_PLAYER_UNIT_SPELL_EFFECT,null)
        set i=i+1
    endloop
                                                                           
    call TriggerAddCondition(gg_trg_Death_Coil,Condition(function Trig_Death_Coil_Conditions))
    call TriggerAddAction(gg_trg_Death_Coil,function Trig_Death_Coil_Precast_Actions)
    
    call TriggerAddCondition(t,Condition(function Trig_Death_Coil_Conditions))
    call TriggerAddAction(t,function Trig_Death_Coil_Actions)
    
    set t=null
endfunction
 

Attachments

  • Death Coil MUI.w3x
    20.4 KB · Views: 31
Last edited by a moderator:
JASS:
function Death_Coil_Hit_Conditions takes nothing returns boolean
    local integer id=GetHandleId(GetTriggeringTrigger())
    local unit target=LoadUnitHandle(udg_hashtable_deathcoil,id,2)
    
    return GetTriggerUnit()==target
    
    set target=null
endfunction

That null at the end won't work, ever. Just skip using the local unit and inline the LoadUnitHandle line into the exitwhen statement.
 
Status
Not open for further replies.
Top