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

[Solved] a bug with activable barrage

Status
Not open for further replies.
Level 9
Joined
Dec 12, 2007
Messages
489
please help,

I attempt to recreate an ability, simply an activable barrage (for those familiar with Dota, it's like Medusa's Split Shot)

It was based off Defend (to achieve damage factor) ability which gives hidden spellbook containing Barrage when activated.

I managed to finish the ability, it was working as intended until I found some intriguing bug happening after few testing.

After few minutes (not specifically after certain time or after some condition met), the Hero instantly dies upon activating said ability.

this death is not attributed to any killing unit or player, and there is no trigger that calls KillUnit().

I've searched for possible known bug regarding Defend, Barrage, and Spellbook ability.
I found a related thread that said using spellbook to abuse changing max hp with item ability might kill said unit if certain condition is met (involving upgrades too though my ability didn't use any hp changing effect that can trigger same effect).

I've used BJDebugMsg to at least pinpoint where the problem might be,
for start, the debug message display correct behavior on any case (turn on/off, death while ability on/off, ability is given at correct level),
and when the bug starts to happen, it displays that the unit is issued an order to "undefend" twice on activation before dying (I assume that's a trait of having the ability and using UnitDex library that uses undefend) which didn't happen before.

below is the trigger of said ability, I have tried using few different method to handle add/removing Barrage to avoid crash due to removing ability from dead unit.


JASS:
scope SplitShot initializer init

    globals
        private constant integer        ABIL_ID     =   'A01Y'
        private constant integer        DUMMY_ID_1  =   'D00N'
        private constant integer        DUMMY_ID_2  =   'D00O'
        private constant real           INTERVAL    =   0.03125

        private constant string         ORDER_ON    =   "defend"
        private constant string         ORDER_OFF   =   "undefend"
        private          trigger        TRIG
        private          Table          SS          =   0
        private          integer        ORDER_ON_ID
        private          integer        ORDER_OFF_ID
    endglobals
   
    private struct Data
        unit hero
        timer t
        integer lv
        integer id
        boolean onSS
    endstruct

    private function onLoop takes nothing returns nothing
        local timer t = GetExpiredTimer()
        local Data data = GetTimerData(t)
       
        // check if the Hero still exist and not repicked or removed from game
        if HERO[data.id] == null then
//            call UnitRemoveAbility(data.hero,DUMMY_ID_1)
            call SS.remove(GetUnitId(data.hero))
            set data.hero = null
            set data.t = null
            call data.destroy()
            call ReleaseTimer(t)
            set t = null
            return
        endif

        // check if Hero died, automatically order off
        if GetWidgetLife(data.hero) < .405 then
            set data.onSS = false
            set t = null
            return
        endif
       
        if not data.onSS or GetUnitAbilityLevel(data.hero,'DOOM') > 0 then
//            if GetWidgetLife(data.hero) > .405 then
//                call UnitRemoveAbility(data.hero,DUMMY_ID_1)
                call IssueImmediateOrder(data.hero,ORDER_OFF)
                call PauseTimer(t)
//            endif
        endif
       
        set t = null
    endfunction
   
    private function onOrder takes nothing returns boolean
        local unit u = GetTriggerUnit()
        local integer orderid = GetIssuedOrderId()
        local Data data
               
        if orderid == ORDER_ON_ID then
//            if GetWidgetLife(u) > .405 then
                set data = SS[GetUnitId(u)]
                set data.onSS = true
                call UnitAddAbility(data.hero,DUMMY_ID_1)
                call SetUnitAbilityLevel(data.hero,DUMMY_ID_1,data.lv)
                call TimerStart(data.t,INTERVAL,true,function onLoop)
//            endif
           
        elseif orderid == ORDER_OFF_ID then
//            if GetWidgetLife(u) > .405 then
                set data = SS[GetUnitId(u)]
                set data.onSS = false
                call UnitRemoveAbility(data.hero,DUMMY_ID_1)
                call PauseTimer(data.t)
//            endif
        endif
       
        set u = null
        return false
    endfunction

    private function onLearn takes nothing returns nothing
        local unit u = GetTriggerUnit()
        local Data data

        if GetLearnedSkill() == ABIL_ID then
            if GetLearnedSkillLevel() == 1 then
                call TriggerRegisterUnitEvent(TRIG,u,EVENT_UNIT_ISSUED_ORDER)
                set data = Data.create()
                set data.hero = u
                set data.t    = NewTimer()
                set data.lv   = 1
                set data.id   = GetPlayerId(GetOwningPlayer(u))
                set data.onSS = false
                call SetTimerData(data.t,data)
                set SS[GetUnitId(u)] = data

            else
                set data = SS[GetUnitId(u)]
                set data.lv = GetUnitAbilityLevel(u,ABIL_ID)

            endif
        endif
       
        set u = null
    endfunction

    private function init takes nothing returns nothing
        local integer i = 0

        //disable ability so it doesn't show up in the command card
        loop
            exitwhen i > (MAX_PLAYERS+1)
            call SetPlayerAbilityAvailable(Player(i),DUMMY_ID_1,false)
            set i = i + 1
        endloop
       
        set TRIG = CreateTrigger()
        call RegisterPlayerUnitEvent(EVENT_PLAYER_HERO_SKILL, function onLearn)       
        call TriggerAddCondition(TRIG,Condition(function onOrder))
        set SS = Table.create()

        call XE_PreloadAbility(ABIL_ID)
        call XE_PreloadAbility(DUMMY_ID_1)
        call XE_PreloadAbility(DUMMY_ID_2)
       
        set ORDER_ON_ID  = OrderId(ORDER_ON)
        set ORDER_OFF_ID = OrderId(ORDER_OFF)
    endfunction

endscope



JASS:
scope SplitShot initializer init

    globals
        private constant integer        ABIL_ID     =   'A01Y'
        private constant integer        DUMMY_ID_1  =   'D00N'
        private constant integer        DUMMY_ID_2  =   'D00O'
        private constant real           INTERVAL    =   0.03125

        private constant string         ORDER_ON    =   "defend"
        private constant string         ORDER_OFF   =   "undefend"
        private          integer        ORDER_ON_ID
        private          integer        ORDER_OFF_ID
    endglobals
   
    private function switchOff takes nothing returns nothing
        local unit u = GetDyingUnit()
        if GetUnitAbilityLevel(u,ABIL_ID) > 0 then
            call UnitRemoveAbility(u,DUMMY_ID_1)
        endif
        set u = null
    endfunction

    private function onOrder takes nothing returns nothing
        local unit v = GetTriggerUnit()
        if GetUnitAbilityLevel(v,ABIL_ID) > 0 and GetIssuedOrderId()==ORDER_ON_ID and GetUnitAbilityLevel(v,DUMMY_ID_1) == 0 then
            call UnitAddAbility(v,DUMMY_ID_1)
            call SetUnitAbilityLevel(v,DUMMY_ID_2,GetUnitAbilityLevel(v,ABIL_ID))
        endif
        if GetUnitAbilityLevel(v,ABIL_ID) > 0 and GetIssuedOrderId()==ORDER_OFF_ID and GetUnitAbilityLevel(v,DUMMY_ID_1) > 0 then
            call UnitRemoveAbility(v,DUMMY_ID_1)
        endif
        set v = null
    endfunction

    private function init takes nothing returns nothing
        local integer i = 0

        //disable ability so it doesn't show up in the command card
        loop
            exitwhen i > (MAX_PLAYERS+1)
            call SetPlayerAbilityAvailable(Player(i),DUMMY_ID_1,false)
            set i = i + 1
        endloop
       
        call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_ORDER, function onOrder)
        call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_DEATH, function switchOff)

        call XE_PreloadAbility(ABIL_ID)
        call XE_PreloadAbility(DUMMY_ID_1)
        call XE_PreloadAbility(DUMMY_ID_2)
       
        set ORDER_ON_ID  = OrderId(ORDER_ON)
        set ORDER_OFF_ID = OrderId(ORDER_OFF)
    endfunction

endscope



JASS:
scope SplitShot initializer init

    globals
        private constant integer        ABIL_ID     =   'A01Y'
        private constant integer        DUMMY_ID_1  =   'D00N'
        private constant integer        DUMMY_ID_2  =   'D00O'
        private constant real           INTERVAL    =   0.03125

        private constant string         ORDER_ON    =   "defend"
        private constant string         ORDER_OFF   =   "undefend"
        private          trigger        TRIG
        private          Table          SS          =   0
        private          integer        ORDER_ON_ID
        private          integer        ORDER_OFF_ID
    endglobals
   
    private struct Data
        unit hero
        timer t
        integer lv
        integer id
        boolean onSS
       
        method onDestroy takes nothing returns nothing
            call SS.remove(GetUnitId(.hero))
            set .hero = null
            set .t = null
        endmethod
    endstruct

    private function onLoop takes nothing returns nothing
        local timer t = GetExpiredTimer()
        local Data data = GetTimerData(t)
       
        // check if the Hero still exist and not repicked or removed from game
        if HERO[data.id] == null then
            call data.destroy()
            call ReleaseTimer(t)
            set t = null
            return
        endif

        // check if Hero died
        if GetWidgetLife(data.hero) < .405 then
            set data.onSS = false
            set t = null
            return
        endif
       
        if not data.onSS or GetUnitAbilityLevel(data.hero,'DOOM') > 0 then
            if GetUnitAbilityLevel(data.hero,DUMMY_ID_1) > 0 then
                call BJDebugMsg(GetUnitName(data.hero)+" remove SplitShot1 " + I2S(GetUnitAbilityLevel(data.hero,DUMMY_ID_1)))
                call BJDebugMsg(GetUnitName(data.hero)+" remove SplitShot2 " + I2S(GetUnitAbilityLevel(data.hero,DUMMY_ID_2)))
                call UnitRemoveAbility(data.hero,DUMMY_ID_1)
                call BJDebugMsg(GetUnitName(data.hero)+" removed SplitShot1 " + I2S(GetUnitAbilityLevel(data.hero,DUMMY_ID_1)))
                call BJDebugMsg(GetUnitName(data.hero)+" removed SplitShot2 " + I2S(GetUnitAbilityLevel(data.hero,DUMMY_ID_2)))
            endif
        endif
       
        set t = null
    endfunction
   
    private function onOrder takes nothing returns boolean
        local unit u = GetTriggerUnit()
        local integer orderid = GetIssuedOrderId()
        local Data data
               
        if orderid == ORDER_ON_ID then
            set data = SS[GetUnitId(u)]
            set data.onSS = true
            call UnitAddAbility(data.hero,DUMMY_ID_1)
            call BJDebugMsg(GetUnitName(data.hero)+" on SplitShot " + I2S(GetUnitAbilityLevel(data.hero,DUMMY_ID_2)))

            call SetUnitAbilityLevel(data.hero,DUMMY_ID_2,data.lv)
           
            call BJDebugMsg(GetUnitName(data.hero)+" on SplitShot " + I2S(GetUnitAbilityLevel(data.hero,DUMMY_ID_2)))
           
        elseif orderid == ORDER_OFF_ID then
            set data = SS[GetUnitId(u)]
            set data.onSS = false
            call BJDebugMsg(GetUnitName(data.hero)+" off SplitShot " + I2S(GetUnitAbilityLevel(data.hero,DUMMY_ID_2)))
        endif
       
        set u = null
        return false
    endfunction

    private function onLearn takes nothing returns nothing
        local unit u = GetTriggerUnit()
        local Data data

        if GetLearnedSkill() == ABIL_ID then
            if GetLearnedSkillLevel() == 1 then
                call TriggerRegisterUnitEvent(TRIG,u,EVENT_UNIT_ISSUED_ORDER)
                set data = Data.create()
                set data.hero = u
                set data.t    = NewTimer()
                set data.lv   = 1
                set data.id   = GetPlayerId(GetOwningPlayer(u))
                set data.onSS = false
                set SS[GetUnitId(u)] = data
                call SetTimerData(data.t,data)
                call TimerStart(data.t,INTERVAL,true,function onLoop)

                call BJDebugMsg(GetUnitName(data.hero)+" learn SplitShot " + I2S(GetUnitAbilityLevel(u,ABIL_ID)))
            else
                set data = SS[GetUnitId(u)]
                set data.lv = GetUnitAbilityLevel(u,ABIL_ID)

                call BJDebugMsg(GetUnitName(data.hero)+" learn SplitShot " + I2S(GetUnitAbilityLevel(u,ABIL_ID)))
            endif
        endif
       
        set u = null
    endfunction

    private function init takes nothing returns nothing
        local integer i = 0

        //disable ability so it doesn't show up in the command card
        loop
            exitwhen i > (MAX_PLAYERS+1)
            call SetPlayerAbilityAvailable(Player(i),DUMMY_ID_1,false)
            set i = i + 1
        endloop
       
        set TRIG = CreateTrigger()
        call RegisterPlayerUnitEvent(EVENT_PLAYER_HERO_SKILL, function onLearn)       
        call TriggerAddCondition(TRIG,Condition(function onOrder))
        set SS = Table.create()

        call XE_PreloadAbility(ABIL_ID)
        call XE_PreloadAbility(DUMMY_ID_1)
        call XE_PreloadAbility(DUMMY_ID_2)
       
        set ORDER_ON_ID  = OrderId(ORDER_ON)
        set ORDER_OFF_ID = OrderId(ORDER_OFF)
    endfunction

endscope


Attempt #2 is using the simple method used in early version of Dota's Medusa's Split Shot (found in the unprotected old version) which hopefully solves the problem (it didn't)

all attempt works on testing and display the same problem after further testing.

for now, I assume this got something to do with UnitDex's undefend.
I haven't tried using different base ability (e.g. Immolation or Mana Shield) though it won't give the damage factor feature that comes with Defend ability.

anyone has similar experience on before? or know what causing this?
or is there other workaround to achieve the same result?

thank you in advance.
 
Level 9
Joined
Dec 12, 2007
Messages
489
It seems that every time a spell is added to the unit, all spells are first removed from it (internally, this does not trigger orders), then added again. This means that if your unit has a -1000 HP ability but also a +2000 HP ability, if the + HP ability is removed first, it will die if it has less than 0 max life at one point, if the -HP ability is removed first, it won't.

See if you have anything like that in your code.
 
Level 9
Joined
Dec 12, 2007
Messages
489
By the way, what is the damage factor of Defend (sustaining or dealing out?), And what did it do in your spell?

for this case, the damage output (field Damage Dealt - Def2), 1.00 for 100% damage.
by inputting less than 1.00, once activated, the damage output on anything considered attack (auto attack, barrage, critical strike) is reduced (multiplied by that factor),
an ideal method to balance Barrage.

Defend offer not just attack damage output manipulation, including manipulating damage taken, reducing magic damage taken, deflect damage, even altering attack speed and movement speed without buff and being activable.

Barrage alone will always hit at minimum 3 units at normal attack damage no matter how we change the ability data,
using damage detection is an overkill and difficult to do (I found that someone actually made this custom Barrage in Hive or WC3c),
using dummy aura or damage reducing ability will look ugly on the UI (negative number or another buff),
so the best option is using Defend or Elune's Grace (almost same field, but passive not activable)
 
Level 9
Joined
Dec 12, 2007
Messages
489
I've done a decent amount of testing with Defend myself; have you found (as I did) about how it only affects damage from Piercing sources?
was it only on incoming piercing damage?
because in my test, 60% damage dealt on Hero (Hero damage, about 100 attack damage) yield less than 60 output damage (factoring target's armor),

now this concerns me, I will do further test soon, will report soon, thank you.
 
Doesn't the defend tooltip state that it only reduces damage from Piercing sources?

Activate to have a <Adef,DataF1>% chance to reflect Piercing attacks upon the source, and to take only <Adef,DataA1,%>% of the damage from attacks that are not reflected. While Defend is active, movement is reduced to <Adef,DataC1,%>% of normal speed.

Although Arcanite Shield says it reduces all ranged damage taken, it still mentions Piercing in it's Data field "Data - Deflect Damage Taken (Piercing)".

EDIT: I guess you could interpret "attacks that are not reflected" as any non-piercing attack and the unreflected piercing attacks. That's not how I thought it worked though :S

The shield only mentions ranged attacks though.

Reduces damage from ranged attacks to <AIdd,DataA1,%>%. Also increases the Hero's armor by <AId5,DataA1> when worn.
 
I will never understand why people are pondering things that can be tested in game so easily. Have you tried actually testing any of that? Why are you theorycrafting based on tooltips?

Ummm... sorry I guess? I just thought the spell always worked that way, and it never crossed my mind people would have a different interpretation of the spell. So I just brought up the tooltips because they seemed surprised that the damage was only reduced if it was piercing damage.

I actually tested it just now (I may have done it before, since I had a small deja vu) , I have a 90% defend ability in my map and a trigger that uses GUI Damage Engine to create text tags with the damage taken. Any non-piercing damage is not reduced by defend.
 
The comment was referring to people who're pondering the mechanics based on tooltips and random sources instead of testing things directly. That being said, I didn't even know some of the capabilities of defend until I read this thread - since I have never really used it, so it's fun to learn something, as well.
 

Kyrbi0

Arena Moderator
Level 45
Joined
Jul 29, 2008
Messages
9,502
Doesn't the defend tooltip state that it only reduces damage from Piercing sources?



Although Arcanite Shield says it reduces all ranged damage taken, it still mentions Piercing in it's Data field "Data - Deflect Damage Taken (Piercing)".

EDIT: I guess you could interpret "attacks that are not reflected" as any non-piercing attack and the unreflected piercing attacks. That's not how I thought it worked though :S

The shield only mentions ranged attacks though.
Hm, "arcanite shield"? Is that a Campaign item? I'd love to see some testing on that...
 
Hm, "arcanite shield"? Is that a Campaign item? I'd love to see some testing on that...

IIRC it's from Rexxar's campaign. It has an ability called Defend (item) that works like Elune's Grace, except it reduces piercing damage taken instead of magic damage taken. It's in the miscellaneous category in the World Editor. I'm pretty familiar with it because it was in basically every Life of a Peasant map ^^
 
Status
Not open for further replies.
Top