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

Think This Will Desync, or is it safe?

Status
Not open for further replies.
Level 31
Joined
Jul 10, 2007
Messages
6,306
This prevents players from controlling units by switching unit control when that player selects that unit. The problem with switching unit control is that the unit is automatically deselected, which can cause some issues.

Whether the unit is selected or not is stored locally for each player
set selected = IsUnitSelected(u, GetLocalPlayer())

From here, the unit ownership changes and the unit is selected again.
JASS:
if (selected) then
    call SelectUnit(u, true)
endif

My question is does anyone know if this code will desync or not? It's very iffy because of select events. My thoughts are that it would only desync if a trigger was run that had desyncing code in it ; ). Considering that the only triggers in this specific map that have select/deselect events in them are disabled during this, I'm not sure. The events might still go through some sort of call stack (although it would be empty), thus causing a weird memory issue.


Anyways, yaar ^)^.


This is part of an advanced queue for massing units. When a player has a large number of units sent in a game like Tower Wars or Hero Wars, they can't build their own buildings. To counter this, the sends are spread throughout the team and a computer player. When a player selects a unit that they control that they shouldn't be able to control, the unit is then changed over to the computer player so that the player can't control the unit. When the player deselects the unit, that player gets control of the unit.

Giving/Taking away control isn't a very elegant solution to preventing players from having control over certain units, but it's the only one I could come up with. If the players had no control over any units (plain observers), they could have their move/attack abilities disabled, but they'd still be able to stop the unit, which is no good.

JASS:
struct PreventSelect extends array
    private static trigger selectt
    private static trigger deselectt
    private static method select takes nothing returns boolean
        local unit u = GetTriggerUnit()
        local integer i = GetUnitUserData(u)
        local boolean selected
        if (isSend[i] and GetTriggerPlayer() == GetOwningPlayer(u)) then
            call DisableTrigger(deselectt)
            call DisableTrigger(selectt)
            set selected = IsUnitSelected(u, GetLocalPlayer())
            call SetUnitOwner(u, deferPlayer[GetPlayerId(GetTriggerPlayer())], false)
            call IssuePointOrder(u, "move", unitMoveX[i], unitMoveY[i])
            if (selected) then
                call SelectUnit(u, true)
            endif
            call EnableTrigger(selectt)
            call EnableTrigger(deselectt)
        endif
        set u = null
        return false
    endmethod
    private static method deselect takes nothing returns boolean
        local unit u = GetTriggerUnit()
        local integer i = GetUnitUserData(u)
        local boolean selected
        if (isSend[i] and GetTriggerPlayer() == unitOwner[i]) then
            call DisableTrigger(deselectt)
            call DisableTrigger(selectt)
            set selected = IsUnitSelected(u, GetLocalPlayer()) and GetLocalPlayer() != GetTriggerPlayer()
            call SetUnitOwner(u, GetTriggerPlayer(), false)
            call IssuePointOrder(u, "move", unitMoveX[i], unitMoveY[i])
            if (selected) then
                call SelectUnit(u, true)
            endif
            call EnableTrigger(selectt)
            call EnableTrigger(deselectt)
        endif
        set u = null
        return false
    endmethod
    private static method onInit takes nothing returns nothing
        local integer i = 9
        set selectt = CreateTrigger()
        set deselectt = CreateTrigger()
        call TriggerAddCondition(selectt, Condition(function thistype.select))
        call TriggerAddCondition(deselectt, Condition(function thistype.deselect))
        loop
            call TriggerRegisterPlayerUnitEvent(selectt, Player(i), EVENT_PLAYER_UNIT_SELECTED, null)
            call TriggerRegisterPlayerUnitEvent(deselectt, Player(i), EVENT_PLAYER_UNIT_DESELECTED, null)
            exitwhen 0 == i
            set i = i - 1
        endloop
    endmethod
endstruct
 
Level 26
Joined
Aug 18, 2009
Messages
4,097
When a trigger/thread is run, it allocates a new id and is sure to desync if you only do this locally. About SelectUnit being async-friendly, guess what:

JASS:
function SelectUnitForPlayerSingle takes unit whichUnit, player whichPlayer returns nothing
    if (GetLocalPlayer() == whichPlayer) then
        // Use only local code (no net traffic) within this block to avoid desyncs.
        call ClearSelection()
        call SelectUnit(whichUnit, true)
    endif
endfunction

Unit selections/deselections are broadcasted like player inputs. So although the function is only called on your client, the information of the unit being selected reaches the other players and therefore runs the events for everyone.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
How's this then?

JASS:
struct PreventSelect extends array
    private static trigger selectt
    private static trigger deselectt
    private static method select takes nothing returns boolean
        local unit u = GetTriggerUnit()
        local integer i = GetUnitUserData(u)
        local boolean selected
        if (isSend[i] and GetTriggerPlayer() == GetOwningPlayer(u)) then
            call DisableTrigger(deselectt)
            call DisableTrigger(selectt)
            call SyncSelections()
            set selected = IsUnitSelected(u, GetLocalPlayer())
            call SetUnitOwner(u, deferPlayer[GetPlayerId(GetTriggerPlayer())], false)
            call IssuePointOrder(u, "move", unitMoveX[i], unitMoveY[i])
            if (selected) then
                call SelectUnit(u, true)
            endif
            call EnableTrigger(selectt)
            call EnableTrigger(deselectt)
        endif
        set u = null
        return false
    endmethod
    private static method deselect takes nothing returns boolean
        local unit u = GetTriggerUnit()
        local integer i = GetUnitUserData(u)
        local boolean selected
        if (isSend[i] and GetTriggerPlayer() == unitOwner[i]) then
            call DisableTrigger(deselectt)
            call DisableTrigger(selectt)
            call SyncSelections()
            set selected = IsUnitSelected(u, GetLocalPlayer()) and GetLocalPlayer() != GetTriggerPlayer()
            call SetUnitOwner(u, GetTriggerPlayer(), false)
            call IssuePointOrder(u, "move", unitMoveX[i], unitMoveY[i])
            if (selected) then
                call SelectUnit(u, true)
            endif
            call EnableTrigger(selectt)
            call EnableTrigger(deselectt)
        endif
        set u = null
        return false
    endmethod
    private static method onInit takes nothing returns nothing
        local integer i = 9
        set selectt = CreateTrigger()
        set deselectt = CreateTrigger()
        call TriggerAddCondition(selectt, Condition(function thistype.select))
        call TriggerAddCondition(deselectt, Condition(function thistype.deselect))
        loop
            call TriggerRegisterPlayerUnitEvent(selectt, Player(i), EVENT_PLAYER_UNIT_SELECTED, null)
            call TriggerRegisterPlayerUnitEvent(deselectt, Player(i), EVENT_PLAYER_UNIT_DESELECTED, null)
            exitwhen 0 == i
            set i = i - 1
        endloop
    endmethod
endstruct
 
Level 26
Joined
Aug 18, 2009
Messages
4,097
What I wanted to tell is that although the SelectUnit-line is between DisableTrigger and EnableTrigger, due to the delay the trigger will detect the selecton since it was already enabled again and therefore still fire. So waiting for synchronization would actually make sence after SelectUnit. This of course pauses the trigger, other code can run in the meantime like another instance of this trigger. The first run could enable the trigger again before the second run has finished, whereas through this second run the trigger would be executed. Maybe build a counter how many times it has to be blocked.

edit: I just see that you do not want to run another trigger but just avoid recursion. This does not need a counter then of course since the script will not be executed again until previous has finished.

Orders can still be given by player during the selection delay btw, you only overwrite that after the time it needs to sync.

But how do you want to avoid orders while SyncSelections is running with the trigger still disabled? Give it to another player in the mean time or so. Really, there was often the question as how to take away control from single units but there was no really good solution.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Like this?

JASS:
struct PreventSelect extends array
    private static trigger selectt
    private static trigger deselectt
    private static method select takes nothing returns boolean
        local unit u = GetTriggerUnit()
        local integer i = GetUnitUserData(u)
        local boolean selected
        if (isSend[i] and GetTriggerPlayer() == GetOwningPlayer(u)) then
            call DisableTrigger(deselectt)
            call DisableTrigger(selectt)
            set selected = IsUnitSelected(u, GetLocalPlayer())
            call SetUnitOwner(u, deferPlayer[GetPlayerId(GetTriggerPlayer())], false)
            call IssuePointOrder(u, "move", unitMoveX[i], unitMoveY[i])
            if (selected) then
                call SelectUnit(u, true)
            endif
            call SyncSelections()
            call EnableTrigger(selectt)
            call EnableTrigger(deselectt)
        endif
        set u = null
        return false
    endmethod
    private static method deselect takes nothing returns boolean
        local unit u = GetTriggerUnit()
        local integer i = GetUnitUserData(u)
        local boolean selected
        if (isSend[i] and GetTriggerPlayer() == unitOwner[i]) then
            call DisableTrigger(deselectt)
            call DisableTrigger(selectt)
            set selected = IsUnitSelected(u, GetLocalPlayer()) and GetLocalPlayer() != GetTriggerPlayer()
            call SetUnitOwner(u, GetTriggerPlayer(), false)
            call IssuePointOrder(u, "move", unitMoveX[i], unitMoveY[i])
            if (selected) then
                call SelectUnit(u, true)
            endif
            call SyncSelections()
            call EnableTrigger(selectt)
            call EnableTrigger(deselectt)
        endif
        set u = null
        return false
    endmethod
    private static method onInit takes nothing returns nothing
        local integer i = 9
        set selectt = CreateTrigger()
        set deselectt = CreateTrigger()
        call TriggerAddCondition(selectt, Condition(function thistype.select))
        call TriggerAddCondition(deselectt, Condition(function thistype.deselect))
        loop
            call TriggerRegisterPlayerUnitEvent(selectt, Player(i), EVENT_PLAYER_UNIT_SELECTED, null)
            call TriggerRegisterPlayerUnitEvent(deselectt, Player(i), EVENT_PLAYER_UNIT_DESELECTED, null)
            exitwhen 0 == i
            set i = i - 1
        endloop
    endmethod
endstruct
 
Level 26
Joined
Aug 18, 2009
Messages
4,097
Why do you make the player select the unit on deselection event and restore the order? Deselection causes no harm.

onSelection, you change the player and restore the order although selection does not mean that the player has any given orders and the unit is still selected afterwards, so the player can distribute them freely without triggering this trigger.

Why don't you interrupt the order on order event? The selection event does not ensure either that the unit receives no order by the player.
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
SyncSelection acts as a wait (like other sync natives related to gamecache), therefore it can't be used in a trigger condition, only in a trigger action.

You could simply link booleans (or use a group, whatever...) to the unit when you select/deselect it and then handle them.

Btw, it makes sense that SelectUnit can safely be used in a local block, else "natural" player selections wouldn't work that much :p
 
Status
Not open for further replies.
Top