• 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] Raise Dead aura

Level 2
Joined
Sep 28, 2017
Messages
2
Hello everyone!

I have a custom item that provides an Unholy Aura. As a bonus effect, I wanted to make own units that die under the effect of the aura to be raised as skeletons.
I first tried to do this with GUI via Unit has a specific buff condition, but quickly figured out that it doesn't work with A unit dies event.
So my next step was trying to build a trigger around A unit dies event that checks for a presence of the item wielder in aura range. The result looks like this:
  • TabletRaisedead
    • Events
      • Unit - A unit Dies
    • Conditions
      • ((Dying unit) is Summoned) Equal to False
      • ((Dying unit) is Mechanical) Equal to False
      • ((Dying unit) is A Hero) Equal to False
      • ((Dying unit) is A flying unit) Equal to False
    • Actions
      • Unit Group - Add all units of (Units within 1200.00 of (Position of (Dying unit)) matching ((((Matching unit) has an item of type UnholyTablet) Equal to True) and ((Owner of (Matching unit)) Equal to (Owner of (Dying unit))))) to TestGroup1
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Number of units in TestGroup1) Greater than or equal to 1
        • Then - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (Random integer number between 1 and 10) Less than or equal to 7
            • Then - Actions
              • Unit - Create 1 Skeleton for (Owner of (Dying unit)) at (Position of (Dying unit)) facing (Facing of (Dying unit)) degrees
              • Unit - Add classification of Summoned to (Last created unit)
              • Unit - Add a 120.00 second Raise Dead expiration timer to (Last created unit)
              • Special Effect - Create a special effect at (Position of (Dying unit)) using Abilities\Spells\Undead\AnimateDead\AnimateDeadTarget.mdl
              • Special Effect - Destroy (Last created special effect)
            • Else - Actions
              • Unit - Create 1 Skeleton archer for (Owner of (Dying unit)) at (Position of (Dying unit)) facing (Facing of (Dying unit)) degrees
              • Unit - Add classification of Summoned to (Last created unit)
              • Unit - Add a 120.00 second Raise Dead expiration timer to (Last created unit)
              • Special Effect - Create a special effect at (Position of (Dying unit)) using Abilities\Spells\Undead\AnimateDead\AnimateDeadTarget.mdl
              • Special Effect - Destroy (Last created special effect)
        • Else - Actions
          • Do nothing
      • Unit Group - Remove all units from TestGroup1
On a small scale, this thing works with desired effect, but:
1) Since it uses a global variable, I'm afraid about it interfering with itself when used on a larger scale.
2) I might be mistaken, but Units withing radius matching condition can create leaks?

To address these issues, I tried to convert this trigger into JASS and tweak it a little.
Now, the important thing is — I have a very vague understanding of JASS and coding in general. The extent of my knowledge is an IT class in school, an entry-level course in C that I took years ago and never used it since, and some wandering around on the Hive.
With that starting point, I spent some time going back-and-forth from GUI to JASS and came up with this:
JASS:
function Trig_TabletRaisedeadCJ_Conditions takes nothing returns boolean
    if ( not ( IsUnitType(GetDyingUnit(), UNIT_TYPE_SUMMONED) == false ) ) then
        return false
    endif
    if ( not ( IsUnitType(GetDyingUnit(), UNIT_TYPE_MECHANICAL) == false ) ) then
        return false
    endif
    if ( not ( IsUnitType(GetDyingUnit(), UNIT_TYPE_HERO) == false ) ) then
        return false
    endif
    if ( not ( IsUnitType(GetDyingUnit(), UNIT_TYPE_FLYING) == false ) ) then
        return false
    endif
    return true
endfunction

function Trig_TabletRaisedeadCJ_Func002001003001 takes nothing returns boolean
    return ( UnitHasItemOfTypeBJ(GetFilterUnit(), 'I00A') == true )
endfunction

function Trig_TabletRaisedeadCJ_Func002001003002 takes nothing returns boolean
    return ( GetOwningPlayer(GetFilterUnit()) == GetOwningPlayer(GetDyingUnit()) )
endfunction

function Trig_TabletRaisedeadCJ_Func002001003 takes nothing returns boolean
    return GetBooleanAnd( Trig_TabletRaisedeadCJ_Func002001003001(), Trig_TabletRaisedeadCJ_Func002001003002() )
endfunction

function Trig_TabletRaisedeadCJ_Func006C takes nothing returns boolean
    if ( not ( GetRandomInt(1, 10) <= 7 ) ) then
        return false
    endif
    return true
endfunction

function Trig_TabletRaisedeadCJ_Func009C takes group CHK returns boolean
    if ( not ( CountUnitsInGroup(CHK) >= 1 ) ) then
        return false
    endif
    return true
endfunction

function Trig_TabletRaisedeadJ_Actions takes nothing returns nothing
    local unit DyingU = GetDyingUnit()
    local location DLoc = GetUnitLoc(GetDyingUnit())
    local real DAng = GetUnitFacing(GetDyingUnit())
    local group MatchG = GetUnitsInRangeOfLocMatching(1200.00, DLoc, Condition(function Trig_TabletRaisedeadCJ_Func002001003))
    if ( Trig_TabletRaisedeadJ_Func009C(MatchG) ) then
        if  ( Trig_TabletRaisedeadCJ_Func006C() ) then
            call CreateNUnitsAtLoc( 1, 'nskg', GetOwningPlayer(DyingU), DLoc, DAng )
            call UnitAddTypeBJ( UNIT_TYPE_SUMMONED, GetLastCreatedUnit() )
            call UnitApplyTimedLifeBJ( 120.00, 'Brai', GetLastCreatedUnit() )
            call AddSpecialEffectLocBJ( DLoc, "Abilities\\Spells\\Undead\\AnimateDead\\AnimateDeadTarget.mdl" )
            call DestroyEffectBJ( GetLastCreatedEffectBJ() )
        else
            call CreateNUnitsAtLoc( 1, 'nskf', GetOwningPlayer(DyingU), DLoc, DAng )
            call UnitAddTypeBJ( UNIT_TYPE_SUMMONED, GetLastCreatedUnit() )
            call UnitApplyTimedLifeBJ( 120.00, 'Brai', GetLastCreatedUnit() )
            call AddSpecialEffectLocBJ( DLoc, "Abilities\\Spells\\Undead\\AnimateDead\\AnimateDeadTarget.mdl" )
            call DestroyEffectBJ( GetLastCreatedEffectBJ() )
        endif
    else
        call DoNothing(  )
    endif
    call RemoveLocation(DLoc)
    call DestroyGroup(MatchG)
    call RemoveUnit(DyingU)
endfunction

//===========================================================================
function InitTrig_TabletRaisedeadCJ takes nothing returns nothing
    set gg_trg_TabletRaisedeadCJ = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_TabletRaisedeadCJ, EVENT_PLAYER_UNIT_DEATH )
    call TriggerAddCondition( gg_trg_TabletRaisedeadCJ, Condition( function Trig_TabletRaisedeadCJ_Conditions ) )
    call TriggerAddAction( gg_trg_TabletRaisedeadCJ, function Trig_TabletRaisedeadCJ_Actions )
endfunction

When I try to run this, I get an "Expected a name" script error at this line:
JASS:
if ( Trig_TabletRaisedeadJ_Func009C(MatchG) ) then
This line calls a function that checks if the group has at least one hero which owns the item in question. When converted directly from GUI, the function looks like this:
JASS:
function Trig_TabletRaisedead_Func006C takes nothing returns boolean
    if ( not ( CountUnitsInGroup(udg_TestGroup1) >= 1 ) ) then
        return false
    endif
    return true
endfunction
The value that it checks is easily called from the global variable udg_TestGroup1, but I want to feed it the value stored in the local variable MatchG instead.
So I edited the function where it is declared and changed takes nothing into takes group CHK:
JASS:
function Trig_TabletRaisedeadCJ_Func009C takes group CHK returns boolean
    if ( not ( CountUnitsInGroup(CHK) >= 1 ) ) then
        return false
    endif
    return true
endfunction
Now in my (unfortunately limited) understanding, next time I call this function in my trigger, I need to feed it a group-type value, and that's what I do: in the main function, when this function is called, instead of "if ( Trig_TabletRaisedeadJ_Func009C() ) then" I now have a "if ( Trig_TabletRaisedeadJ_Func009C(MatchG) ) then", MatchG being a previously declared variable of type group and value "GetUnitsInRangeOfLocMatching(1200.00, DLoc, Condition(function Trig_TabletRaisedeadCJ_Func002001003))"
And this is where I get a script error.
What am I doing wrong?

Thanks in advance!
 

Uncle

Warcraft Moderator
Level 73
Joined
Aug 10, 2018
Messages
7,915
I would avoid this design if possible, it's very inefficient. Especially if units die often in your map.

Instead, you could trigger your own Aura and have a variable acting like a "buff" which can be checked upon death:

Or, you could detect for lethal damage using Damage events:
  • RDA Lethal Damage
    • Events
      • Unit - A unit Takes damage
    • Conditions
      • ((Damage Target) has buff Devotion Aura) Equal to True
      • (Life of (Damage Target)) Less than or equal to ((Damage taken) + 0.41)
    • Actions
      • Set VariableSet RDA_Target = (Damage Target)
      • Unit Group - Add RDA_Target to RDA_Group
      • Trigger - Turn on RDA Check For Dead <gen>
  • RDA Check For Dead
    • Events
      • Time - Every 0.01 seconds of game time
    • Conditions
    • Actions
      • Unit Group - Pick every unit in RDA_Group and do (Actions)
        • Loop - Actions
          • Set VariableSet RDA_Target = (Picked unit)
          • Unit Group - Remove RDA_Target from RDA_Group.
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (RDA_Target is alive) Equal to False
            • Then - Actions
              • Set VariableSet RDA_Point = (Position of RDA_Target)
              • Special Effect - Create a special effect at RDA_Point using Abilities\Spells\Undead\RaiseSkeletonWarrior\RaiseSkeleton.mdl
              • Special Effect - Destroy (Last created special effect)
              • -------- --------
              • -------- Create the skeleton: --------
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • (Random integer number between 1 and 10) Less than or equal to 7
                • Then - Actions
                  • Unit - Create 1 Skeleton Warrior for (Owner of RDA_Target) at RDA_Point facing Default building facing degrees
                • Else - Actions
                  • Unit - Create 1 Skeletal Mage for (Owner of RDA_Target) at RDA_Point facing Default building facing degrees
              • -------- --------
              • Custom script: call RemoveLocation( udg_RDA_Point )
              • -------- --------
              • -------- Modify the skeleton: --------
              • Unit - Add classification of Summoned to (Last created unit)
              • Unit - Add a 120.00 second Generic expiration timer to (Last created unit)
            • Else - Actions
      • -------- --------
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (RDA_Group is empty) Equal to True
        • Then - Actions
          • Trigger - Turn off (This trigger)
        • Else - Actions
There's overhead with using Damage events but if your map already uses them, or something like Bribe's Damage Engine, then it's a great solution.
 

Attachments

  • Raise Dead Aura 1.w3m
    19 KB · Views: 1
Last edited:

Uncle

Warcraft Moderator
Level 73
Joined
Aug 10, 2018
Messages
7,915
Here's a more advanced version of the map that tries to avoid any edge cases and optimize things even further. I introduced a Unit Indexer which is a system for attaching data to Units by using Arrays + the unit's Custom Value property.


To respond to some comments in your original post:

1) Converting a trigger to Jass isn't really going to yield any immediate benefits, and the code is poorly translated. That being said, Jass/Lua are more flexible/efficient if you take advantage of the extra features and functionality that they offer, but that seems unnecessary here. If you're confused about Jass you should check out some tutorials. ChatGPT is also pretty decent at writing Jass code now, just make sure that you explain the situation to it -> "I'm on the latest version of Warcraft 3, can you write me some vJass code that does the following... Remember that I'm limited to the Warcraft 3 API".

2) You're leaking a Unit Group and Point in your original trigger here:
  • Unit Group - Add all units of (Units within 1200.00 of (Position of (Dying unit)) matching ((((Matching unit) has an item of type UnholyTablet) Equal to True) and ((Owner of (Matching unit)) Equal to (Owner of (Dying unit))))) to TestGroup1
In order for the game to get access to multiple units at a time it has to create a Unit Group object and store all of them in it. In this case, "Units within range" is creating a Unit Group object to get units matching your filter. The issue is that you have NO access to this Unit Group object, meaning you cannot get rid of it, which results in an immediate memory leak.

(Position of (Dying unit)) has the same issue, but in this case it creates a Point object which you cannot access and cannot destroy, causing a memory leak. To avoid these leaks you have to rely on variables of the associated types and Custom Script to manually Destroy/Remove them (interchangeable terminology for cleaning up the leak).
  • Set Variable YourPointVar = (Position of (Dying unit))
  • Set Variable YourGroupVar = (Units within 1200.00 range of YourPointVar)
  • -------- use these variables then clean them up... --------
  • Custom script: call RemoveLocation( udg_YourPointVar )
  • Custom script: call DestroyGroup( udg_YourGroupVar )
3) You're leaking a Point when you create your Skeletons:
  • Unit - Create 1 Skeleton for (Owner of (Dying unit)) at (Position of (Dying unit)) facing (Facing of (Dying unit)) degrees
Same issue I described in #2.
 

Attachments

  • Raise Dead Aura 2.w3m
    25 KB · Views: 1
Last edited:
Level 2
Joined
Sep 28, 2017
Messages
2
Thanks for the answer!

Sorry, but I forgot to mention an important detail — I haven't upgraded an I'm still on 1.26a, therefore I don't have the damage events in editor at all :sad:

Going the Damage engines route is a viable option, yes. However, this is just for a melee map with some slight customization that I'm making just for myself for fun. It's probably not going to have many custom abilities except this one, and I'm trying to avoid using a Damage Engine if possible, because with that, I'll be breaking five abilities to fix one. But I'm considering it anyway, in case other option fail.

Your Celerity aura method is thus far the best alternative and I'll try building my ability based on your example, thank you very much!

On another note, I'd still like to know what exactly procs that error message in the JASS code, at least for future reference in case I'll need it someday. Converting the trigger to JASS was done specifically to deal with the leaks you found. That's why I declared local variables for Unit Group and Point and cleared them at the end of a function. Is there a more efficient way to get rid of the leaks?
The problem I'm facing is that this exact Unit Group is referenced in two separate functions, one of which in nested in another. I can assign it to a local variable in one function, but then how do I reference the stored value in the nested function?
Can I just use "set bj_wantDestroyGroup = true" before the whole Unit Group action instead? Will this solve the leak? That would rid me of the need to reference the value, and I think the trigger will work just fine. Also, if I understand correctly, "bj_wantDestroyGroup" is a global variable. Can it mess with other triggers if left in "true" state? Should I probably call a "set bj_wantDestroyGroup = false" at the end of the function?

Oh, and I didn't know that ChatGPT can handle JASS : ) Will try that too!
 

Uncle

Warcraft Moderator
Level 73
Joined
Aug 10, 2018
Messages
7,915
I should've said this sooner. Your original GUI trigger is perfectly fine and isn't making any logical mistakes. It's just not very efficient and has memory leaks. But the leaks can be addressed without any serious effort and since it's a singleplayer map you have a lot of room for pushing performance:
  • TabletRaisedead
    • Events
      • Unit - A unit Dies
    • Conditions
      • ((Dying unit) is Summoned) Equal to False
      • ((Dying unit) is Mechanical) Equal to False
      • ((Dying unit) is A Hero) Equal to False
      • ((Dying unit) is A flying unit) Equal to False
    • Actions
      • Set Tablet_Point = (Position of (Dying unit))
      • Set Tablet_Group = Units within 1200.00 of Tablet_Point matching ((((Matching unit) has an item of type UnholyTablet) Equal to True) and ((Owner of (Matching unit)) Equal to (Owner of (Dying unit)))))
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Number of units in Tablet_Group) Greater than or equal to 1
        • Then - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (Random integer number between 1 and 10) Less than or equal to 7
            • Then - Actions
              • Unit - Create 1 Skeleton for (Owner of (Dying unit)) at Tablet_Point facing (Facing of (Dying unit)) degrees
            • Else - Actions
              • Unit - Create 1 Skeleton archer for (Owner of (Dying unit)) at Tablet_Point facing (Facing of (Dying unit)) degrees
          • Unit - Add classification of Summoned to (Last created unit)
          • Unit - Add a 120.00 second Raise Dead expiration timer to (Last created unit)
          • Special Effect - Create a special effect at Tablet_Point using Abilities\Spells\Undead\AnimateDead\AnimateDeadTarget.mdl
          • Special Effect - Destroy (Last created special effect)
        • Else - Actions
          • Do nothing
      • Custom script: call RemoveLocation(udg_Tablet_Point)
      • Custom script: call DestroyGroup(udg_Tablet_Group)
I would recommend Disabling this trigger (Initially On = False) by default and only Enabling it while at least one Unit is carrying your UnholyTablet item. That way it's only running when it's supposed to. I also recommend using unique variables inside of any trigger that uses the Event "A unit Dies', as this Event breaks the standard trigger queue rules.


Anyway, Damage Engine is still possible on 1.26a as far as I remember. It shouldn't break anything on it's own, it's not like it runs any logic that modifies the outcome of your own abilities/triggers. Although, the older version might not be as great as the current one. Perhaps it's time to update to a newer version of Warcraft 3? People often use 1.31 as it's the last pre-Reforged patch and offers some great new tools (Make backups before doing so!).

The Jass is very messy, so maybe someone else can chime in on that because I can't really read that (nor want to, lol). But if you get an error after converting a working GUI trigger that'd be concerning. Jass has this annoying hierarchy requirement where functions have to literally be positioned above other functions that reference them. Maybe that's what you're experiencing?

Regarding "set bj_wantDestroyGroup = true", this variable does tell most functions to automatically destroy the next Unit Group object that they create. But like you mentioned, it could be unsafe if you're nesting things and calling it multiple times within some spaghetti code situation. The functions will (or should) automatically set it back to false at the end.

You're leaking in your Jass code because you never null your local variables. Non-primitives need to be nulled out (do so AFTER destroying/removing them):
vJASS:
set DLoc = null
set MatchG = null
set DyingU = null
 
Top