• 🏆 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!

Unit groups and Dead units

Status
Not open for further replies.
Level 24
Joined
Feb 9, 2009
Messages
1,787
Hello, tried to make an AOE spell that deals damage divided by the number of units in the group.

My problem is that the corpses are being counted with this group despite being conditioned out of it.

  • Set EL_Damage_Group = (Units within EL_Damage_Area_of_Effect of EL_Targeted_Location matching ((((Matching unit) is A structure) Equal to False) and ((((Matching unit) is Magic Immune) Equal to False) and ((((Matching unit) is alive) Equal to True) and (((Matching unit) belongs to
  • Unit Group - Pick every unit in EL_Damage_Group and do (Actions)
    • Loop - Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Number of units in EL_Damage_Group) Less than 2
        • Then - Actions
          • Unit - Cause EL_Caster to damage (Picked unit), dealing (EL_Total_Damage x 1.50) damage of attack type Spells and damage type Magic
        • Else - Actions
          • Unit - Cause EL_Caster to damage (Picked unit), dealing (EL_Total_Damage / (Real((Number of units in EL_Damage_Group)))) damage of attack type Spells and damage type Magic
      • Special Effect - Create a special effect attached to the origin of (Picked unit) using Abilities\Weapons\Bolt\BoltImpact.mdl
      • Special Effect - Destroy (Last created special effect)
I've also tried units in range matching condition with no luck either.
- (Matching Unit) is alive equal to true.
- (Matching Unit) is dead equal to false.
 
Last edited:
Should be something like (and (Matching Unit)'s Current Life is Greater than or Equal to 0.405)

You may not be able to go as accurate as 0.405 in GUI, so you may need a variable.


  • Set RealVariable = 0
  • Custom script: set udg_RealVariable = 0.405
  • Set EL_Damage_Group = (Units within EL_Damage_Area_of_Effect of EL_Targeted_Location and (Matching Unit)'s Current Life is Greater than or Equal to RealVariable)
 
Level 24
Joined
Feb 9, 2009
Messages
1,787
GetUnitLife>0 works just as well since this isn't a damage detection system.

Tried it, same result, thanks though.

Can you show me your code then?
It's simply the same as the first post's code but with the implementation of your suggestion.

Add "(Matching Unit) is alive equal to true." to your tempgroup.
oops sorry I'll add that instead of the last line at the top

Gunna try and add a buff to the units struck and then pick them accordingly, Thank you everyone who posted.
 
Last edited:
Level 24
Joined
Feb 9, 2009
Messages
1,787
Set EL_Damage_Group = (Units within EL_Damage_Area_of_Effect of EL_Targeted_Location matching ((((Matching unit) is A structure) Equal to False) and ((((Matching unit) is Magic Immune) Equal to False) and ((((Matching unit) is alive) Equal to True) and (((Matching unit) is dead) Equal to False

I've tried it with both Alive and Dead booleans by themselves by the way
 
Level 40
Joined
Dec 14, 2005
Messages
10,532
Interesting. Boolexprs have some bugs, but I was under the impression GUI was immune to them. Apparently it isn't, unless I'm missing something.

The easiest solution is to just fake that part of the filter in the ForGroup, however ugly it may be. Freehand:

  • Unit Group - Pick Every Unit in (Units within blah of blah matching ((((Matching Unit) is a Structure) Equal to False) and (((Matching Unit) is Magic Immune) Equal to False))) and do Actions
    • Loop - Actions
      • If (All conditions are true) then do (then actions) else do (else actions)
        • If - Conditions
          • (((Picked Unit) is Alive) Equal to True)
        • Then - Actions
          • -------- do stuff as normal --------
        • Else - Actions
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
IIRC, the IsUnitAlive gui stuff is some kind of GetWidgetLife > 0, and still IIRC a dead unit can have some life, let's say more than 0.405 (could even be caused by some passive ability)

He should use IsUnitType(...DEAD), i suppose you can do it also in GUI.
 
Level 40
Joined
Dec 14, 2005
Messages
10,532
IIRC, the IsUnitAlive gui stuff is some kind of GetWidgetLife > 0, and still IIRC a dead unit can have some life, let's say more than 0.405 (could even be caused by some passive ability)

He should use IsUnitType(...DEAD), i suppose you can do it also in GUI.
They can have life, but it's really really really rare and requires the mapmaker to have increased their life after they were dead.
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
Can you convert the trigger to JASS and post it? This way we can find out if there's an issue with the GUI conditions.


But yes, there are abilities that increase the life above zero, even for dead units (tranquility is one of those abilities), breaking the unit alive condition.


Another issue:
You shouldn't use "Number of units in" within the group loop. It will basicly enumerate all units inside the group and count them on each group loop iteration, which might not only cause bugs, but also make your spell terribly CPU intense.
Instead, get the number of units before picking all units in the group and save it to a variable.
 
Do you work with bj_wantDestroyGroup before?

If yes, try to add following on top of code, to ensure your group wont get destoyed when you count units:
  • Custom script: set bj_wantDestroyGroup = false
....
Your code

And like said by Zwiebelchen above you should count them with a custom integer to avoid calling this uncool CountUnits function so often. (and will bug if bj_wantDestroyGroup = true)

And if not, don't count units each time, would be enough todo it once before your loop and store it into a variable.
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
This custom script is not a good advise, it will screw up an "every unit of type", because how the GUI filters were done.
It will only enum the units of player(0), and then the group will be destroyed (and there is possibly other issues, such as a group leak, can't remember)
You can find them yourself following the logic.

So just use DestroyGroup.
 
Level 24
Joined
Feb 9, 2009
Messages
1,787
Can you convert the trigger to JASS and post it? This way we can find out if there's an issue with the GUI conditions.

Another issue:
You shouldn't use "Number of units in" within the group loop. It will basicly enumerate all units inside the group and count them on each group loop iteration, which might not only cause bugs, but also make your spell terribly CPU intense.
Instead, get the number of units before picking all units in the group and save it to a variable.

JASS:
function Trig_Bolt_Spell_Conditions takes nothing returns boolean
    if ( not ( GetSpellAbilityId() == 'A03H' ) ) then
        return false
    endif
    return true
endfunction

function Trig_Bolt_Spell_Func013002003001 takes nothing returns boolean
    return ( IsUnitType(GetFilterUnit(), UNIT_TYPE_STRUCTURE) == false )
endfunction

function Trig_Bolt_Spell_Func013002003002001 takes nothing returns boolean
    return ( IsUnitType(GetFilterUnit(), UNIT_TYPE_MAGIC_IMMUNE) == false )
endfunction

function Trig_Bolt_Spell_Func013002003002002 takes nothing returns boolean
    return ( IsUnitDeadBJ(GetFilterUnit()) == false )
endfunction

function Trig_Bolt_Spell_Func013002003002 takes nothing returns boolean
    return GetBooleanAnd( Trig_Bolt_Spell_Func013002003002001(), Trig_Bolt_Spell_Func013002003002002() )
endfunction

function Trig_Bolt_Spell_Func013002003 takes nothing returns boolean
    return GetBooleanAnd( Trig_Bolt_Spell_Func013002003001(), Trig_Bolt_Spell_Func013002003002() )
endfunction

function Trig_Bolt_Spell_Func018Func001C takes nothing returns boolean
    if ( not ( CountUnitsInGroup(udg_EL_Damage_Group) < 2 ) ) then
        return false
    endif
    return true
endfunction

function Trig_Bolt_Spell_Func018A takes nothing returns nothing
    if ( Trig_Bolt_Spell_Func018Func001C() ) then
        call UnitDamageTargetBJ( udg_EL_Caster, GetEnumUnit(), ( udg_EL_Total_Damage * 1.50 ), ATTACK_TYPE_NORMAL, DAMAGE_TYPE_MAGIC )
    else
        call UnitDamageTargetBJ( udg_EL_Caster, GetEnumUnit(), ( udg_EL_Total_Damage / I2R(CountUnitsInGroup(udg_EL_Damage_Group)) ), ATTACK_TYPE_NORMAL, DAMAGE_TYPE_MAGIC )
    endif
    call AddSpecialEffectTargetUnitBJ( "origin", GetEnumUnit(), "Abilities\\Weapons\\Bolt\\BoltImpact.mdl" )
    call DestroyEffectBJ( GetLastCreatedEffectBJ() )
endfunction

function Trig_Bolt_Spell_Func020Func004002003001 takes nothing returns boolean
    return ( IsUnitAliveBJ(GetFilterUnit()) == true )
endfunction

function Trig_Bolt_Spell_Func020Func004002003002 takes nothing returns boolean
    return ( IsPlayerEnemy(GetOwningPlayer(GetFilterUnit()), udg_EL_Owner) == true )
endfunction

function Trig_Bolt_Spell_Func020Func004002003 takes nothing returns boolean
    return GetBooleanAnd( Trig_Bolt_Spell_Func020Func004002003001(), Trig_Bolt_Spell_Func020Func004002003002() )
endfunction

function Trig_Bolt_Spell_Func020Func005Func001001 takes nothing returns boolean
    return ( IsUnitDeadBJ(GetEnumUnit()) == true )
endfunction

function Trig_Bolt_Spell_Func020Func005A takes nothing returns nothing
    if ( Trig_Bolt_Spell_Func020Func005Func001001() ) then
        call GroupRemoveUnitSimple( GetEnumUnit(), udg_EL_Seek_Group )
    else
        call DoNothing(  )
    endif
endfunction

function Trig_Bolt_Spell_Actions takes nothing returns nothing
    set udg_EL_Caster = GetTriggerUnit()
    set udg_EL_Owner = GetOwningPlayer(udg_EL_Caster)
    set udg_EL_Targeted_Location = GetSpellTargetLoc()
    set udg_EL_Ability_Level = GetUnitAbilityLevelSwapped(GetSpellAbilityId(), udg_EL_Caster)
    set udg_EL_Dummy_Ability_Ligtning = 'A00G'
    set udg_EL_Base_Number_of_Ligtnings = 2
    set udg_EL_Total_Number_of_Ligtnings = ( udg_EL_Base_Number_of_Ligtnings * udg_EL_Ability_Level )
    set udg_EL_Seek_Area_of_Effect = 500.00
    set udg_EL_Damage_Area_of_Effect = 275.00
    set udg_EL_Base_Damage = 100.00
    set udg_EL_Total_Damage = ( udg_EL_Base_Damage * I2R(udg_EL_Ability_Level) )
    set udg_EL_Damage_Group = GetUnitsInRangeOfLocMatching(udg_EL_Damage_Area_of_Effect, udg_EL_Targeted_Location, Condition(function Trig_Bolt_Spell_Func013002003))
    call AddSpecialEffectLocBJ( udg_EL_Targeted_Location, "Abilities\\Spells\\Human\\Thunderclap\\ThunderClapCaster.mdl" )
    call DestroyEffectBJ( GetLastCreatedEffectBJ() )
    call AddSpecialEffectLocBJ( udg_EL_Targeted_Location, "Abilities\\Spells\\Other\\Charm\\CharmTarget.mdl" )
    call DestroyEffectBJ( GetLastCreatedEffectBJ() )
    call ForGroupBJ( udg_EL_Damage_Group, function Trig_Bolt_Spell_Func018A )
    call DestroyGroup(udg_EL_Damage_Group)
    set bj_forLoopAIndex = 1
    set bj_forLoopAIndexEnd = udg_EL_Total_Number_of_Ligtnings
    loop
        exitwhen bj_forLoopAIndex > bj_forLoopAIndexEnd
        set udg_EL_Loop_Location = PolarProjectionBJ(udg_EL_Targeted_Location, 5.00, ( 360.00 / I2R(GetForLoopIndexA()) ))
        call AddSpecialEffectLocBJ( udg_EL_Loop_Location, "Abilities\\Spells\\Other\\Monsoon\\MonsoonBoltTarget.mdl" )
        call DestroyEffectBJ( GetLastCreatedEffectBJ() )
        set udg_EL_Seek_Group = GetUnitsInRangeOfLocMatching(udg_EL_Seek_Area_of_Effect, udg_EL_Targeted_Location, Condition(function Trig_Bolt_Spell_Func020Func004002003))
        call ForGroupBJ( udg_EL_Seek_Group, function Trig_Bolt_Spell_Func020Func005A )
        set udg_EL_Seek_Unit = GroupPickRandomUnit(udg_EL_Seek_Group)
        call CreateNUnitsAtLoc( 1, 'h008', udg_EL_Owner, udg_EL_Loop_Location, bj_UNIT_FACING )
        call UnitApplyTimedLifeBJ( 1.50, 'BTLF', GetLastCreatedUnit() )
        call UnitAddAbilityBJ( udg_EL_Dummy_Ability_Ligtning, GetLastCreatedUnit() )
        call SetUnitAbilityLevelSwapped( udg_EL_Dummy_Ability_Ligtning, GetLastCreatedUnit(), udg_EL_Ability_Level )
        call IssueTargetOrderBJ( GetLastCreatedUnit(), "chainlightning", udg_EL_Seek_Unit )
        call DestroyGroup(udg_EL_Seek_Group)
        call RemoveLocation(udg_EL_Loop_Location)
        set bj_forLoopAIndex = bj_forLoopAIndex + 1
    endloop
    call RemoveLocation(udg_EL_Targeted_Location)
endfunction

//===========================================================================
function InitTrig_Bolt_Spell takes nothing returns nothing
    set gg_trg_Bolt_Spell = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_Bolt_Spell, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( gg_trg_Bolt_Spell, Condition( function Trig_Bolt_Spell_Conditions ) )
    call TriggerAddAction( gg_trg_Bolt_Spell, function Trig_Bolt_Spell_Actions )
endfunction

Get a variable first, sounds good!

Edit: added Jass tags
 
Last edited:
Level 19
Joined
Mar 18, 2012
Messages
1,716
I hope GUI doesn't generate "Do Nothing" by default.

Good Lord ... GUI
IsUnitAliveBJ --> IsUnitDeadBJ --> GetUnitState(whichUnit, UNIT_STATE_LIFE) <= 0.

I wonder why it does generate one time GetEnumUnit() and then GetFilterUnit(), while both functions are related to a group enumeration.
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
Please wrap it in jass tags...

And after checking the JASS i'm positive your issue is caused by the nested group loop and the bugged IsUnitAlive.

No, this native function is an ai one, so no the gui don't use it.
People tends to use it with the native function jasshelper support (you can do the very same if you import inside the map a custom common.j with this native function declaration added), but i prefer the IsUnitType(...DEAD) stuff.
Now maybe it's not bugged, but simply have different behaviors, which could make sense for an ai, i mean in some situations it could return false while it would be true with the IsUnitType stuff.
Like an unit which is being resurected and such. Clearly i've never used it, it's just a guess.

BPower said:
I wonder why it does generate one time GetEnumUnit() and then GetFilterUnit(), while both functions are related to a group enumeration.

GetEnumUnit -> ForForce
GetFilterUnit -> boolexpr filter
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
So is it possible to fix this?
Yeah, as I said, just do the unit counting before you do the ForGroup loop and replace the Is-unit-alive condition by an is-unit-type condition checking for the "dead" type.

No, this native function is an ai one, so no the gui don't use it.
People tends to use it with the native function jasshelper support (you can do the very same if you import inside the map a custom common.j with this native function declaration added), but i prefer the IsUnitType(...DEAD) stuff.
Now maybe it's not bugged, but simply have different behaviors, which could make sense for an ai, i mean in some situations it could return false while it would be true with the IsUnitType stuff.
Like an unit which is being resurected and such. Clearly i've never used it, it's just a guess.
Lol... Dude, I know that. I was referring to IsUnitAliveBJ, which simply gets the GetUnitState of the unit.
 
Level 40
Joined
Dec 14, 2005
Messages
10,532
Your damage detection system is changing the units' life to 1 after they die, which is causing them to not get picked up by the life>0 checks to see if the units are alive.

The DDS appears to be badly bugged (I spent a few minutes trying to fix it, but the problems are too rampant to do a quick job) and I suggest you discard it in favour of a different one. I'll report it on the resource page.
 
Last edited:
Status
Not open for further replies.
Top