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

Snippet to fix W3 bug on "Orb of slow" and similar abilities

Status
Not open for further replies.
Level 12
Joined
Feb 27, 2019
Messages
399
Hello,

You may know the following abilities:
- Orb of Slow (AIsb)
- Orb of Lightning (AIll)
- Attack black arrow bonus (AIdf)
- Melee Lightning Damage Bonus (AIlx)

They are similar, and allow you to make normal attacks have a chance of casting any ability with a target. Pretty cool no ?

However they have a known weird issue (bug from Blizzard) in which, you didn't ordered the owner of the ability to do something, and even if you have put the chance to cast the ability to 100%, it will never fire.

So I have been working on a "work-around" fix for this matter. After hours and hours of experimenting and programming, I got a detailed analysis and a custom fix that works !! It is MUI and I tested it OK with all the cases I thought of (patrol, hold position, attack, smart, move, guard-position consistence...) :)

What do you think of the coding ? May it be usefull for other players ? Should I provide a minimal map for players to download & install from ?




Detailed analysis:
First impressions
The ability only casts when players manually gave one of the following orders:
- Order targeting an object (unit): smart (right-click), attack
- Order targeting a point (any point, but order must not be over): attack ground, attack-move, patrol

Test cases (logging last order issued & current order)
OK - Unit is on patrol, and engages a target: lastevent=patrol, currentorder=_
OK - Unit is still doing an attack position order: lastevent=attack, currentorder=attack
OK - Unit is attacking an unit because of an attack or smart order: lastevent=attack/smart, currentorder=attacks/mart (both are identical)
KO - Unit is holding position: lastevent=holdposition, currentorder=_
KO - Unit aquires a new target, and was stopped before: lastevent=stop, current=_
KO - Unit aquires a new target, and last order was irrelevant (learn skill, use skill, etc...): lastevent=****, current=_
KO - Unit finished an attack position, smart or move order; then aquires a target: lastevent=attack/smart/move, currentorder=_
KO - Unit finished an attack or smart order (the unit died or became invisible/invincible), and aquires a new one: lastevent=smart/attack, currentorder = _

How to use:
When you create a unit with the bugged ability, or when a unit learns the bugged ability, just call RegisterUnitForOosFix(<unit>). My code handles the rest (target aquisition, improper orders handle, unit death...).

Triggers:
OosFix RegisterUnit (JASS API function)
JASS:
function RegisterUnitForOosFix takes unit u returns nothing
    // Add to registered units
    call GroupAddUnitSimple( u, udg_OosFixRegisteredUnits )
  
    // Unit is issued order events
    if ( IsTriggerEnabled( gg_trg_OosFix_UnitIssuedOrder ) == false ) then
        call EnableTrigger( gg_trg_OosFix_UnitIssuedOrder )
    endif
    call TriggerRegisterUnitEvent( gg_trg_OosFix_UnitIssuedOrder, u, EVENT_UNIT_ISSUED_TARGET_ORDER )
    call TriggerRegisterUnitEvent( gg_trg_OosFix_UnitIssuedOrder, u, EVENT_UNIT_ISSUED_POINT_ORDER )
    call TriggerRegisterUnitEvent( gg_trg_OosFix_UnitIssuedOrder, u, EVENT_UNIT_ISSUED_ORDER )
  
    // Unit acquires target event
    if ( IsTriggerEnabled( gg_trg_OosFix_UnitTargetAcquisition ) == false ) then
        call EnableTrigger( gg_trg_OosFix_UnitTargetAcquisition )
    endif
    call TriggerRegisterUnitEvent( gg_trg_OosFix_UnitTargetAcquisition, u, EVENT_UNIT_ACQUIRED_TARGET )
  
    // Unit dies
    if ( IsTriggerEnabled( gg_trg_OosFix_UnitDeath ) == false ) then
        call EnableTrigger( gg_trg_OosFix_UnitDeath )
    endif
    call TriggerRegisterUnitEvent( gg_trg_OosFix_UnitDeath, u, EVENT_UNIT_DEATH )

    // Unit leaves map
    if ( IsTriggerEnabled( gg_trg_OosFix_UnitLeaveMap ) == false ) then
        call EnableTrigger( gg_trg_OosFix_UnitLeaveMap )
    endif
endfunction

OosFix UnregisterUnit (JASS API function. Automatically called for non-hero dead units and removed units)
JASS:
function UnregisterUnitForOosFix takes unit u returns nothing
    // Flush datas from the hashtable
    call FlushChildHashtableBJ( GetHandleIdBJ(u), udg_GameCache )

    // Remove from the hold position resume group
    if ( IsUnitInGroup(u, udg_OosFixHoldPositionResumeGroup) == true ) then
        call GroupRemoveUnitSimple( u, udg_OosFixHoldPositionResumeGroup )
    endif
  
    // Remove from registered units
    call GroupRemoveUnitSimple( u, udg_OosFixRegisteredUnits )
  
    // If it was the last unit, pause the system
    if ( IsUnitGroupEmptyBJ(udg_OosFixRegisteredUnits) == true ) then
        call DisableTrigger( gg_trg_OosFix_UnitIssuedOrder )
        call DisableTrigger( gg_trg_OosFix_UnitTargetAcquisition )
        call DisableTrigger( gg_trg_OosFix_HoldPositionResume )
        call DisableTrigger( gg_trg_OosFix_UnitDeath )
        call DisableTrigger( gg_trg_OosFix_UnitLeaveMap )
    endif
endfunction

OosFix UnitIssuedOrder (GUI, disabled as default)
  • OosFix UnitIssuedOrder
    • Events
    • Conditions
    • Actions
      • Set TempBoolean = True
      • -------- Patrol order is ignored if distance is too short, because no action was taken by the unit --------
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (String((Issued order))) Equal to patrol
        • Then - Actions
          • Set TempPoint = (Position of (Ordered unit))
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (Distance between TempPoint and (Target point of issued order)) Less than 100.00
            • Then - Actions
              • Set TempBoolean = False
            • Else - Actions
              • Do nothing
          • Custom script: call RemoveLocation(udg_TempPoint)
        • Else - Actions
          • Do nothing
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • TempBoolean Equal to True
        • Then - Actions
          • Hashtable - Save (String((Issued order))) as (Key UnitIssuedOrder) of (Key (Ordered unit)) in GameCache
        • Else - Actions
          • Do nothing

OosFix UnitTargetAcquisition (GUI, disabled as default)
  • OosFix UnitTargetAcquisition
    • Events
    • Conditions
    • Actions
      • -------- Retrieve unit issued order --------
      • Set TempString1 = <Empty String>
      • Set TempString1 = (Load (Key UnitIssuedOrder) of (Key (Triggering unit)) from GameCache)
      • -------- Case A: unit was forced back to holdposition, but re-acquires a target --------
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • TempString1 Equal to holdpositionforced
        • Then - Actions
          • Trigger - Turn off OosFix UnitIssuedOrder <gen>
          • -------- Attack-move in place: preserves guard position. --------
          • Set TempPoint = (Position of (Triggering unit))
          • Unit - Order (Triggering unit) to Attack-Move To TempPoint
          • Custom script: call RemoveLocation(udg_TempPoint)
          • Trigger - Turn on OosFix UnitIssuedOrder <gen>
          • Unit Group - Add (Triggering unit) to OosFixHoldPositionResumeGroup
          • Trigger - Turn on OosFix HoldPositionResume <gen>
          • Hashtable - Save holdposition as (Key UnitIssuedOrder) of (Key (Triggering unit)) in GameCache
        • Else - Actions
          • Do nothing
      • -------- Case B: hold position order is under execution --------
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • TempString1 Equal to holdposition
        • Then - Actions
          • -------- Wait for the end of this acquisition, then reload variable to ensure MUI --------
          • Wait 0.01 seconds
          • Set TempString1 = <Empty String>
          • Set TempString1 = (Load (Key UnitIssuedOrder) of (Key (Triggering unit)) from GameCache)
          • Trigger - Turn off OosFix UnitIssuedOrder <gen>
          • -------- Force back to hold position. Cancels previous acquisition. --------
          • Unit - Order (Triggering unit) to Hold Position
          • Trigger - Turn on OosFix UnitIssuedOrder <gen>
          • Hashtable - Save holdpositionforced as (Key UnitIssuedOrder) of (Key (Triggering unit)) in GameCache
        • Else - Actions
          • Do nothing
      • -------- Case C: no order under execution for the moment --------
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • TempString1 Not equal to patrol
          • TempString1 Not equal to holdposition
          • TempString1 Not equal to holdpositionforced
          • (String((Current order of (Triggering unit)))) Equal to <Empty String>
        • Then - Actions
          • Trigger - Turn off OosFix UnitIssuedOrder <gen>
          • -------- Attack-move in place: preserves guard position. Do not change acquisition. --------
          • Set TempPoint = (Position of (Triggering unit))
          • Unit - Order (Triggering unit) to Attack-Move To TempPoint
          • Custom script: call RemoveLocation(udg_TempPoint)
          • Trigger - Turn on OosFix UnitIssuedOrder <gen>
        • Else - Actions
      • -------- Case DEFAULT: no change, preserves orders under-execution. --------
      • -------- Else: string(UnitIssuedOrder) == "patrol" or (string(UnitIssuedOrder) != "holdposition" string(current order) != <string vide>) --------

OosFix HoldPositionResume (GUI, disabled as default)
  • OosFix HoldPositionResume
    • Events
      • Time - Every 0.01 seconds of game time
    • Conditions
    • Actions
      • Unit Group - Pick every unit in OosFixHoldPositionResumeGroup and do (Actions)
        • Loop - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • ((Picked unit) is dead) Equal to True
            • Then - Actions
              • -------- Remove dead units from checks --------
              • Unit Group - Remove (Picked unit) from OosFixHoldPositionResumeGroup
            • Else - Actions
              • Set TempString1 = <Empty String>
              • Set TempString1 = (Load (Key UnitCurrentOrder) of (Key (Picked unit)) from GameCache)
              • Set TempString2 = <Empty String>
              • Set TempString2 = (Load (Key UnitIssuedOrder) of (Key (Picked unit)) from GameCache)
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • TempString1 Equal to (String((Current order of (Picked unit))))
                • Then - Actions
                  • -------- Current order is unchanged since last check, do nothing --------
                  • Do nothing
                • Else - Actions
                  • -------- Save the new current order value --------
                  • Hashtable - Save (String((Current order of (Picked unit)))) as (Key UnitCurrentOrder) of (Key (Picked unit)) in GameCache
                  • -------- Case A: last issued order is hold position (normal or forced), and current order changes from something else to idle --------
                  • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                    • If - Conditions
                      • TempString1 Not equal to holdposition
                      • Or - Any (Conditions) are true
                        • Conditions
                          • TempString2 Equal to holdposition
                          • TempString2 Equal to holdpositionforced
                      • (String((Current order of (Picked unit)))) Equal to <Empty String>
                    • Then - Actions
                      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                        • If - Conditions
                          • TempString2 Equal to holdpositionforced
                        • Then - Actions
                          • Hashtable - Save holdposition as (Key UnitIssuedOrder) of (Key (Picked unit)) in GameCache
                        • Else - Actions
                          • Do nothing
                      • Trigger - Turn off OosFix UnitIssuedOrder <gen>
                      • -------- Force back to hold position. --------
                      • Unit - Order (Picked unit) to Hold Position
                      • Trigger - Turn on OosFix UnitIssuedOrder <gen>
                      • Unit Group - Remove (Picked unit) from OosFixHoldPositionResumeGroup
                    • Else - Actions
                      • Do nothing
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (OosFixHoldPositionResumeGroup is empty) Equal to True
        • Then - Actions
          • Trigger - Turn off (This trigger)
        • Else - Actions
          • Do nothing

OosFix UnitDeath (GUI, disabled as default)
  • OosFix UnitDeath
    • Events
    • Conditions
      • ((Triggering unit) is A Hero) Equal to False
    • Actions
      • -------- Non-hero units cannot be revived, clear variables --------
      • Custom script: call UnregisterUnitForOosFix( GetTriggerUnit() )

OosFix UnitRemoved (GUI, disabled as default)
  • OosFix UnitRemoved
    • Events
      • Unit - A unit leaves (Entire map)
    • Conditions
      • ((Triggering unit) is in OosFixRegisteredUnits) Equal to True
    • Actions
      • Custom script: call UnregisterUnitForOosFix( GetTriggerUnit() )
 
Last edited:
Level 12
Joined
Feb 27, 2019
Messages
399
You don't need all those do nothings. Remove all the simples in the Group functions for a bit of more optimization.

OK I'll remove the DoNothing(). About the "Simple" thing:
- GroupAddUnitSimple adds a single unit to a group
- GroupAddUnit adds a group to a group. So it would require to create a temporary group of 1 unit to use no ?

Same for GroupRemoveUnitSimple :)
 
Level 11
Joined
Jul 4, 2016
Messages
627
That isn't the case. GroupAddGroup and GroupRemoveGroup is what affects groups.

The simple functions calls the "non-simple" functions. It also switches the position of the inputs. Essentially you are going an extra step to do the same thing.

JASS:
function GroupRemoveUnitSimple takes unit whichUnit, group whichGroup returns nothing
    call GroupRemoveUnit(whichGroup, whichUnit)
endfunction

function GroupRemoveUnit takes group whichGroup, unit whichUnit returns nothing
endfunction
 
Status
Not open for further replies.
Top