• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

[JASS] Help with a spell set

Status
Not open for further replies.
Level 14
Joined
Jul 26, 2008
Messages
1,009
Hi, I'm creating a spell set that relies on a system. When a unit casts a spell that spell is saved in a hashtable attached to the unit. Details of the spell are also saved.

The spells are Double Cast Active, Mimic, and Tricast. Doublecast Active repeats the last spell cast by the unit. I'd like it to aim at the same angle on point casts instead of the same spot, but I don't think I can do the math for that.

Then there's mimic, which will take the last spell cast by a target unit and give it to you. Each cast decreases an integer and when it hits 0 you're given mimic back and the spell is removed.

Finally is Tricast. The last 3 spells cast on a unit are saved. When cast on that unit those 3 spells are called up again and cast on the unit.

All 3 spells will not cast, and won't even go to conditions. That's the first problem. I'd also prefer to optomize it so they run smoothly and do things efficiently. Later on I'll prep them for easy configuration. Thanks for the help!

JASS:
globals
    hashtable LastSpell
    hashtable TripleSpell
endglobals

function Trig_LastSpell_Actions takes unit c, unit t returns nothing
 local integer i = LoadInteger(TripleSpell, 0, GetHandleId(c)) + 1
    //Double Cast and Mimic
    call SaveReal(LastSpell, 3, GetHandleId(c), GetSpellTargetX())
    call SaveReal(LastSpell, 4, GetHandleId(c), GetSpellTargetY())
    call SaveStr(LastSpell, 5, GetHandleId(c), OrderId2String(GetUnitCurrentOrder(c)))
    call SaveInteger(LastSpell, 6, GetHandleId(c), GetSpellAbilityId())
    call SaveUnitHandle(LastSpell, 10, GetHandleId(c), t)
    //Tricast
    call SaveInteger(TripleSpell, 0, GetHandleId(t), i)
    if i >= 3 then
       call SaveInteger(TripleSpell, 0, GetHandleId(t), 0)
    endif
    call BJDebugMsg(I2S(i))
    call SaveInteger(TripleSpell, i*10, GetHandleId(t), GetSpellAbilityId())
    call SaveStr(TripleSpell, i, GetHandleId(t), OrderId2String(GetUnitCurrentOrder(c)))
endfunction

function Trig_LastSpell_Conditions takes nothing returns boolean
    if not(GetSpellAbilityId() == 'DblA') or not(GetSpellAbilityId() == 'TriC') or not(GetSpellAbilityId() == 'mimi') then
        call Trig_LastSpell_Actions(GetTriggerUnit(), GetSpellTargetUnit())
    endif
 return false
endfunction

//===========================================================================
function InitTrig_LastSpell takes nothing returns nothing
    set gg_trg_LastSpell = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_LastSpell, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition(gg_trg_LastSpell, function Trig_LastSpell_Conditions)
 set LastSpell = InitHashtable()
 set TripleSpell = InitHashtable()
endfunction[/HIDDEN]

JASS:
scope DoubleCastActive initializer Init

globals
    private unit TempUnit
endglobals

private function Enemy takes nothing returns boolean
    return IsUnitEnemy(GetEnumUnit(), GetOwningPlayer(TempUnit))
endfunction

private function Ally takes nothing returns boolean
    return IsUnitAlly(GetEnumUnit(), GetOwningPlayer(TempUnit))
endfunction

private function Actions takes unit c,unit t,unit d, string id returns nothing
    local integer i = LoadInteger(LastSpell, 6, GetHandleId(GetTriggerUnit()))
    local real x = GetUnitX(c)
    local real x2 = LoadReal(LastSpell, 3, GetHandleId(GetTriggerUnit()))
    local real y2 = LoadReal(LastSpell, 4, GetHandleId(GetTriggerUnit()))
    local group g = NewGroup()
    local boolexpr boo = Filter(function Enemy)
    set d = CreateUnit(GetTriggerPlayer(),'hsor',x,GetUnitY(c),GetUnitFacing(c))
    call SetUnitState(d,UNIT_STATE_MANA,9999)
    call UnitAddAbility(d,i)
    call UnitApplyTimedLife(d,'BTLF',15.)
    
    if IsUnitAlly(t, GetOwningPlayer(c)) then
        set boo = Filter(function Ally)
    endif
    
    if x2 == 0 and x != 0 then
        call IssueImmediateOrder(d,id)
    elseif t != null then
        call GroupEnumUnitsInRange(g, x2, y2, 400, boo)
        set t = FirstOfGroup(g)
        call IssueTargetOrder(d,id,t)
    else
        call IssuePointOrder(d,id,x2+GetRandomReal(0,100),y2+GetRandomReal(0,100))
    endif
 
 call ReleaseGroup(g)
endfunction

private function Conditions takes nothing returns boolean
 local string str = LoadStr(LastSpell, 5, GetHandleId(GetTriggerUnit()))
 local unit t = LoadUnitHandle(LastSpell, 10, GetHandleId(GetTriggerUnit()))
    if GetSpellAbilityId() == 'DblA' then
        call Actions(GetTriggerUnit(),t,null, str)
    endif
    return false
endfunction
 
//===========================================================================
public function Init takes nothing returns nothing
    local trigger t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(t,Condition(function Conditions))
endfunction

endscope[/HIDDEN]

JASS:
function Trig_Triple_Cast_Conditions takes nothing returns boolean
    return GetSpellAbilityId() == 'TriC'
endfunction

function Trig_Triple_Cast_Actions takes nothing returns nothing
    local unit c = GetTriggerUnit()
    local unit t = GetSpellTargetUnit()
    local unit array d
    local real x = GetUnitX(c)
    local real y = GetUnitY(c)
    local integer i = 1
    local integer array abil
    local string array id
    
    
 loop
  exitwhen i == 4
  call BJDebugMsg(I2S(i))
    set abil[i] = LoadInteger(TripleSpell, i*10, GetHandleId(GetSpellTargetUnit()))
    set id[i] = LoadStr(TripleSpell, i, GetHandleId(GetTriggerUnit()))
    set d[i] = CreateUnit(GetTriggerPlayer(),'e002',x,GetUnitY(c),GetUnitFacing(c))
    call UnitAddAbility(d[i],abil[i])
    call UnitApplyTimedLife(d[i],'BTLF',5.)
    call IssueTargetOrder(d[i],id[i],t)
    set d[i] = null
    set id[i] = null
    set i = i + 1
 endloop
 set t = null
 set c = null
endfunction

//===========================================================================
function InitTrig_Triple_Cast takes nothing returns nothing
    set gg_trg_Triple_Cast = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_Triple_Cast, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( gg_trg_Triple_Cast, Condition( function Trig_Triple_Cast_Conditions ) )
    call TriggerAddAction( gg_trg_Triple_Cast, function Trig_Triple_Cast_Actions )
endfunction

[/HIDDEN]
 
Last edited:
Level 18
Joined
Jan 21, 2006
Messages
2,552
Your "heart" of your system is a little lame. Why not use a little more of the vJass syntax? You could probably do this really nicely by using Grim001's AutoIndex library. There isn't any real need to use so many function calls when you could just store the spell information in a struct and store the integer reference.

JASS:
struct spelldata
    unit     caster         = null
    unit     target         = null

    real     targetx 
    real     targety
    real     castangle
    real     castrange

    string    orderstring
    integer   abilityid

    //all you need to do is make "spelldata" variable and create it on the
    //event response of the ability and then store the struct to the hashtable
    //as you did before.

    static method create takes nothing returns thistype
        local thistype sd = allocate()
        local real x
        local real y

        set sd.caster  = GetTriggerUnit()
        set sd.target  = GetSpellTargetUnit()
        set sd.targetx = GetSpellTargetX()
        set sd.targety = GetSpellTargetY()

        set x = GetUnitX(sd.caster)
        set y = GetUnitY(sd.caster)

        //this is your "cast angle", measured in radians. if you're trying to use it as degrees
        //remember to multiply by 180/bj_PI.
        set sd.castangle = Atan2(sd.targety-y, sd.targetx-x)
    
        //this is your "cast distance", which stores the range at which the spell was casted. this
        //can be used with the cast-angle to achieve a "duplicate" cast.
        set sd.castrange = SquareRoot((sd.targetx-x)*(sd.targetx-x) + (sd.targety-y)*(sd.targety-y))

        set sd.abilityid = GetSpellAbilityId()
        set sd.orderstring = OrderId2String(GetUnitCurrentOrder(sd.caster))

        return sd
    endmethod 
endstruct

Titanhex said:
but I don't think I can do the math for that.

I'll show you how to do it in the struct above.

Okay, using struct spelldata that I defined above you could re-write the "heart" of your system as:

JASS:
globals
    hashtable LastSpell
    hashtable TripleSpell
endglobals

function Trig_LastSpell_Actions takes nothing returns nothing
 local spelldata sd = spelldata.create()
 local integer i = LoadInteger(TripleSpell, 0, GetHandleId(sd.caster))

    //Double Cast and Mimic
    call SaveInteger(LastSpell, 3, GetHandleId(sd.caster), sd) 

    //Tricast
    call SaveInteger(LastSpell, 0, GetHandleId(sd.target), i+1)

    if i >= 2 then
       call SaveInteger(LastSpell, 0, GetHandleId(sd.target), 0)
    endif
    call SaveStr(TripleSpell, i, GetHandleId(sd.target), sd.orderstring)
    call SaveInteger(TripleSpell, i*10, GetHandleId(sd.target), sd.abilityid)
endfunction

//===========================================================================
function InitTrig_LastSpell takes nothing returns nothing
    set gg_trg_LastSpell = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_LastSpell, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddAction( gg_trg_LastSpell, function Trig_LastSpell_Actions )
endfunction

You could probably remake this system quite a bit better though, let me read the rest of your post and see what the problem is.

Okay, well it looks like you're trying to use hashtables to cross-reference data, and it's a little hard to follow...
 
Last edited:
Level 14
Joined
Jul 26, 2008
Messages
1,009
Thanks Berban. Hopefully I can grasp what you're pointing out here, as I am positive it'll be more efficient. I may need a further explanation, but we'll see once I've analyzed the code further.

As for the angle, that only solves one half of the problem. You've found the facing angle, which seems like a start, but... What I need now is to get the spell cast at that same angle. Not sure where to order the dummy unit to cast for this. What would be the SpellTargetX and SpellTargetY so that if he goes up a distance of 900 after casting straight north he wouldn't cast back south? It would also be good to preserve the distance, for spells like Flame Strike.

As for the slightly confusing system. Well, what it does is takes the data of the spell being cast and attaches it to the casting unit. The spell target, spellX, spellY, orderstring, and abilityid are all saved. Each new cast replaces the last data so it's only the last spell cast in the mix.

But for Tricast it takes the last 3 spells cast on a unit. Since they're target spells there's less data, and it's saved in a different hashtable.

When a unit casts mimic, it takes the last spell cast by a unit and gives it to the casting unit.

When a unit casts Tricast, it calls up 3 dummies and orders them to cast the last 3 spells to target that unit.

Hope that clarifies. I've modified the system a bit since my first post, and mimic works flawlessly. However DoubleCast is acting weird and Tricast is still a little buggy.
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
Titanhex said:
so that if he goes up a distance of 900 after casting straight north he wouldn't cast back south? It would also be good to preserve the distance, for spells like Flame Strike.

JASS:
set sd.castangle = Atan2(sd.targety-GetUnitY(sd.caster), sd.targetx-GetUnitX(sd.caster))

What I've done here is stored the angle (in radians) that the spell was casted from the caster. This value can be re-used to reference the same angle that it was initially cast at. I will add the distance-to-target in the data-set, but it really wouldn't be hard for you to do this one yourself.

Titanhex said:
You've found the facing angle, which seems like a start, but...

No, I've found the angle between the target of the spell and the caster, which is the angle at which the spell was casted (from the caster).

Titanhex said:
As for the slightly confusing system. Well, what it does is takes the data of the spell being cast and attaches it to the casting unit. The spell target, spellX, spellY, orderstring, and abilityid are all saved. Each new cast replaces the last data so it's only the last spell cast in the mix.

Okay, well when you're "replacing" the last data-entry then you would want to destroy the spell-data simply by calling the destroy() method. Then you would create another one for the most recent spell-cast and store that in place of the old one. The only real difference between what you were using is you have to destroy the struct because it needs to be recycled.

Titanhex said:
But for Tricast it takes the last 3 spells cast on a unit. Since they're target spells there's less data, and it's saved in a different hashtable.

You really should be able to combine the two into one since the concept is exactly the same. If you want you could have a struct that contains various spell-data structures to store the stuff you need.

JASS:
struct spellhistory
	spelldata array entry [3] //the 3 is the most spells that "spellhistory"
	                          //can store at one time.
	integer		stored	  = 0

	method onDestroy takes nothing returns nothing
		local integer i = 0
		loop
			exitwhen(i == 3)
			if(entry[i]!=0) then
				call entry[i].destroy()
			endif
			set i = i+1
		endloop
	endmethod

	//might as well make sure you can't add duplicate spell
	//data entries
	method add takes spelldata dat returns boolean
		local integer i = 0
		//return false if the spell is already included
		//in the instance of spellhistory
		loop
			exitwhen(i == 3)
			if(entry[i] == dat) then
				return false
			endif
			set i = i+1
		endloop
		//if there is no more room then replace the oldest
		//entry that was added
		if(stored == 3) then
			//we need to shift index 2 to 1, 1 to 0, then
			//replace index 2 with "dat"
			call entry[0].destroy() //no longer required
			set entry[0] = entry[1]
			set entry[1] = entry[2]
			set entry[2] = dat
		else
			set entry[stored] = dat
			set stored = stored+1
		endif
		return true
	endmethod
endstruct

That's just an idea, perhaps you elaborate on it for your specific need.

Titanhex said:
When a unit casts mimic, it takes the last spell cast by a unit and gives it to the casting unit.

Titanhex said:
When a unit casts Tricast, it calls up 3 dummies and orders them to cast the last 3 spells to target that unit.

You could use the struct I posted above to store up to 3 spell-data entries and reference them as you please.

Titanhex said:
Hope that clarifies. I've modified the system a bit since my first post, and mimic works flawlessly. However DoubleCast is acting weird and Tricast is still a little buggy.

Tell me if you have any problems after trying a few of the things I've said.

If you want you can use the AutoIndex library then you would have pretty a pretty cool interface for recording spells that have been cast (or have been cast on) for all units that exist in your map. Perhaps this will help some.

JASS:
library UnitAbility requires AutoIndex

    globals
    
        public constant integer     STORED_ABILITIES        = 3
        
    endglobals

    struct abilitydata
        public unit     caster      = null
        public unit     target      = null
        
        public real     castx
        public real     casty
        public real     targetx
        public real     targety
        public real     castAngle
        public real     castRange
        
        public integer  abilityid
        public string   orderstring
        
        static method create takes nothing returns thistype
            local thistype abil=allocate()
            
            set abil.caster=GetTriggerUnit()
            set abil.castx=GetUnitX(abil.caster)
            set abil.casty=GetUnitY(abil.caster)
            set abil.target=GetSpellTargetUnit()
            set abil.targetx=GetSpellTargetX()
            set abil.targety=GetSpellTargetY()
            set abil.abilityid=GetSpellAbilityId()
            set abil.orderstring=OrderId2String(GetUnitCurrentOrder(abil.caster))
            
            //misc spell-data
            set abil.castAngle=Atan2(abil.targety-abil.casty, abil.targetx-abil.castx) //in radians
            set abil.castRange=SquareRoot((abil.targetx-abil.castx)*(abil.targetx-abil.castx) + (abil.targety-abil.casty)*(abil.targety-abil.casty))
            
            return abil
        endmethod
    endstruct

    struct unitabilities
        public  abilitydata array   entry       [STORED_ABILITIES]
        public  integer             stored      = 0
        
        method onDestroy takes nothing returns nothing
            local integer i=0
            loop
                exitwhen(i==stored)
                if(entry[i]!=0) then
                    call entry[i].destroy()
                endif
                set i=i+1
            endloop
        endmethod
        
        method addAbility takes abilitydata dat returns boolean
            local integer i=0
            loop
                exitwhen(i==stored)
                if(entry[i]==dat) then
                    return false
                endif
                set i=i+1
            endloop
            if(stored==STORED_ABILITIES) then
                call entry[0].destroy()
                
                set entry[0]=entry[1]
                set entry[1]=entry[2]
                set entry[2]=dat
            else
                set entry[stored]=dat
                set stored=stored+1
            endif
            return true
        endmethod
    endstruct
    
    struct unitabilityhistory //requires AutoIndex
        public  unitabilities   out     
        public  unitabilities   in
    
        private trigger         abilevent   = CreateTrigger() 
        
        method onDestroy takes nothing returns nothing
            call out.destroy()
            call in.destroy()
            
            call DestroyTrigger(abilevent)
        endmethod
    
        static method onAbilEvent takes nothing returns boolean
            call unitabilityhistory[GetTriggerUnit()].out.addAbility(abilitydata.create())
            call unitabilityhistory[GetSpellTargetUnit()].in.addAbility(abilitydata.create())
        
            return false
        endmethod
    
        method onCreate takes nothing returns nothing
            set out=unitabilities.create()
            set in=unitabilities.create()
    
            call TriggerRegisterUnitEvent(abilevent, me, EVENT_UNIT_SPELL_EFFECT) 
            call TriggerAddCondition(abilevent, Filter(function thistype.onAbilEvent)) 
        endmethod
    
        implement AutoCreate
        implement AutoDestroy
    endstruct

endlibrary

Because of the cool interface provided by auto-index referencing the most recent spell casted on any given unit (referenced as yourUnit) would be as simple as;

JASS:
local abilitydata dat = unitabilityhistory[ yourUnit ].in.entry[ unitabilityhistory[yourUnit].in.stored - 1 ]
//let's say we wanted the caster and the target of the most recent ability
local unit caster
local unit target

set caster = dat.caster
set target = dat.target

//it's that easy.

It seems like quite a handful, but it's a lot easier than what you seem to have been doing. You could also be creative and add your own reCast method to make the caster re-cast the spell automatically, which would probably be included in the unitabilityhistory API. I suppose I could have thought up better names : (
 
Last edited:
Level 14
Joined
Jul 26, 2008
Messages
1,009
I've started the transition and ran into a few problems.

The spell seems to save the last spell cast on it which is great for Tricast. But it doesn't seem to save the last spell cast by the unit. I'll see if I can't input that data myself, couldn't be difficult. I understand the system about 3/4ths of the way.

Luckily my map uses Auto-Index and I see that it stores data in units. Though one problem I can't seem to solve is preventing the spell Double Cast Active from over-riding the last spell. It's pointless for the spell Double Cast to be cast again (And when cast it overrides the last spell with itself). I don't want mimic or tricast being cast again other.

This was my attempt at implementing it into the system.

JASS:
scope DoubleCastActive initializer Init

globals
    private unit TempUnit
endglobals

private function Enemy takes nothing returns boolean
    return IsUnitEnemy(GetEnumUnit(), GetOwningPlayer(TempUnit))
endfunction

private function Ally takes nothing returns boolean
    return IsUnitAlly(GetEnumUnit(), GetOwningPlayer(TempUnit))
endfunction

private function Actions takes unit c,unit d returns nothing
    local abilitydata dat = unitabilityhistory[ c ].in.entry[ unitabilityhistory[c].in.stored - 1 ]
    local integer i = dat.abilityid
    local real x = GetUnitX(c)
    local real x2 = dat.targetx
    local real y2 = dat.targety
    local group g = NewGroup()
    local boolexpr boo = Filter(function Enemy)
    local string id = dat.orderstring
    set d = CreateUnit(GetTriggerPlayer(),'hsor',x,GetUnitY(c),GetUnitFacing(c))
    call SetUnitState(d,UNIT_STATE_MANA,9999)
    call UnitAddAbility(d,i)
    call UnitApplyTimedLife(d,'BTLF',15.)
    
    if IsUnitAlly(dat.target, GetOwningPlayer(c)) then
        set boo = Filter(function Ally)
    endif
    
    if x2 == 0 and x != 0 then
        call IssueImmediateOrder(d,id)
    elseif dat.target != null then
        call GroupEnumUnitsInRange(g, x2, y2, 400, boo)
        set dat.target = FirstOfGroup(g)
        call IssueTargetOrder(d,id,dat.target)
    else
        call IssuePointOrder(d,id,x2+GetRandomReal(0,100),y2+GetRandomReal(0,100))
    endif
 
 call ReleaseGroup(g)
endfunction

private function Conditions takes nothing returns boolean
    if GetSpellAbilityId() == 'DblA' then
        call Actions(GetTriggerUnit(),null)
    endif
    return false
endfunction
 
//===========================================================================
public function Init takes nothing returns nothing
    local trigger t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(t,Condition(function Conditions))
endfunction

endscope
 
Level 14
Joined
Jul 26, 2008
Messages
1,009
I've started the transition and ran into a few problems.

The spell seems to save the last spell cast on it which is great for Tricast. But it doesn't seem to save the last spell cast by the unit. I'll see if I can't input that data myself, couldn't be difficult. I understand the system about 3/4ths of the way.

Luckily my map uses Auto-Index and I see that it stores data in units. Though one problem I can't seem to solve is preventing the spell Double Cast Active from over-riding the last spell. It's pointless for the spell Double Cast to be cast again (And when cast it overrides the last spell with itself). I don't want mimic or tricast being cast again other.

This was my attempt at implementing it into the system.

JASS:
scope DoubleCastActive initializer Init

globals
    private unit TempUnit
endglobals

private function Enemy takes nothing returns boolean
    return IsUnitEnemy(GetEnumUnit(), GetOwningPlayer(TempUnit))
endfunction

private function Ally takes nothing returns boolean
    return IsUnitAlly(GetEnumUnit(), GetOwningPlayer(TempUnit))
endfunction

private function Actions takes unit c,unit d returns nothing
    local abilitydata dat = unitabilityhistory[ c ].in.entry[ unitabilityhistory[c].in.stored - 1 ]
    local integer i = dat.abilityid
    local real x = GetUnitX(c)
    local real x2 = dat.targetx
    local real y2 = dat.targety
    local group g = NewGroup()
    local boolexpr boo = Filter(function Enemy)
    local string id = dat.orderstring
    set d = CreateUnit(GetTriggerPlayer(),'hsor',x,GetUnitY(c),GetUnitFacing(c))
    call SetUnitState(d,UNIT_STATE_MANA,9999)
    call UnitAddAbility(d,i)
    call UnitApplyTimedLife(d,'BTLF',15.)
    
    if IsUnitAlly(dat.target, GetOwningPlayer(c)) then
        set boo = Filter(function Ally)
    endif
    
    if x2 == 0 and x != 0 then
        call IssueImmediateOrder(d,id)
    elseif dat.target != null then
        call GroupEnumUnitsInRange(g, x2, y2, 400, boo)
        set dat.target = FirstOfGroup(g)
        call IssueTargetOrder(d,id,dat.target)
    else
        call IssuePointOrder(d,id,x2+GetRandomReal(0,100),y2+GetRandomReal(0,100))
    endif
 
 call ReleaseGroup(g)
endfunction

private function Conditions takes nothing returns boolean
    if GetSpellAbilityId() == 'DblA' then
        call Actions(GetTriggerUnit(),null)
    endif
    return false
endfunction
 
//===========================================================================
public function Init takes nothing returns nothing
    local trigger t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(t,Condition(function Conditions))
endfunction

endscope
 
Level 14
Joined
Nov 18, 2007
Messages
1,084
This is not really related to your problem:
You're using filter functions, right? Shouldn't GetEnumUnit() be GetFilterUnit()?
I think you should also replace GetTriggerPlayer() with GetOwningPlayer(c).
Also, it might be better if you use a global boolexpr instead of a local one. (You don't need to worry about destroying it later since it will get reused.)

OFF-TOPIC: Accidental double post?
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
I'm glad you like.

Also, for your other problem (excluding specific abilities from AbilityData) there are a few ways you could do this. Here's a relatively simple one.

JASS:
method freeze takes boolean flag returns nothing
    if(flag) then
        call DisableTrigger(abilevent)
    else
        call EnableTrigger(abilevent)
    endif
endmethod

Put this code into the unitabilityhistory struct. It will allow you to freeze the trigger that is responsible for updating and maintaining AbilityData. What this means is that you can freeze the system (for a specific unit) while you do all of your internal work, and then unfreeze it after you're done. Any abilities that are put into effect while the trigger if "frozen" will not be added to unitabilityhistory.
 
Level 14
Joined
Jul 26, 2008
Messages
1,009
Ahh I'm back. Is there a way to detect if the last skill used is an item skill? No way I can think of :X

My mimic spell takes the last skill used. I've recently realized it can take item skills, which can be disasterous. But other than that it's such an awesome skill, and I'd love for it to work properly.

Also for the solution you posted, I'm a bit fuzzy on exactly when to call this. Do I call it within the spell cast that I don't want to be memorized, and if so, at what point?

JASS:
private function Conditions
    if GetSpellAbilityId() == 'DblC' then
        call unitabilityhistory.freeze(true)
        call Actions(GetTriggerUnit(),null)
    endif
 return false
endfunction

Then unfreeze at end of spell? Wouldn't I need to do it before it's even been cast? I mean I've done preventative check casting for spells, but I'm not sure how I would unfreeze it if I did SPELL_CAST events.
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
Well you could register an item-use trigger after (meaning below, in the function script) the spell-effect event and then reference the last used spell in the item-use trigger, since it will trigger immediately after the spell-effect trigger due to being registered after. From there you can use a global boolean variable to determine whether the "last" spell used was an item spell, and reference the global else-where within the same thread execution period for accurate results.

Also, about your other question.

JASS:
method freeze takes boolean flag returns nothing

This is an instance method, so you're not going to be able to use unitabilityhistory.freeze(true), instead you would use a unit reference such as unitabilityhistory[GetTriggerUnit()].

I'm trying to think of perhaps a better way to freeze the trigger so that it can be excluded even when used on the spell-effect event. Currently it processes it's actions before this trigger, which eliminates a lot of functionality since you can't "omit" a spell from being detected.

I'll post a fix.

By the way, if I have the library trigger execute it's actions after your triggers that also means that if you reference the "last" ability cast, it will refer to the actual previous ability (not the current one), which actually works out better. You could always use abilitydata.create() anyways to reference the "current" ability's data.

Okay, it took a little while to get it fully functional there were some problems with AutoIndex using TimerUtils on preplaced units. By the way, this script will not require TimerUtils. It should allow you to call unitabilityhistory[GetTriggerUnit()].freeze() and the "current" ability will not be caught by the system. It should allow you to use it as you had recommended above by calling the freeze method at any point in time during the execution of the spell's trigger-actions. Actually screw the freeze() method, I'll include it in the API just in case but I'll include a method skip which will omit the current spell from unit ability history.

I still need to fix it up a bit, but here's some code:

JASS:
library UnitAbility requires AutoIndex, TimerUtils

    /*
                USE SCRIPT THAT IS POSTED BELOW THIS ONE
    */

    globals
    
        public constant integer     STORED_ABILITIES        = 3
        
    endglobals

    struct abilitydata
        readonly    unit     caster      = null
        readonly    unit     target      = null
        
        readonly    real     castx
        readonly    real     casty
        readonly    real     targetx
        readonly    real     targety
        readonly    real     castAngle
        readonly    real     castRange
        
        readonly    integer  abilityid
        readonly    string   orderstring
        
        method copy takes nothing returns thistype
            local thistype copy = allocate()
            
            set copy.caster     = caster
            set copy.castx      = castx
            set copy.casty      = casty
            set copy.target     = target
            set copy.targetx    = targetx
            set copy.targety    = targety
            set copy.abilityid  = abilityid
            set copy.orderstring= orderstring
            set copy.castAngle  = castAngle
            set copy.castRange  = castRange
            
            return copy
        endmethod
        
        static method create takes nothing returns thistype
            local thistype abil=allocate()
            
            set abil.caster=GetTriggerUnit()
            set abil.castx=GetUnitX(abil.caster)
            set abil.casty=GetUnitY(abil.caster)
            set abil.target=GetSpellTargetUnit()
            set abil.targetx=GetSpellTargetX()
            set abil.targety=GetSpellTargetY()
            set abil.abilityid=GetSpellAbilityId()
            set abil.orderstring=OrderId2String(GetUnitCurrentOrder(abil.caster))
            
            //misc spell-data
            set abil.castAngle=Atan2(abil.targety-abil.casty, abil.targetx-abil.castx) //in radians
            set abil.castRange=SquareRoot((abil.targetx-abil.castx)*(abil.targetx-abil.castx) + (abil.targety-abil.casty)*(abil.targety-abil.casty))
            
            return abil
        endmethod
    endstruct

    struct unitabilities
        readonly    abilitydata array   entry       [STORED_ABILITIES]
        readonly    integer             stored      = 0
        
        method onDestroy takes nothing returns nothing
            local integer i=0
            loop
                exitwhen(i==stored)
                if(entry[i]!=0) then
                    call entry[i].destroy()
                endif
                set i=i+1
            endloop
        endmethod
        
        method addAbility takes abilitydata dat returns boolean
            local integer i=0
            loop
                exitwhen(i==stored)
                if(entry[i]==dat) then
                    return false
                endif
                set i=i+1
            endloop
            if(stored==STORED_ABILITIES) then
                call entry[0].destroy()
                
                set entry[0]=entry[1]
                set entry[1]=entry[2]
                set entry[2]=dat
            else
                set entry[stored]=dat
                set stored=stored+1
            endif
            return true
        endmethod
    endstruct
    
    private struct savedata
        public  unit                source      = null
        public  unit                target      = null
        public  abilitydata         abilData
        
        static method create takes unit source, unit target, abilitydata data1 returns thistype
            local thistype data = allocate()
            
            set data.source     = source
            set data.target     = target
            set data.abilData   = data1
            
            return data
        endmethod
    endstruct
    
    struct unitabilityhistory //requires AutoIndex, TimerUtils
        public  unitabilities   out     
        public  unitabilities   in
    
        private trigger         abilevent   = CreateTrigger() 
        private timer           threadDelay = null
        
        private boolean         skipFlag    = false
        
        method freeze takes boolean flag returns nothing
            if flag then
                call EnableTrigger(abilevent)
            else
                call DisableTrigger(abilevent)
            endif
        endmethod
        method skip takes nothing returns nothing
            set skipFlag = true
        endmethod
        
        method onDestroy takes nothing returns nothing
            call out.destroy()
            call in.destroy()
            
            call DestroyTrigger(abilevent)
            if threadDelay != null then
                call ReleaseTimer(threadDelay)
            endif
        endmethod
    
        static method onAbilEventThreadDelay takes nothing returns nothing
            local savedata data = GetTimerData(GetExpiredTimer())
            
            call thistype[data.source].out.addAbility(data.abilData.copy())
            call thistype[data.target].in.addAbility(data.abilData.copy())
            
            set thistype[data.source].skipFlag = false
            call data.abilData.destroy()
            call ReleaseTimer(GetExpiredTimer())
        endmethod
        static method onAbilEvent takes nothing returns boolean
            local thistype data = thistype[GetTriggerUnit()]
            
            if not data.skipFlag then
                set data.threadDelay = NewTimer()
                
                call SetTimerData(data.threadDelay, savedata.create(GetTriggerUnit(), GetSpellTargetUnit(), abilitydata.create()))
                call TimerStart(data.threadDelay, 0, false, function thistype.onAbilEventThreadDelay)
            endif
            
            return false
        endmethod
    
        method onCreate takes nothing returns nothing
            set out = unitabilities.create()
            set in  = unitabilities.create()
            call TriggerRegisterUnitEvent(abilevent, me, EVENT_UNIT_SPELL_EFFECT) 
            call TriggerAddCondition(abilevent, Filter(function thistype.onAbilEvent)) 
        endmethod
    
        implement AutoCreate
        implement AutoDestroy
    endstruct

endlibrary

Okay, there was a small logic error in my code that would cause it to skip everything after the skip() method is used. Now it should work properly.

JASS:
library UnitAbility requires AutoIndex, TimerUtils

    globals
    
        public constant integer     STORED_ABILITIES        = 3
        
    endglobals

    struct abilitydata
        readonly    unit     caster      = null
        readonly    unit     target      = null
        
        readonly    real     castx
        readonly    real     casty
        readonly    real     targetx
        readonly    real     targety
        readonly    real     castAngle
        readonly    real     castRange
        
        readonly    integer  abilityid
        readonly    string   orderstring
        
        method copy takes nothing returns thistype
            local thistype copy = allocate()
            
            set copy.caster     = caster
            set copy.castx      = castx
            set copy.casty      = casty
            set copy.target     = target
            set copy.targetx    = targetx
            set copy.targety    = targety
            set copy.abilityid  = abilityid
            set copy.orderstring= orderstring
            set copy.castAngle  = castAngle
            set copy.castRange  = castRange
            
            return copy
        endmethod
        
        static method create takes nothing returns thistype
            local thistype abil=allocate()
            
            set abil.caster=GetTriggerUnit()
            set abil.castx=GetUnitX(abil.caster)
            set abil.casty=GetUnitY(abil.caster)
            set abil.target=GetSpellTargetUnit()
            set abil.targetx=GetSpellTargetX()
            set abil.targety=GetSpellTargetY()
            set abil.abilityid=GetSpellAbilityId()
            set abil.orderstring=OrderId2String(GetUnitCurrentOrder(abil.caster))
            
            //misc spell-data
            set abil.castAngle=Atan2(abil.targety-abil.casty, abil.targetx-abil.castx) //in radians
            set abil.castRange=SquareRoot((abil.targetx-abil.castx)*(abil.targetx-abil.castx) + (abil.targety-abil.casty)*(abil.targety-abil.casty))
            
            return abil
        endmethod
    endstruct

    struct unitabilities
        readonly    abilitydata array   entry       [STORED_ABILITIES]
        readonly    integer             stored      = 0
        
        method onDestroy takes nothing returns nothing
            local integer i=0
            loop
                exitwhen(i==stored)
                if(entry[i]!=0) then
                    call entry[i].destroy()
                endif
                set i=i+1
            endloop
        endmethod
        
        method addAbility takes abilitydata dat returns boolean
            local integer i=0
            loop
                exitwhen(i==stored)
                if(entry[i]==dat) then
                    return false
                endif
                set i=i+1
            endloop
            if(stored==STORED_ABILITIES) then
                call entry[0].destroy()
                
                set entry[0]=entry[1]
                set entry[1]=entry[2]
                set entry[2]=dat
            else
                set entry[stored]=dat
                set stored=stored+1
            endif
            return true
        endmethod
    endstruct
    
    private struct savedata
        public  unit                source      = null
        public  unit                target      = null
        public  abilitydata         abilData
        
        static method create takes unit source, unit target, abilitydata data1 returns thistype
            local thistype data = allocate()
            
            set data.source     = source
            set data.target     = target
            set data.abilData   = data1
            
            return data
        endmethod
    endstruct
    
    struct unitabilityhistory //requires AutoIndex, TimerUtils
        public  unitabilities   out     
        public  unitabilities   in
    
        private trigger         abilevent   = CreateTrigger() 
        private timer           threadDelay = null
        
        private boolean         skipFlag    = false
        
        method freeze takes boolean flag returns nothing
            if flag then
                call EnableTrigger(abilevent)
            else
                call DisableTrigger(abilevent)
            endif
        endmethod
        method skip takes nothing returns nothing
            set skipFlag = true
        endmethod
        
        method onDestroy takes nothing returns nothing
            call out.destroy()
            call in.destroy()
            
            call DestroyTrigger(abilevent)
            if threadDelay != null then
                call ReleaseTimer(threadDelay)
            endif
        endmethod
    
        static method onAbilEventThreadDelay takes nothing returns nothing
            local savedata data = GetTimerData(GetExpiredTimer())
            
            call thistype[data.source].out.addAbility(data.abilData.copy())
            call thistype[data.target].in.addAbility(data.abilData.copy())
            
            call data.abilData.destroy()
            call ReleaseTimer(GetExpiredTimer())
        endmethod
        static method onAbilEvent takes nothing returns boolean
            local thistype data = thistype[GetTriggerUnit()]
            
            if not data.skipFlag then
                set data.threadDelay = NewTimer()
                
                call SetTimerData(data.threadDelay, savedata.create(GetTriggerUnit(), GetSpellTargetUnit(), abilitydata.create()))
                call TimerStart(data.threadDelay, 0, false, function thistype.onAbilEventThreadDelay)
            endif
            set data.skipFlag = false
            
            return false
        endmethod
    
        method onCreate takes nothing returns nothing
            set out = unitabilities.create()
            set in  = unitabilities.create()
            call TriggerRegisterUnitEvent(abilevent, me, EVENT_UNIT_SPELL_EFFECT) 
            call TriggerAddCondition(abilevent, Filter(function thistype.onAbilEvent)) 
        endmethod
    
        implement AutoCreate
        implement AutoDestroy
    endstruct

endlibrary
 
Last edited:
Status
Not open for further replies.
Top