HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

Strange Bug

08-14-2008, 12:48 PM#1
Tukki
Hey, I'm remaking an old spell of mine into vJass. But there's a strange bug that makes my dummies revive after I've killed them. If anyone could solve this it would be great! It uses ABC, PUI and Damage Detection as it is.

Problem: When I kill the dummies in the DummBehaviour function, they somehow re-spawns at the same location and are uncontrollable.

Spell tooltip:
Hidden information:
Spell Type: Damage Buffing.
Cooldown: 30 - 24 Seconds.

Upon activation the next attacks will conjure replicates of the Samurai, which slashes the foe for a precentage of the original damage.
Lasts for 3 - 6 hits.


Globals description:
Collapse JASS:
 //*************************************************
    //* AID_SWIFT_STRIKES: The spells' rawcode.
    //* UID_DUMMY_HERO:    Dummy unit rawcode, with the same model as the hero.
    //* 
    //* ANIMATION:         Which animation is to be played?
    //* MULTIPLE_CAST:     Are ppl allowed to refresh the spell?
    //* ANIMATION_SPEED:   If you want to decrese/increase the dummies animation speed, this is it.
    //* TIME_TO_DAMAGE:    The time between the animation is played and the damage is dealt.
    //* TRANSPARANCY:      The alpha color of the dummies.
    //*
    //* DAMAGE_TYPE:       Since we use Damage Detection, we have to use a custom damage_type.
    //* ATTACK_TYPE:       Attack type of the damage.
    //* IGNORE_ARMOR:      Wheter the damage should ignore armor or not.
    //*
    //* DAMAGE_BASE:       The base % of the damage the images will deal.
    //* DAMAGE_MULT:       The increment per level % of the damage.
    //* STRIKE_BASE:       The base amount of images created.
    //* STRIKE_MULT:       Increment per level of images created.
    //* HITS_BASE:         Base amount of hits the spell will affect.
    //* HITS_MULT:         Increment per level of hits the spell will affect.
    //*
    //* TAG_COLOR:         Which color will the damage be displayed in?
    //* TAG_SIZE:          Size of the texttag.
    //* TAG_VELOCITY:      The y speed of the texttag.
    //* TAG_FADE_BEGIN:    The time where the texttag begins to fade.
    //* TAG_LIFE_TIME:     Maximum lifte-time of the texttag.
    //*
    //* BLOOD_SFX:         Effect created when an image deals damage.
    //* DISTRACTION_SFX:   The effect created when a image dies.
    //* ATTACH_POINT:      Attachment point of blood sfx.
    //*************************************************

The spell's main code:
Collapse JASS:
scope SwiftStrikes initializer InitTrig

globals
    private constant integer AID_SWIFT_STRIKES = 'A000'
    private constant integer UID_DUMMY_HERO    = 'h000'
    
    private constant string  ANIMATION         = "Attack"
    private constant boolean MULTIPLE_CAST     = false
    private constant real    ANIMATION_SPEED   = 100.0
    private constant real    TIME_TO_DAMAGE    = 0.5
    private constant integer TRANSPARANCY      = 128
    
    private constant integer DAMAGE_TYPE       = DAMAGE_TYPE_SPELL
    private constant attacktype ATTACK_TYPE    = ATTACK_TYPE_HERO
    private constant boolean IGNORE_ARMOR      = false
    
    private constant real    DAMAGE_BASE       = 0.33
    private constant real    DAMAGE_MULT       = 0.11
    private constant integer STRIKE_BASE       = 1
    private constant integer STRIKE_MULT       = 1
    private constant integer HITS_BASE         = 3
    private constant integer HITS_MULT         = 1
    
    private constant string  TAG_COLOR         = "DD0000"
    private constant real    TAG_SIZE          = 0.024 
    private constant real    TAG_VELOCITY      = 0.02
    private constant real    TAG_FADE_BEGIN    = 1.0
    private constant real    TAG_LIFE_TIME     = 2.0
    
    private constant string  BLOOD_SFX         = "Abilities\\Spells\\Other\\Stampede\\StampedeMissileDeath.mdl"
    private constant string  DISTRACTION_SFX   = "Abilities\\Spells\\Items\\AIil\\AIilTarget.mdl"
    private constant string  ATTACH_POINT      = "chest"
endglobals

//*********************************************************
private constant function GetDamagePercent takes integer level returns real
    return (DAMAGE_BASE + ((level-1)*DAMAGE_MULT))
endfunction

//*********************************************************
private constant function GetStrikes takes integer level returns integer
    return (STRIKE_BASE + ((level-1)*STRIKE_MULT))
endfunction

//*********************************************************
private constant function GetHits takes integer level returns integer
    return (HITS_BASE + ((level-1)*HITS_MULT))
endfunction

//*********************************************************
private keyword Data // keyword used to use un-initialized stuff

//*********************************************************
globals
    private triggeraction ACTION     = null 
    private group         STRIKERS   = null
    private trigger       TRIGGER    = null
    private Data array Datas
    private unit array Pivot
endglobals

//*********************************************************
private struct Data
    unit Caster
    real Damage
    integer Strikes
    integer Level
    integer Hits
    
    //*********************************************************
    static method create takes unit whichCaster returns Data
     local Data d
     local integer index = GetUnitIndex(whichCaster)
     local boolean b = false
        
        if (Pivot[index] == whichCaster) then // If the unit is currently using the spell..
            if (MULTIPLE_CAST) then // ..check if you may cast it multiple times
                set d = Datas[index] // and set d to an already initialized struct
                set b = true // and set b to true to prevent overwriting
            else
                call DisplayTextToPlayer(GetOwningPlayer(whichCaster), 0, 0, SCOPE_PREFIX + " is already in use!")
            endif
        else
            set d = Data.allocate() // else create the struct and assign the values
        endif
        
        set d.Caster = whichCaster
        set d.Level = GetUnitAbilityLevel(d.Caster, AID_SWIFT_STRIKES)
        set d.Damage = GetDamagePercent(d.Level)
        set d.Strikes = GetStrikes(d.Level)
        set d.Hits = GetHits(d.Level)
        
        if not (b) then // If the caster is using the spell, skip this step
            call GroupAddUnit(STRIKERS, d.Caster)
            set Datas[index] = d
            set Pivot[index] = whichCaster
        endif
    return d
    endmethod
    
    //*********************************************************
    static method operator[] takes unit whichUnit returns Data
     local integer index = GetUnitIndex(whichUnit)
        if (Pivot[index] == whichUnit) and (IsUnitInGroup(whichUnit, STRIKERS) and Datas[index] != null) then
            return Datas[index]// Returns the struct if above conditions are met
        endif
    return 0 // Returns 0 if the conditions are false
    endmethod
    
    //*********************************************************
    private method onDestroy takes nothing returns nothing
        set Datas[GetUnitIndex(.Caster)] = 0
        set Pivot[GetUnitIndex(.Caster)] = null
        call GroupRemoveUnit(STRIKERS, .Caster)
    endmethod
endstruct

//*********************************************************
private struct Striker // Struct for dummies. Used to damage and kill the dummy
    unit toKill
    unit Damager
    unit Damaged
    real Damage

    //*********************************************************
    static method create takes unit u, unit Damager, unit Damaged, real Damage returns Striker
     local Striker d = Striker.allocate()
     
        set d.toKill = u // Sets the appropriate values
        set d.Damager = Damager
        set d.Damaged = Damaged
        set d.Damage = Damage
    return d
    endmethod
    
    private method onDestroy takes nothing returns nothing
        set .toKill = null
        set .Damager = null
        set .Damaged = null
    endmethod
endstruct

//********************************************************* 
private function ShowDamage takes unit whichUnit, real whichDamage returns nothing
 local texttag DTag = CreateTextTag() // Function for displaying damage.
 local integer correct 
    
    if (StringLength(R2S(whichDamage)) < 32) then
        set correct = StringLength(R2S(whichDamage)) * 8
    elseif (32 < StringLength(R2S(whichDamage))) then
        set correct = 32 * 8
    endif
    call SetTextTagText(DTag, "|cFF" + TAG_COLOR + I2S(R2I(whichDamage)), TAG_SIZE)
    call SetTextTagPos(DTag, GetUnitX(whichUnit) - correct, GetUnitY(whichUnit), 16)
    call SetTextTagVelocity(DTag, 0, TAG_VELOCITY)
    call SetTextTagVisibility(DTag, true)
    call SetTextTagFadepoint(DTag, TAG_FADE_BEGIN)
    call SetTextTagLifespan(DTag, TAG_LIFE_TIME)
    call SetTextTagPermanent(DTag, false)
    
    set DTag = null
endfunction

//*********************************************************
private function CreateBloodSfx takes unit whichUnit returns nothing // Function for creating special effect.
    call DestroyEffect(AddSpecialEffectTarget(BLOOD_SFX, whichUnit, ATTACH_POINT))
endfunction

//*********************************************************
private function DummyBehaviour takes nothing returns nothing // The Damage/Removal function.
 local timer t = GetExpiredTimer()
 local Striker d = GetTimerStructA(t) // Retrieves the attached struct from the timer.
                
    call UnitDamageTargetEx(d.Damager, d.Damaged, d.Damage, ATTACK_TYPE, DAMAGE_TYPE, IGNORE_ARMOR) // Deals damage to the damaged unit, (with Damage Detection function)
    call CreateBloodSfx(d.Damaged) // Create Blood sfx, rather obviuos.
    call ShowDamage(d.Damaged, d.Damage) // creates a texttag showing damage.
    
    if (GetWidgetLife(d.toKill) > 0.405) then
        call KillUnit(d.toKill) // <-- here we kill our dummy, but the problem is that it is revived!.
        call d.destroy() // Destroys struct.
    endif
    call DestroyTimer(t)
    set t = null 
endfunction

//*********************************************************
private function Actions takes nothing returns nothing // The damage action.
 local unit Damager = GetTriggerDamageSource()
 local unit Damaged = GetTriggerDamageTarget()
 local integer Type = GetTriggerDamageType()
 local Data d       = Data[Damager]
 local real Damage  = GetTriggerDamage() * d.Damage
 local real x       = GetUnitX(Damager)
 local real y       = GetUnitY(Damager)
 local real face    = GetUnitFacing(Damager)
 local integer TempInt = 0
 local unit striker
 local Striker c 
 local timer t
 
    if (integer(d) != 0 and Type == DAMAGE_TYPE_ATTACK) then // If there's a struct and damage type is Attack then go!
        if (d.Hits > 0) then // Checks if the caster may still use the spell, else destroy it.
        set d.Hits = d.Hits - 1
            loop
                set striker = CreateUnit(GetOwningPlayer(Damager), UID_DUMMY_HERO, x, y, face) // create 'dummy image'.
                exitwhen TempInt == d.Strikes // loop until all images are created.
                if (GetWidgetLife(striker) > 0.405) then
                    call SetUnitTimeScale(striker, ANIMATION_SPEED * 0.01) // changes animation speed.
                    call SetUnitAnimation(striker, ANIMATION) // playes the desired animation.
                    call SetUnitVertexColor(striker, 255, 255, 255, TRANSPARANCY) // modifies the alpha color to the specified.
                endif
                set c = Striker.create(striker, d.Caster, Damaged, Damage) // creates a struct for the dummy, so it may damage and be killed.
                set t = CreateTimer() // create timer.
                call SetTimerStructA(t, c) // attach struct to timer.
                call TimerStart(t, TIME_TO_DAMAGE, false, function DummyBehaviour) // and start the Damage/Removal function.
                call TriggerSleepAction(0.0) // Wait lowest amount possible so not all images are created immediately.
                set TempInt = TempInt + 1
            endloop
        else
            call d.destroy() // destroy struct if the caster has used all hits.
        endif
    endif
    
    set Damager = null // remove leaks.
    set Damaged = null
endfunction

//*********************************************************
private function Conditions takes nothing returns boolean
    if (GetTriggerEventId() == EVENT_PLAYER_UNIT_SPELL_EFFECT) then // if a unit casts a spell..
        if (GetSpellAbilityId() == AID_SWIFT_STRIKES) then // ..and that spell is swift strikes..
            call Data.create(GetSpellAbilityUnit()) // ..then start the spell.
        endif
    elseif (GetTriggerEventId() == EVENT_PLAYER_UNIT_DEATH) then // else if a unit dies..
        if (GetUnitTypeId(GetDyingUnit()) == UID_DUMMY_HERO) then // ..and it's a dummy..
            call DestroyEffect( AddSpecialEffect(DISTRACTION_SFX, GetUnitX(GetDyingUnit()), GetUnitY(GetDyingUnit()))) // ..create an effect.
        endif
    endif
    return false // return false as there's no real action
endfunction

//*********************************************************
private function InitTrig takes nothing returns nothing
 local trigger T = CreateTrigger()
 
    set STRIKERS = CreateGroup() // create the group that containes all casters
    set TRIGGER = CreateTrigger() // the trigger used for damage events
    set ACTION = TriggerAddAction(TRIGGER, function Actions) // the action of the above trigger
    call TriggerRegisterDamageEvent(TRIGGER)

    call TriggerRegisterAnyUnitEventBJ(T, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerRegisterAnyUnitEventBJ(T, EVENT_PLAYER_UNIT_DEATH)
    call TriggerAddCondition(T, Condition( function Conditions))
endfunction
endscope

----
EDIT: Changed some parts.
----
08-14-2008, 01:47 PM#2
Blubb-Tec
Please explain what the spell does, how you realized it, and comment your code. Then I'll be happy to spend my time helping you getting your spell to work.
08-14-2008, 02:25 PM#3
Tukki
First post updated.
08-14-2008, 10:03 PM#4
Pyrogasm
Change the dummy unit's type to "Can't Raise, does not decay".
08-14-2008, 10:45 PM#5
Tukki
Already done it, but the bug is still there. Somehow it worked fine to kill the unit manually (using my hero to attack it).
08-15-2008, 12:00 AM#6
Szythe
Most likely the dummy is being forced to play an animation directly after it dies. Sometimes this can screw things up, and the unit will play neither the desired animation, or death, but simply "stand". I had a similar problem in a spell I made.

Try adding a check for GetWidgetLife(unit) > 0.405 before setting the unit's animation.
08-15-2008, 04:42 AM#7
Blubb-Tec
okay, now the code is readable, thanks for the comments :)

something i found:
Collapse JASS:
          loop
                set striker = CreateUnit(GetOwningPlayer(Damager), UID_DUMMY_HERO, x, y, face) // create 'dummy image'.
                exitwhen TempInt >= d.Strikes // loop until all images are created.
           [...] //actions with the dummy
           endloop

In the case TempInt is >= d.Strikes there would still be a dummy created, but not registered for the spell. Might that be the cause?
08-15-2008, 11:03 AM#8
Tukki
Thanks, will check those things out.

EDIT: Nope it those things didn't work, but thanks anyway.
EDIT_2: Updated script.