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

Desync when removing local hidden unit

Status
Not open for further replies.
Level 15
Joined
Nov 30, 2007
Messages
1,202
I'm trying to hide a unit locally but it seems to be causing desync. Would it work if i unhid the unit for all before removing it?

What the code does is basically switch between different units to be displayed during picking phase.

Critical section:

JASS:
private function SetSelection takes player p, integer heroIndex returns nothing
        local integer pid = GetPlayerId(p)
        local SpellMenu menu = UserData(pid).menu
        local SpellOption o
        if UserData(pid).dummy != null then      
            call RemoveUnit(UserData(pid).dummy)            // Will removing a locally hidden unit cause desync?
        endif
        set UserData(pid).heroId = heroIndex
        set UserData(pid).dummy = CreateUnit(p, Hero(heroIndex).type, x, y, f)
        call UnitAddAbility(UserData(pid).dummy, 'Aloc')     // locust
        if not (GetLocalPlayer() == p) then                 // Hide it for all except one                                         
            call ShowUnit(UserData(pid).dummy, false)
        endif
        if UserData(pid).option != 0 then
            set o = menu.get(menu.indexOf(UserData(pid).option))
            call o.setLocalEnabled(pid, true)
        endif
        set o = Hero(heroIndex).optionKey
        call o.setLocalEnabled(pid, false)
        set UserData(pid).option = o
        if GetLocalPlayer() == p then
            call PanCameraToTimed(GetUnitX(UserData(pid).dummy), GetUnitY(UserData(pid).dummy), 0)
        endif
        // Run callback
        set lastPickedHeroType = Hero(heroIndex).type
        if trgOnSelect != null then
            call TriggerEvaluate(trgOnSelect)
        endif
    endfunction

JASS:
library HeroPickPlugin uses SpellOptionsMenu, RegisterPlayerEvent
 
    /*
        This is a simple Hero Pick system which provides a menu for selecting a hero.
    */
 
    globals
        private constant string MENU_TITLE             = "Hero Selection"
        private constant string RANDOM_HERO_BTN        = "ReplaceableTextures\\CommandButtons\\BTNSelectHeroOn.blp"
        private constant string RANDOM_HERO_DISBTN     = "ReplaceableTextures\\CommandButtons\\BTNSelectHeroOff.blp"
        private constant string CONFIRM_BTN            = "ReplaceableTextures\\CommandButtons\\BTNSell.blp"
 
        private real x        
        private real y
        private real f
 
        private integer lastPickedHeroType
 
        private trigger trgOnPick     = null
        private trigger trgOnSelect = null
 
        private integer numHeroes     = 0                        
        private unit u
        private string s
    endglobals
 
    /*
        Change to appropriate setup
    */
    private function SetupSelectionZone takes nothing returns nothing
        set x = GetRectCenterX(gg_rct_HeroSelection)
        set y = GetRectCenterY(gg_rct_HeroSelection)
        set f = 270
    endfunction
 
    // **************************************************
    // *** System Code **********************************
    // **************************************************
 
    private function GetHeroName takes integer unitTypeId returns string
        set u = CreateUnit(Player(bj_PLAYER_NEUTRAL_EXTRA), unitTypeId, 0, 0, 0)
        set s = GetUnitName(u)
        call RemoveUnit(u)
        set u = null
        return s
    endfunction
 
    private struct UserData extends array
        unit dummy
        integer heroId
        integer option
        SpellMenu menu
 
        method clear takes nothing returns nothing
            if .dummy != null then
                call RemoveUnit(.dummy)
                set .dummy = null
            endif
            if .menu != 0 then
                call .menu.destroy()
                set .menu = 0
                call BJDebugMsg("Destroyed...")
            endif
            set .heroId = -1
            set .option = 0
            call BJDebugMsg("Clear completed...")
        endmethod
 
        // will remove any menus that are left open
        private static method onLeave takes nothing returns nothing
            local integer pid = GetPlayerId(GetTriggerPlayer())
            if UserData(pid).menu != 0 then
                call UserData(pid).clear()
            endif
        endmethod
 
        private static method onInit takes nothing returns nothing
            local integer i = 0
            loop
                exitwhen i == bj_MAX_PLAYERS
                set UserData(i).heroId = -1
                set i = i + 1
            endloop
            call SetupSelectionZone()
            call RegisterAnyPlayerEvent(EVENT_PLAYER_LEAVE, function thistype.onLeave)
        endmethod
    endstruct
 
    private struct Hero extends array
        integer type
        string btn
        string disbtn
        string label
        string tooltip
        integer optionKey
 
        method setLabel takes string s returns thistype
            set .label = s
            return this
        endmethod
 
        method setType takes integer t returns thistype
            set .type = t
            call .setLabel(GetHeroName(t))
            return this
        endmethod
 
        method setTooltip takes string s returns thistype
            set .tooltip = s
            return this
        endmethod
 
        method setBtn takes string s returns thistype
            set .btn = s
            return this
        endmethod
 
        method setDisbtn takes string s returns thistype
            set .disbtn = s
            return this
        endmethod
    endstruct
 
    private function SetSelection takes player p, integer heroIndex returns nothing
        local integer pid = GetPlayerId(p)
        local SpellMenu menu = UserData(pid).menu
        local SpellOption o
        if UserData(pid).dummy != null then     
            call RemoveUnit(UserData(pid).dummy)            // Will removing a locally hidden unit cause desync?
        endif
        set UserData(pid).heroId = heroIndex
        set UserData(pid).dummy = CreateUnit(p, Hero(heroIndex).type, x, y, f)
        call UnitAddAbility(UserData(pid).dummy, 'Aloc')     // locust
        if not (GetLocalPlayer() == p) then                 // Hide it for all except one                                        
            call ShowUnit(UserData(pid).dummy, false)
        endif
        if UserData(pid).option != 0 then
            set o = menu.get(menu.indexOf(UserData(pid).option))
            call o.setLocalEnabled(pid, true)
        endif
        set o = Hero(heroIndex).optionKey
        call o.setLocalEnabled(pid, false)
        set UserData(pid).option = o
        if GetLocalPlayer() == p then
            call PanCameraToTimed(GetUnitX(UserData(pid).dummy), GetUnitY(UserData(pid).dummy), 0)
        endif
        // Run callback
        set lastPickedHeroType = Hero(heroIndex).type
        if trgOnSelect != null then
            call TriggerEvaluate(trgOnSelect)
        endif
    endfunction
 
    private function GetRandomHero takes player p returns integer
        local integer rdm
        local integer pid = GetPlayerId(p)
        if numHeroes > 1 then
            loop
                set rdm = GetRandomInt(0, numHeroes - 1)
                if rdm != UserData(pid).heroId then
                    return rdm
                endif
            endloop
        endif
        return 0
    endfunction
 
    private function OnRandomPick takes nothing returns nothing
        local player p = GetTriggerPlayer()
        call SetSelection(p, GetRandomHero(p))
        set p = null
    endfunction
 
    private function OnHeroPick takes nothing returns nothing
        local SpellOption o = GetTriggerSpellOption()
        call SetSelection(GetTriggerPlayer(), o.userData)
    endfunction
 
    private function OnConfirm takes nothing returns nothing
        local player p = GetTriggerPlayer()
        local integer pid = GetPlayerId(p)
        call UserData(pid).menu.close(p)
        set lastPickedHeroType = Hero(UserData(pid).heroId).type
        call UserData(pid).clear()
        // Run callback
        if trgOnPick != null then
            call TriggerEvaluate(trgOnPick)
        endif
        set p = null
    endfunction
 
    private function AddMenuOptions takes SpellMenu menu returns nothing
        local integer i = 0
        local Hero h
        local SpellOption o
        loop
            exitwhen i >= numHeroes
            set h = Hero(i)
            set o = menu.createOption(0, h.label, h.tooltip, h.btn, h.disbtn, function OnHeroPick)
            set h.optionKey = o
            set o.userData = i
            set i = i + 1
        endloop
        set o = menu.createOption(3, "Random Hero", "Choses a random hero for you.", RANDOM_HERO_BTN, RANDOM_HERO_DISBTN, function OnRandomPick)
        call menu.lock(o, 3)
        set o = menu.createOption(7, "Confirm Hero", " ", CONFIRM_BTN, CONFIRM_BTN, function OnConfirm)
        call menu.lock(o, 7)
    endfunction
 
    // **************************************************
    // *** Public Interface *****************************
    // **************************************************
 
    function AddPickableHero takes integer unitTypeId, string btn, string disbtn, string description returns nothing
        call Hero(numHeroes).setType(unitTypeId).setTooltip(description).setBtn(btn).setDisbtn(disbtn)
        set numHeroes = numHeroes + 1
    endfunction
 
    function OpenHeroPickMenu takes player p returns nothing
        local integer pid = GetPlayerId(p)
        if UserData(pid).menu == 0 then
            set UserData(pid).menu = SpellMenu.create(MENU_TITLE, true)
            call AddMenuOptions(UserData(pid).menu)
        endif
        call SetSelection(p, GetRandomHero(p))
        call UserData(pid).menu.open(p)
    endfunction
 
    function OpenHeroPickMenuAll takes nothing returns nothing
        local integer i = 0
        local player p
        loop
            exitwhen i == bj_MAX_PLAYERS
            set p = Player(i)
            if (GetPlayerSlotState(p) == PLAYER_SLOT_STATE_PLAYING and /*
                */ GetPlayerController(p) == MAP_CONTROL_USER) then
                call OpenHeroPickMenu(p)
            endif
            set i = i + 1
        endloop
        set p = null
    endfunction
 
    function GetPickedHeroDummy takes player p returns unit
        return UserData(GetPlayerId(p)).dummy
    endfunction
 
    function GetPickedHeroType takes nothing returns integer
        return lastPickedHeroType
    endfunction
 
    function OnHeroPickEvent takes code c returns nothing
        if trgOnPick == null then
            set trgOnPick = CreateTrigger()
        endif
        call TriggerAddCondition(trgOnPick, Condition(c))
    endfunction
 
    function OnHeroPickChangeEvent takes code c returns nothing
        if trgOnSelect == null then
            set trgOnSelect = CreateTrigger()
        endif
        call TriggerAddCondition(trgOnSelect, Condition(c))
    endfunction
 
endlibrary
Usage:
JASS:
scope HeroPick initializer Init
    /*
        Opens the pick menu for all playing players
    */
    private function StartHeroPick takes nothing returns nothing
        call OpenHeroPickMenuAll()
    endfunction
    private function OnRepick takes nothing returns nothing
        call OpenHeroPickMenu(GetTriggerPlayer())
    endfunction
 
    private function OnHeroPick takes nothing returns nothing
        local player p = GetTriggerPlayer()
        local integer unitTypeId = GetPickedHeroType()
        local unit u = CreateUnit(p, unitTypeId, 0, 0, 0)
        if GetLocalPlayer() == p then
            call PanCameraToTimed(GetUnitX(u), GetUnitY(u), 0)
            call SelectUnit(u, true)
        endif
        set p = null
        set u = null
    endfunction
 
    private function OnHeroChange takes nothing returns nothing
        local unit u =  GetPickedHeroDummy(GetTriggerPlayer())
        local effect eff = AddSpecialEffectTargetUnitBJ( "origin", u, "Abilities\\Spells\\Items\\TomeOfRetraining\\TomeOfRetrainingCaster.mdl")
        call DestroyEffect(eff)
        set eff = null
        set u = null
    endfunction
    private function Init takes nothing returns nothing
        // Chat event
        call Command.register("-repick", function OnRepick)
   
        // Callback registration
        call OnHeroPickEvent(function OnHeroPick)
        call OnHeroPickChangeEvent(function OnHeroChange)
   
        // Paladin
        call AddPickableHero('Hpal',/*
        */"ReplaceableTextures\\CommandButtons\\BTNHeroPaladin.blp",/*
        */"ReplaceableTextures\\CommandButtonsDisabled\\DISBTNHeroPaladin.blp",/*
        */"Warrior Hero, exceptional at defense and augmenting nearby friendly troops. Can learn Holy Light, Divine Shield, Devotion Aura and Resurrection. |n|n|cffffcc00Attacks land units.|r")
   
        // Blood Mage
        call AddPickableHero('Hblm',/*
        */"ReplaceableTextures\\CommandButtons\\BTNHeroBloodElfPrince.blp",/*
        */"ReplaceableTextures\\CommandButtonsDisabled\\DISBTNHeroBloodElfPrince.blp", /*
        */"Mystical Hero, adept at controlling magic and ranged assaults. Can learn Flame Strike, Banish, Siphon Mana and Phoenix. |n|n|cffffcc00Attacks land and air units.|r")
   
        // Archmage
        call AddPickableHero('Hamg',/*
        */"ReplaceableTextures\\CommandButtons\\BTNHeroArchMage.blp",/*
        */"ReplaceableTextures\\CommandButtonsDisabled\\DISBTNHeroArchMage.blp",/*
        */"Mystical Hero, adept at ranged assaults. Can learn Blizzard, Summon Water Elemental, Brilliance Aura and Mass Teleport. |n|n|cffffcc00Attacks land and air units.|r")
   
        call StartHeroPick()
    endfunction
endscope

Proposed solution:

JASS:
method safeDummyRemoval takes nothing returns nothing
            if GetLocalPlayer() == GetOwningPlayer(.dummy) then
                call ShowUnit(.dummy, false) // Now everyone should have the same unit state
            endif
            call RemoveUnit(.dummy)
        endmethod


I'm also uncertain if its the special effect created on the unit or the removal of the previous dummy unit that causes desync.
 
Last edited:
Level 15
Joined
Nov 30, 2007
Messages
1,202
The only way I got local unit hiding to work was with vertex coloring and locust. But that method has its limits.
Yes, from what Sir Moriarty explained any code block executed that is changed due to unit state changes will cause a desync to avoid these traps one needs to be change back the state so it's the same for all players before executing.
 
Status
Not open for further replies.
Top