HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

Interesting "Node," Ability Trouble

09-06-2007, 04:39 AM#1
Karawasa
Below you will find an ability that uses "nodes," to figure out bonuses to be applied to units in a linking chain. What happens, is that one tower will link into another via a channel spell, and add its attack damage (in the form of an item attack bonus) to the target tower. Say we have three towers, 1 links into 2, and 2 then has double damage, then 2 links into 3, now 3 has triple damage and is the sole attacking tower (since the other two are channeling).

Everything in this trigger works like a charm, except for detecting when to cut off towers from the node. When issued a stop order in game, everything works as it should. However, if the unit is stopped in another way, such as issuing a stop order via triggers, being killed, or upgrading to another tower (thereby stopping the channel), the whole thing fucks up.

I quote the problem:

Quote:
I seem to get double frees of Prism, towers randomly(ones that were there, and new ones) are a part of each others "series", when they clearly are not, and the bonuses stop being passed around correctly

Here is the code:

Collapse JASS:
//Implementation: *create and active unit ally targetable ability to be detected when cast, based off channel
//                *copy over my damage dummy ability for the towers and this trigger
//                *configure options given and check it up! 
//Documentation: Works well, neat ability I think.  You may want to create an effect on casting tower or something 
//               when cast in the object data.

scope Prism 

globals
    //config. options:
    private constant integer tower1_id = 'h001' //rawcode of level 1 tower
    private constant integer tower2_id = 'h000' //rawcode of level 2 tower
    private constant integer abil_id = 'A004' //rawcode of ability
    private constant integer dum1_id = 'A002' //rawcode of dummy damage level 1 ability
    private constant integer dum2_id = 'A003' //rawcode of dummy damage level 2 ability 
    private constant string lightnin = "DRAB" //string of wanted lightnign between towers
    private constant string wrong1 = "You may only cast this on Level 1 Reflection Towers." //simerror for level 1 towers
    private constant string wrong2 = "You may only cast this on Level 2 Reflection Towers." //simerror for level 2 towers
    private constant string wrong3 = "You may not cast this on towers you are in series with." //simerror for casting on towers already in your series
    
    //needed variables:
    private sound SimError = null
    private unit U = null
endglobals

//Get correct dummy damage ability    
private function get_abil takes unit tower returns integer
    if GetUnitTypeId(tower)==tower1_id then
        return dum1_id
    else
        return dum2_id
    endif
endfunction
//needed filter
private function Prism_Filter takes nothing returns boolean
    return GetWidgetLife(GetFilterUnit())>.405 and GetOwningPlayer(GetFilterUnit())==GetOwningPlayer(U) and GetFilterUnit()!=U
endfunction

//Needed struct
struct Prism
    private Prism target  = 0
    private unit prismUnit // Isn't used atm, you want to use this in add/removeBonus
    private integer selfBonus = 0 // What bonus this Prism has
    private integer giveBonus = 1 // What bonus it gives, this should probably change with level...
    lightning L = null
    unit targ
     
     //destroy the struct
     private method stopBeingAPrism takes nothing returns nothing
         if this!=0 then
             call this.destroy()
         endif
     endmethod
     //returns the new struct
     public static method new takes unit whichUnit returns Prism
            local Prism p = Prism.create()

            set p.prismUnit = whichUnit
            return p
     endmethod
     //...... 
     public method castPrismOn takes Prism whatPrism returns nothing
           call UnitRemoveAbility(this.prismUnit, get_abil(this.prismUnit))
           call whatPrism.addBonus(this.selfBonus + this.giveBonus)
           set this.target  = whatPrism            
     endmethod 
     //unit stops, do some stuff 
     public method stopChannelPrism takes nothing returns nothing
            call this.target.removeBonus(this.selfBonus + this.giveBonus)
            set this.target = 0
            if this.selfBonus != 0 then
                call UnitAddAbility(this.prismUnit, get_abil(this.prismUnit))
                call SetUnitAbilityLevel(this.prismUnit, get_abil(this.prismUnit), this.selfBonus)
            else
                call this.stopBeingAPrism()
            endif
     endmethod
     //add bonuses to tower
     public method addBonus takes integer bonus returns nothing
           set this.selfBonus = this.selfBonus + bonus
           if this.target == 0 then 
               call UnitAddAbility(this.prismUnit, get_abil(this.prismUnit))
               call SetUnitAbilityLevel(this.prismUnit, get_abil(this.prismUnit), this.selfBonus)
           else
               call this.target.addBonus(bonus)
          endif    
     endmethod
     //remove bonuses from towers
     public method removeBonus takes integer bonus returns nothing
          set this.selfBonus = this.selfBonus - bonus
          if this.selfBonus==0 then
              call UnitRemoveAbility(this.prismUnit,get_abil(this.prismUnit))
              if this.target == 0 then
                  call this.stopBeingAPrism()
              endif
          else
              call SetUnitAbilityLevel(this.prismUnit,get_abil(this.prismUnit),this.selfBonus)
          endif
           if this.target != 0 then
               call this.target.removeBonus(bonus)
           endif
     endmethod
     //checks if two unts are in the same series
     public method isPrismInChain takes Prism whatPrism returns boolean
        if this == whatPrism then
            return true
        elseif this.target == 0 then
            return false
        endif
        return this.target.isPrismInChain(whatPrism)
     endmethod    
endstruct     

//filter for casting correct ability
function Trig_Prism_Conditions takes nothing returns boolean     
    return GetSpellAbilityId()==abil_id 
endfunction
//end the prism for the tower when it stops channeling
private function Prism_End takes nothing returns boolean
    local trigger trig = GetTriggeringTrigger()    
    local Prism prism = GetData(trig)

    call DestroyLightning(prism.L)
    call prism.stopChannelPrism()
    call DestroyTrigger(trig)
    
    set trig = null
    return false
endfunction 
//takes the unit and returns a struct for it
private function Unit2Prism takes unit u returns Prism
    local Prism p = Prism( GetData(u) )

    if p == 0 then
         set p = Prism.new(u)
         call SetData(u, p)
    endif
   return p    
endfunction     
//start effects for caster and target tower
function Trig_Prism_Actions takes nothing returns nothing
    local unit cast = GetTriggerUnit()
    local unit targ = GetSpellTargetUnit() 
    local trigger trig = CreateTrigger()
    
    local Prism prism = Unit2Prism(cast)
    call prism.castPrismOn(Unit2Prism(targ))
    call TriggerRegisterUnitEvent(trig,cast,EVENT_UNIT_SPELL_ENDCAST)
    call TriggerRegisterUnitEvent(trig,cast,EVENT_UNIT_DEATH)
    call TriggerRegisterUnitEvent(trig,cast,EVENT_UNIT_UPGRADE_FINISH)
    call TriggerAddCondition(trig,Condition(function Prism_End))
    call SetData(trig,prism)       
    
    set prism.L = AddLightningEx(lightnin,true,GetUnitX(targ),GetUnitY(targ),150.,GetUnitX(cast),GetUnitY(cast),150.)
    set prism.targ = targ
       
    set cast = null
    set targ = null
    set trig = null
endfunction   

//Simrerror message creator for illegal casts 
private function Prism_SimError takes player ForPlayer, string msg returns nothing
    if SimError==null then
        set SimError=CreateSoundFromLabel( "InterfaceError",false,false,false,10,10)
    endif
    if (GetLocalPlayer() == ForPlayer) then
        call ClearTextMessages()
        call DisplayTimedTextToPlayer( ForPlayer, 0.52, -1.00, 2.00, "|cffffcc00"+msg+"|r" )
        call StartSound( SimError )
    endif
endfunction
//Stop tower if it cannot cast it where it is trying to
function Prism_Check takes nothing returns boolean
    local unit u = GetTriggerUnit()
    local unit targ = GetSpellTargetUnit()
    
    if GetSpellAbilityId()!=abil_id then
        set u = null
        set targ = null
        return false
    endif
    if Unit2Prism(targ).isPrismInChain(Unit2Prism(u)) or u==targ or OrderId2String(GetUnitCurrentOrder(u))=="channel" then
        call PauseUnit(u,true)
        call IssueImmediateOrder(u,"stop")
        call PauseUnit(u,false)
        call Prism_SimError(GetOwningPlayer(u),wrong3)
        set u = null
        set targ = null
        return false
    endif
    if GetUnitTypeId(u)==tower1_id and GetUnitTypeId(targ)!=tower1_id then
        call PauseUnit(u,true)
        call IssueImmediateOrder(u,"stop")
        call PauseUnit(u,false)
        call Prism_SimError(GetOwningPlayer(u),wrong1)
        set u = null
        set targ = null
        return false
    elseif GetUnitTypeId(u)==tower2_id and GetUnitTypeId(targ)!=tower2_id then
        call PauseUnit(u,true)
        call IssueImmediateOrder(u,"stop")
        call PauseUnit(u,false)
        call Prism_SimError(GetOwningPlayer(u),wrong2)
        set u = null
        set targ = null
        return false
    endif

    set u = null
    set targ = null
    return false
endfunction 

//in case a tower is sold or is upgraded
function Prism_Death takes nothing returns boolean
    local unit dead = GetTriggerUnit()
    local unit u
    local group g
    local Prism p
    
if GetUnitTypeId(dead)==tower1_id or GetUnitTypeId(dead)==tower2_id then
    set g = CreateGroup()
    set U = dead
    call GroupEnumUnitsInRect(g,bj_mapInitialPlayableArea,Condition(function Prism_Filter))
    //call BJDebugMsg(I2S(CountUnitsInGroup(g)))
    loop
        set u = FirstOfGroup(g)
        exitwhen u==null
        call GroupRemoveUnit(g,u)
                    
        set p = Prism( GetData(u) )
        if p.targ==dead then
            //call BJDebugMsg("Found one")
            call PauseUnit(u,true)
            call IssueImmediateOrder(u,"stop")
            call PauseUnit(u,false)
        endif
    endloop
    
    call DestroyGroup(g)
    set g = null
endif
    
    set dead = null
    return false
endfunction

endscope

//==== Init Trigger Prism ====
function InitTrig_Prism takes nothing returns nothing
    local trigger trig = CreateTrigger()
    
    call TriggerRegisterAnyUnitEventBJ(trig,EVENT_PLAYER_UNIT_SPELL_CHANNEL )
    call TriggerAddCondition(trig, Condition(function Prism_Check))
    
    set trig = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(trig,EVENT_PLAYER_UNIT_UPGRADE_FINISH)
    call TriggerRegisterAnyUnitEventBJ(trig,EVENT_PLAYER_UNIT_DEATH)
    call TriggerAddCondition(trig,Condition(function Prism_Death))
    
    set gg_trg_Prism = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(gg_trg_Prism,EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition(gg_trg_Prism, Condition(function Trig_Prism_Conditions))
    call TriggerAddAction(gg_trg_Prism, function Trig_Prism_Actions)
    
    set trig = null
endfunction 
09-06-2007, 05:22 AM#2
Strilanc
I think your problem is in Prism_Check. One of the if blocks ends then starts another one where there should clearly be an elseif instead. Also, the actions are all the same inside the IFs. Use a boolean flag instead.

In other news, try these handy tips:

- Define a global constant integer nill = 0, and use that instead of '0 for null'. "set this.target = nill" conveys a lot more meaning than "set this.target = 0".

- selfBonus and giveBonus are completely misleading variable names. I kept thinking their meaning was the other way around (bonus given from other towers, bonus from self)

- Whenever you're writing a structure/object, assume someone stupid is going to try to use it. Validate all input, make "sensitive" methods private, etc. It makes a huge difference when you don't have to worry about passing negative numbers or whatnot by accident.
09-06-2007, 06:09 AM#3
Alexander244
Quote:
Originally Posted by Karawasa
However, if the unit is stopped in another way, such as issuing a stop order via triggers, being killed, or upgrading to another tower (thereby stopping the channel), the whole thing fucks up.

You could always periodically check the status of the towers; they are not null or dead, their order is channel etc.
09-06-2007, 06:29 AM#4
Karawasa
@Strilanc

I don't think so, it seems the problem is probably somewhere within the prism_death, as it is not cutting off a tower from the node properly when it is sold/killed/upgraded.

@Alexander244

That would require much more computational power, especially since this can span many, many towers and many different players too.
09-06-2007, 01:12 PM#5
emjlr3
in Prism_Check, the code is not really any less optimized(possibly a hair, I don't care, it is easier for me to read and play with), the code is just longer because of it

regardless, I end the thread in each if statement, therefore no further ones should, or will run after the previous has

@Alex, that still does not negate the need for forcing towers to stop, which is the cause of all the bugs...nothing should be different from stopping in game, or stopping through my trigger, where I simply pause/stop/unpause (have also tried just the order)

however, from this stems all the problems, whereas stop yourself in game, and there are no bugs to speak of....
09-06-2007, 01:46 PM#6
Rising_Dusk
Quote:
@Alexander244

That would require much more computational power, especially since this can span many, many towers and many different players too.
No it wouldn't. Load every tower to check into a group and execute a single ForGroup() call in a single timer callback. One timer running for every tower matching this spread damage thing for the ENTIRE map is not adding any significant extra computational requirements.

You'd just get them in the ForGroup() callback with the GetEnumUnit() call and then check if their current order is equal to whatever the order of the channeling ability is. As far as I am concerned, you're going about this whole code the wrong way. I would never have gone about using all that silly and superfluous stuff for it.
09-06-2007, 02:08 PM#7
emjlr3
that is hardly the whole spell, you would still need to use the method we did to disperse bonuses

regardless of whether a timer is running, or if you just detect end_cast (which is a good bit more optimized)

in any case, again, that is hardly the problem now is it
09-06-2007, 03:19 PM#8
Rising_Dusk
Actually, if you did it my way you never would run into the death, upgrade, stop problem; it would be a completely irrelevant issue. As far as I can tell from posts here that IS the problem.

And no, I wouldn't have to use your method of storing the bonus to the struct. You could use powers of two in the damage bonus to create any damage bonus you want. If you want it by percent, that's even easier (Since then it would stack linearly). If you did it by this method, you'd add an ability to a unit based off item damage bonus (Or command aura in a disabled spellbook, or whatever) and set the level of that ability to whatever would create the correct numerical damage bonus. Then you'd be able to get that damage bonus without any attachment, since you could then back calculate from having the ability and the level of it.
09-06-2007, 06:32 PM#9
Vulkarus
Rising Dusk is right, his way is much more accurate, speedy, and just looks better in whole. One thing though, I wouldn't use the command aura to do the bonus. This is because you may have towers stronger than the one they are giving damage to giving you less damage. Also, if you have 1 fully upgraded tower and 12 smaller ones and have them all use the ability on that 1 tower it does 13x its dmg, when it should be 12x the smaller ones + its own.

Anyway, to use rising dusk's method you would probably need a group for each of the series so you may still want to use vJass to do it. You would need a trigger to add them to or create the group which would check if it is already in a group, and if it is add it to that group instead of making a new one. for the struct you would need a unit and a group. the unit being what is getting the bonuses and the group being all the units that are giving bonuses. You would also need a trigger to remove them from the group which should be, as rising_dusk said run by a timer that checks every .x seconds whether they are casting the spell. Because we would only want 1 timer for all the groups you may want to store that struct in a special global array and have a global integer that is the number of instances that way you know when to pause/start the timer like in vexorian's solar flare spells in vJass example tutorial.