[Crash] Players desync when more than 3 join

Level 3
Joined
Nov 9, 2021
Messages
8
The desync happens at the very start when the game starts and it only happens when there's 3 or more people, when 4 it desyncs 1 person when 5 it desyncs 2 etc. I have managed to identify triggers that cause it but I can't seem to be able to find why they do it so any help would be appreciated! I will also post any triggers that happen on map initialization and elapsed game time is 0.00 seconds as well just in case.

It's basically the Codeless Save/Load by TriggerHappy with some modifications made by DarkPacific etc to make it work in later version of reforged.
JASS:
library SyncHelper

    globals
        public constant string SYNC_PREFIX = "S"
    endglobals
 
    private keyword INITS
 
    private struct Sync extends array
        static trigger Trigger = CreateTrigger()
        implement INITS
    endstruct
 
    function SyncString takes string s returns boolean
        return BlzSendSyncData(SYNC_PREFIX, s)
    endfunction
 
    function OnSyncString takes code func returns triggeraction
        return TriggerAddAction(Sync.Trigger, func)
    endfunction
 
    function RemoveSyncString takes triggeraction t returns nothing
        call TriggerRemoveAction(Sync.Trigger, t)
    endfunction
 
    private module INITS
        private static method onInit takes nothing returns nothing
            local integer i  = 0
        
            loop
                call BlzTriggerRegisterPlayerSyncEvent(.Trigger, Player(i), SYNC_PREFIX, false)
                set i = i + 1
            
                exitwhen i == bj_MAX_PLAYER_SLOTS
            endloop
        endmethod
    endmodule
endlibrary

JASS:
library SaveFile requires FileIO
 
    private keyword SaveFileInit
 
    struct SaveFile extends array
        static constant string ManualPath = "Manual"
        static constant string InvalidPath = "Unknown"
        static constant integer MIN = 1
        static constant integer MAX = 10
    
        private File file
 
        static method operator Folder takes nothing returns string
            return udg_MapName
        endmethod

        static method getPath takes integer slot returns string
            if (slot == 0) then
                return .Folder + "\\SaveSlot_" + .InvalidPath + ".pld"
            elseif (slot > 0 and (slot < .MIN or slot > .MAX)) then
                return .Folder + "\\SaveSlot_" + .InvalidPath + ".pld"
            elseif (slot < 0) then
                return .Folder + "\\SaveSlot_" + .ManualPath + ".pld"
            endif
            return .Folder + "\\SaveSlot_" + I2S(slot) + ".pld"
        endmethod

        static method getBackupPath takes integer slot, integer saveNumber returns string
            if (slot == 0) then
                return .Folder + "\\Backups\\SaveSlot_" + .InvalidPath + ".pld"
            elseif (slot > 0 and (slot < .MIN or slot > .MAX)) then
                return .Folder + "\\Backups\\SaveSlot_" + .InvalidPath + ".pld"
            elseif (slot < 0) then
                return .Folder + "\\Backups\\SaveSlot_" + .ManualPath + ".pld"
            endif
            return .Folder + "\\Backups\\SaveSlot_" + I2S(slot) + "_" + I2S(saveNumber) + ".pld"
        endmethod
 
        static method create takes player p, string title, integer slot, integer saveNumber, string data returns thistype
            if (GetLocalPlayer() == p) then
                call FileIO_Write(.getPath(slot), title + "\n" + data)
                call FileIO_Write(.getBackupPath(slot, saveNumber), title + "\n" + data)
            endif
            return slot
        endmethod
    
        static method clear takes player p, integer slot, integer saveNumber returns thistype
            if (GetLocalPlayer() == p) then
                call FileIO_Write(.getPath(slot), "")
                call FileIO_Write(.getBackupPath(slot, saveNumber), "")
            endif
            return slot
        endmethod
    
        static method exists takes integer slot, integer saveNumber returns boolean // async
            if saveNumber == 0 then
                return StringLength(FileIO_Read(.getPath(slot))) > 1
            else
                return StringLength(FileIO_Read(.getBackupPath(slot, saveNumber))) > 1
            endif
        endmethod
    
        method getLines takes integer line, boolean includePrevious, integer saveNumber returns string // async
            local string contents   = FileIO_Read(.getPath(this))
            local integer len       = StringLength(contents)
            local string char       = null
            local string buffer     = ""
            local integer curLine   = 0
            local integer i         = 0
        
            //call BJDebugMsg("Reading " + .getPath(this) + " with length " + I2S(len))
            //call BJDebugMsg("Contents " + contents)

            if saveNumber > 0 then
                set contents = FileIO_Read(.getBackupPath(this, saveNumber))
                set len = StringLength(contents)
            
                //call BJDebugMsg("saveNumber > 0: Reading Backup")
                //call BJDebugMsg("Reading " + .getBackupPath(this, saveNumber) + " with length " + I2S(len))
                //call BJDebugMsg("Contents " + contents)
            endif
            loop
                exitwhen i > len
                set char = SubString(contents, i, i + 1)
                if (char == "\n") then
                    set curLine = curLine + 1
                    if (curLine > line) then
                        return buffer
                    endif
                    if (not includePrevious) then
                        set buffer = ""
                    endif
                else
                    set buffer = buffer + char
                endif
                set i = i + 1
            endloop
            if (curLine == line) then
                return buffer
            endif
            return null
        endmethod
    
        method getLine takes integer line returns string // async
            return .getLines(line, false, 0)
        endmethod
    
        method getTitle takes integer saveNumber returns string // async
            return .getLines(0, false, saveNumber)
        endmethod
    
        method getData takes nothing returns string // async
            return .getLines(1, false, 0)
        endmethod

        method getBackupData takes integer saveNumber returns string // async
            return .getLines(1, false, saveNumber)
        endmethod
    
        implement SaveFileInit
    endstruct
 
    private module SaveFileInit
        private static method onInit takes nothing returns nothing
            //set thistype.Folder = udg_MapName
        endmethod
    endmodule
 
endlibrary

JASS:
library FileIO
/***************************************************************
*
*   v1.1.0, by TriggerHappy
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*
*   Provides functionality to read and write files.
*   _________________________________________________________________________
*   1. Requirements
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*       - Patch 1.29 or higher.
*       - JassHelper (vJASS)
*   _________________________________________________________________________
*   2. Installation
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*   Copy the script to your map and save it.
*   _________________________________________________________________________
*   3. API
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*       struct File extends array
*
*           static constant integer AbilityCount
*           static constant integer PreloadLimit
*
*           readonly static boolean ReadEnabled
*           readonly static integer Counter
*           readonly static integer array List
*
*           static method open takes string filename returns File
*           static method create takes string filename returns File
*
*           ---------
*
*           method write takes string value returns File
*           method read takes nothing returns string
*
*           method readEx takes boolean close returns string
*           method readAndClose takes nothing returns string
*           method readBuffer takes nothing returns string
*           method writeBuffer takes string contents returns nothing
*           method appendBuffer takes string contents returns nothing
*
*           method close takes nothing returns nothing
*
*           public function Write takes string filename, string contents returns nothing
*           public function Read takes string filename returns string
*
***************************************************************/
 
    globals
        // Enable this if you want to allow the system to read files generated in patch 1.30 or below.
        // NOTE: For this to work properly you must edit the 'Amls' ability and change the levels to 2
        // as well as typing something in "Level 2 - Text - Tooltip - Normal" text field.
        //
        // Enabling this will also cause the system to treat files written with .write("") as empty files.
        //
        // This setting is really only intended for those who were already using the system in their map
        // prior to patch 1.31 and want to keep old files created with this system to still work.
        private constant boolean BACKWARDS_COMPATABILITY = false
    endglobals
 
    private keyword FileInit

    struct File extends array
        static constant integer AbilityCount = 10
        static constant integer PreloadLimit = 200
    
        readonly static integer Counter = 0
        readonly static integer array List
        readonly static integer array AbilityList
    
        readonly static boolean ReadEnabled = false
 
        readonly string filename

        private string buffer
    
        static method open takes string filename returns thistype
            local thistype this = .List[0]
    
            if (this == 0) then
                set this = Counter + 1
                set Counter = this
            else
                set .List[0] = .List[this]
            endif
        
            set this.filename = filename
            set this.buffer = null
        
            debug if (this >= JASS_MAX_ARRAY_SIZE) then
            debug   call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0, 120, "FileIO(" + filename + ") WARNING: Maximum instance limit " + I2S(JASS_MAX_ARRAY_SIZE) + " reached.")
            debug endif
        
            return this
        endmethod
    
        // This is used to detect invalid characters which aren't supported in preload files.
        static if (DEBUG_MODE) then
            private static method validateInput takes string contents returns string
                local integer i = 0
                local integer l = StringLength(contents)
                local string ch = ""
                loop
                    exitwhen i >= l
                    set ch = SubString(contents, i, i + 1)
                    if (ch == "\\") then
                        return ch
                    elseif (ch == "\"") then
                        return ch
                    endif
                    set i = i + 1
                endloop
                return null
            endmethod
        endif

        method write takes string contents returns thistype
            local integer i = 0
            local integer c = 0
            local integer len = StringLength(contents)
            local integer lev = 0
            local string prefix = "-" // this is used to signify an empty string vs a null one
            local string chunk
            debug if (.validateInput(contents) != null) then
            debug   call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0, 120, "FileIO(" + filename + ") ERROR: Invalid character |cffffcc00" + .validateInput(contents) + "|r")
            debug   return this
            debug endif
        
            set this.buffer = null
        
            // Check if the string is empty. If null, the contents will be cleared.
            if (contents == "") then
                set len = len + 1
            endif
        
            // Begin to generate the file
            call PreloadGenClear()
            call PreloadGenStart()
            loop
                exitwhen i >= len
            
                debug if (c >= .AbilityCount) then
                debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0, 120, "FileIO(" + filename + ") ERROR: String exceeds max length (" + I2S(.AbilityCount * .PreloadLimit) + ").|r")
                debug endif
            
                set lev = 0
                static if (BACKWARDS_COMPATABILITY) then
                    if (c == 0) then
                        set lev = 1
                        set prefix = ""
                    else
                        set prefix = "-"
                    endif
                endif
            
                set chunk = SubString(contents, i, i + .PreloadLimit)
                call Preload("\" )\ncall BlzSetAbilityTooltip(" + I2S(.AbilityList[c]) + ", \"" + prefix + chunk + "\", " + I2S(lev) + ")\n//")
                set i = i + .PreloadLimit
                set c = c + 1
            endloop
            call Preload("\" )\nendfunction\nfunction a takes nothing returns nothing\n //")
            call PreloadGenEnd(this.filename)
    
            return this
        endmethod
    
        method clear takes nothing returns thistype
            return this.write(null)
        endmethod
    
        private method readPreload takes nothing returns string
            local integer i = 0
            local integer lev = 0
            local string array original
            local string chunk = ""
            local string output = ""
        
            loop
                exitwhen i == .AbilityCount
                set original[i] = BlzGetAbilityTooltip(.AbilityList[i], 0)
                set i = i + 1
            endloop
        
            // Execute the preload file
            call Preloader(this.filename)
        
            // Read the output
            set i = 0
            loop
                exitwhen i == .AbilityCount
            
                set lev = 0
            
                // Read from ability index 1 instead of 0 if
                // backwards compatability is enabled
                static if (BACKWARDS_COMPATABILITY) then
                    if (i == 0) then
                        set lev = 1
                    endif
                endif
            
                // Make sure the tooltip has changed
                set chunk = BlzGetAbilityTooltip(.AbilityList[i], lev)
            
                if (chunk == original[i]) then
                    if (i == 0 and output == "") then
                        return null // empty file
                    endif
                    return output
                endif
            
                // Check if the file is an empty string or null
                static if not (BACKWARDS_COMPATABILITY) then
                    if (i == 0) then
                        if (SubString(chunk, 0, 1) != "-") then
                            return null // empty file
                        endif
                        set chunk = SubString(chunk, 1, StringLength(chunk))
                    endif
                endif
            
                // Remove the prefix
                if (i > 0) then
                    set chunk = SubString(chunk, 1, StringLength(chunk))
                endif
            
                // Restore the tooltip and append the chunk
                call BlzSetAbilityTooltip(.AbilityList[i], original[i], lev)
            
                set output = output + chunk
            
                set i = i + 1
            endloop
        
            return output
        endmethod
    
        method close takes nothing returns nothing
            if (this.buffer != null) then
                call .write(.readPreload() + this.buffer)
                set this.buffer = null
            endif
            set .List[this] = .List[0]
            set .List[0] = this
        endmethod
    
        method readEx takes boolean close returns string
            local string output = .readPreload()
            local string buf = this.buffer
    
            if (close) then
                call this.close()
            endif
        
            if (output == null) then
                return buf
            endif
        
            if (buf != null) then
                set output = output + buf
            endif
        
            return output
        endmethod
    
        method read takes nothing returns string
            return .readEx(false)
        endmethod
    
        method readAndClose takes nothing returns string
            return .readEx(true)
        endmethod
    
        method appendBuffer takes string contents returns thistype
            set .buffer = .buffer + contents
            return this
        endmethod
 
        method readBuffer takes nothing returns string
            return .buffer
        endmethod
    
        method writeBuffer takes string contents returns nothing
            set .buffer = contents
        endmethod
    
        static method create takes string filename returns thistype
            return .open(filename).write("")
        endmethod
 
        implement FileInit
    endstruct
 
    private module FileInit
        private static method onInit takes nothing returns nothing
            local string originalTooltip
        
            // We can't use a single ability with multiple levels because
            // tooltips return the first level's value if the value hasn't
            // been set. This way we don't need to edit any object editor data.
            set File.AbilityList[0] = 'Amls'
            set File.AbilityList[1] = 'Aroc'
            set File.AbilityList[2] = 'Amic'
            set File.AbilityList[3] = 'Amil'
            set File.AbilityList[4] = 'Aclf'
            set File.AbilityList[5] = 'Acmg'
            set File.AbilityList[6] = 'Adef'
            set File.AbilityList[7] = 'Adis'
            set File.AbilityList[8] = 'Afbt'
            set File.AbilityList[9] = 'Afbk'
        
            // Backwards compatability check
            static if (BACKWARDS_COMPATABILITY) then
                static if (DEBUG_MODE) then
                    set originalTooltip = BlzGetAbilityTooltip(File.AbilityList[0], 1)
                    call BlzSetAbilityTooltip(File.AbilityList[0], SCOPE_PREFIX, 1)
                    if (BlzGetAbilityTooltip(File.AbilityList[0], 1) == originalTooltip) then
                        call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0, 120, "FileIO WARNING: Backwards compatability enabled but \"" + GetObjectName(File.AbilityList[0]) + "\" isn't setup properly.|r")
                    endif
                endif
            endif
        
            // Read check
            set File.ReadEnabled = File.open("FileTester.pld").write(SCOPE_PREFIX).readAndClose() == SCOPE_PREFIX
        endmethod
    endmodule
 
    public function Write takes string filename, string contents returns nothing
        call File.open(filename).write(contents).close()
    endfunction
 
    public function Read takes string filename returns string
        return File.open(filename).readEx(true)
    endfunction
 
endlibrary

JASS:
library SaveHelperLib initializer Init requires SyncHelper, PlayerUtils, SaveFile

    // Uses GUI variables from the "Save Init" trigger. You can modify these functions to use your own variables.
    private keyword SaveHelperInit
 
    struct SaveHelper extends array
    
        static constant hashtable Hashtable = InitHashtable()
        static constant integer KEY_ITEMS = 1
        static constant integer KEY_UNITS = 2
        static constant integer KEY_NAMES = 3
    
        static method MaxCodeSyncLength takes nothing returns integer
            return udg_SaveLoadMaxLength
        endmethod
    
        static method GetUserHero takes User user returns unit
            return udg_SavePlayerHero[user.id]
        endmethod

        // static method GetSavesPerSlot takes User user, integer slot returns unit
        //     return udg_NumberOfSaves[user.id*12+slot]
        // endmethod

        // static method GetSavesPerSlot takes player p, integer slot returns unit
        //     return udg_NumberOfSaves[GetPlayerId(p)*12+slot]
        // endmethod

        static method GetSavesPerSlot takes player p, integer slot returns integer
            return udg_NumberOfSaves[GetPlayerId(p)*12+slot]
        endmethod

        static method SetSavesPerSlot takes player p, integer slot, integer saves returns nothing
           set udg_NumberOfSaves[GetPlayerId(p)*12+slot] = saves
        endmethod

        static method RemoveUserHero takes User user returns nothing
            call RemoveUnit(udg_SavePlayerHero[user.id])
            set udg_SavePlayerHero[user.id] = null
        endmethod
    
        static method SetUserHero takes User user, unit u returns nothing
            set udg_SavePlayerHero[user.id] = u
        endmethod
    
        static method IsUserLoading takes User user returns boolean
            return udg_SavePlayerLoading[user.id]
        endmethod
    
        static method SetUserLoading takes User user, boolean flag returns nothing
            set udg_SavePlayerLoading[user.id] = flag
        endmethod
    
        static method SetSaveSlot takes User user, integer slot returns nothing
            set udg_SaveCurrentSlot[user.id] = slot
        endmethod
    
        static method GetSaveSlot takes User user returns integer
            return udg_SaveCurrentSlot[user.id]
        endmethod
    
        static method GetUnitJobLevel takes unit u returns integer
            if BlzGetUnitIntegerField(u, UNIT_IF_GOLD_BOUNTY_AWARDED_BASE) == 1 then
                return 1
            elseif BlzGetUnitIntegerField(u, UNIT_IF_GOLD_BOUNTY_AWARDED_BASE) == 2 then
                return 2
            else
                return 3
            endif
        endmethod
    
        static method ClassColor takes unit u returns string
            if GetUnitTypeId(u) == 'H007' or GetUnitTypeId(u) == 'H004' then //Acolyte
                return " |cffffffff" + GetObjectName(GetUnitTypeId(u)) + "|r "
            else
                return GetObjectName(GetUnitTypeId(u))
            endif
        endmethod

        static method GetUnitTitle takes unit u returns string
            return ClassColor(u) + "(Lv." + I2S(GetHeroLevel(u)) + ")"
        endmethod
    
        static method GetMapName takes nothing returns string
            return udg_MapName
        endmethod
    
        static method MaxAbilityLevel takes nothing returns integer
            return 10
        endmethod
    
        static method MaxAbilities takes nothing returns integer
            return udg_SaveAbilityTypeMax
        endmethod
    
        static method MaxItems takes nothing returns integer
            return udg_SaveItemTypeMax
        endmethod
    
        static method MaxUnits takes nothing returns integer
            return udg_SaveUnitTypeMax
        endmethod
    
        static method MaxNames takes nothing returns integer
            return udg_SaveNameMax
        endmethod

        static method MaxHeroStat takes nothing returns integer
            return udg_SaveUnitMaxStat
        endmethod
    
        static method GetAbility takes integer index returns integer
            return udg_SaveAbilityType[index]
        endmethod
    
        static method GetItem takes integer index returns integer
            return udg_SaveItemType[index]
        endmethod
    
        static method GetUnit takes integer index returns integer
            return udg_SaveUnitType[index]
        endmethod
    
        static method ConvertItemId takes integer itemId returns integer
            return LoadInteger(thistype.Hashtable, KEY_ITEMS, itemId)
        endmethod

        static method ConvertUnitId takes integer unitId returns integer
            return LoadInteger(thistype.Hashtable, KEY_UNITS, unitId)
        endmethod
    
        static method GetHeroNameFromID takes integer id returns string
            return udg_SaveNameList[id]
        endmethod
    
        static method GetHeroNameID takes string name returns integer
            return LoadInteger(thistype.Hashtable, KEY_NAMES, StringHash(name))
        endmethod
    
        static method ConvertHeroName takes string name returns string
            return udg_SaveNameList[GetHeroNameID(name)]
        endmethod
    
        static method GUILoadNext takes nothing returns nothing
            set udg_SaveValue[udg_SaveCount] = Savecode(udg_SaveTempInt).Decode(udg_SaveMaxValue[udg_SaveCount])
            //call BJDebugMsg(I2S(udg_SaveCount) + ", " + I2S(udg_SaveValue[udg_SaveCount]))
        endmethod
    
        static method GetLevelXP takes integer level returns real
            local real xp = udg_HeroXPLevelFactor // level 1
            local integer i = 1

            loop
                exitwhen i > level
                set xp = (xp*udg_HeroXPPrevLevelFactor) + (i+1) * udg_HeroXPLevelFactor
                set i = i + 1
            endloop
            return xp-udg_HeroXPLevelFactor
        endmethod
    
        static method Init takes nothing returns nothing // called at the end of "Save Init" trigger
            local integer i = 0
            loop
                exitwhen i >= thistype.MaxItems() //or udg_SaveItemType[i] == 0
            
                call SaveInteger(thistype.Hashtable, KEY_ITEMS, udg_SaveItemType[i], i)
            
                set i = i + 1
            endloop
            set i = 0
            loop
                exitwhen i >= thistype.MaxUnits() //or udg_SaveUnitType[i] == 0
            
                call SaveInteger(thistype.Hashtable, KEY_UNITS, udg_SaveUnitType[i], i)
            
                set i = i + 1
            endloop
            set i = 1
            loop
                exitwhen i >= SaveHelper.MaxNames() or udg_SaveNameList[i] == "" or udg_SaveNameList[i] == null
            
                call SaveInteger(thistype.Hashtable, KEY_NAMES, StringHash(udg_SaveNameList[i]), i)
            
                set i = i + 1
            endloop
        endmethod
    endstruct
 
    function GetHeroSaveCode takes unit u returns string
        if (udg_SaveUseGUI) then
            call TriggerExecute(gg_trg_Save_GUI)
            return udg_SaveTempString
        endif
    
        return ""
    endfunction
 
    private function LoadSaveSlot_OnLoad takes nothing returns nothing
        local player p = GetTriggerPlayer()
        local string prefix = BlzGetTriggerSyncPrefix()
        local string data = BlzGetTriggerSyncData()
        local User user = User[p]
    
        call SaveHelper.SetUserLoading(user, false)
    
        if (udg_SaveUseGUI) then
            set udg_SaveLoadEvent_Code = data
            set udg_SaveLoadEvent_Player = p
            set udg_SaveLoadEvent = 1.
            set udg_SaveLoadEvent = -1
        endif

        set p = null
        set prefix = null
        set data = null
    endfunction
 
    function LoadSaveSlot takes player p, integer slot returns nothing
        local SaveFile savefile = SaveFile(slot)
        local string s
        local User user = User[p]
        local integer saveNumber = SaveHelper.GetSavesPerSlot(p, slot)
        
        if (not SaveFile.exists(slot, saveNumber)) then
            call DisplayTextToPlayer(p, 0, 0, "Did not find any save data.")
            return
        elseif (SaveHelper.IsUserLoading(user)) then
            call DisplayTextToPlayer(p, 0, 0, "Please wait while your character synchronizes.")
        else
            if (saveNumber == 0) then
                set s = savefile.getData()
            else
                set s = savefile.getBackupData(saveNumber)
            endif
            if (GetLocalPlayer() == p) then
                call SyncString(s)
            endif
            call ClearTextMessages()
            call DisplayTimedTextToPlayer(p, 0, 0, 15, "Synchronzing with other players...")
            call SaveHelper.SetSaveSlot(user, slot)
        endif

        set s = null
    endfunction
 
    function DeleteCharSlot takes player p, integer slot returns nothing
        local integer saveNumber = SaveHelper.GetSavesPerSlot(p, slot) + 1
        call SaveHelper.SetSavesPerSlot(p, slot, saveNumber)
        if (GetLocalPlayer() == p) then
            call SaveFile(slot).clear(p, slot, saveNumber)
        endif
    endfunction
 
    function SaveCharToSlot takes unit u, integer slot, string s returns nothing
        local player p = GetOwningPlayer(u)
        local integer saveNumber = SaveHelper.GetSavesPerSlot(p, slot) + 1
        call SaveHelper.SetSavesPerSlot(p, slot, saveNumber)
        if (GetLocalPlayer() == p) then
            call SaveFile(slot).create(p, SaveHelper.GetUnitTitle(u), slot, saveNumber, s)
        endif
        call SaveHelper.SetSaveSlot(User[p], slot)
        set p = null
    endfunction
 
    private function Init takes nothing returns nothing
        call OnSyncString(function LoadSaveSlot_OnLoad)
    endfunction
 
endlibrary

  • Init Load Screen
    • Events
      • Map initialization
    • Conditions
    • Actions
      • Set TestVersion = True
      • -------- Save/Load --------
      • Set SaveLoadMaxLength = 64
      • Set SaveUseGUI = True
      • -------- This willl be the directory the save codes will be saved to. --------
      • Set MapName = Twilight's Eve Nightfall
      • Set SaveShowCode = False
      • Set SaveHeroName = False
      • Set SaveNameMax = 999
      • -------- Set these to the values they are in the Advanced -> Gameplay constants --------
      • Set HeroXPConstant = 0
      • Set HeroXPLevelFactor = 100
      • Set HeroXPPrevLevelFactor = 1
      • Set HeroXPRequired = 200
      • -------- Store unit types that can be saved here --------
      • Set SaveUnitType[0] = No unit-type
      • Set SaveUnitType[1] = Personal Stash
      • Set SaveUnitType[2] = Acolyte
      • Set SaveUnitType[3] = Priest
      • Set SaveUnitType[4] = Hierophant
      • Set SaveUnitType[5] = Acolyte (Female)
      • Set SaveUnitType[6] = Matriarch (Female)
      • Set SaveUnitType[7] = Prophetess
      • Set SaveUnitType[8] = Druid
      • Set SaveUnitType[9] = Shapeshifter
      • Set SaveUnitType[10] = Werebear (Shapeshifter)
      • Set SaveUnitType[11] = Werewolf (Shapeshifter)
      • Set SaveUnitType[12] = RuneMaster
      • Set SaveUnitType[13] = Shaman
      • Set SaveUnitType[14] = Summoner
      • Set SaveUnitType[15] = Initiate
      • Set SaveUnitType[16] = Wizard
      • Set SaveUnitType[17] = White Wizard
      • Set SaveUnitType[18] = Mage
      • Set SaveUnitType[19] = ArchSage
      • Set SaveUnitType[20] = Templar
      • Set SaveUnitType[21] = High Templar
      • Set SaveUnitType[22] = Grand Templar
      • Set SaveUnitType[23] = Dark Templar
      • Set SaveUnitType[24] = Dark ArchTemplar
      • Set SaveUnitType[25] = Archer
      • Set SaveUnitType[26] = Marksman
      • Set SaveUnitType[27] = Sniper
      • Set SaveUnitType[28] = Tracker
      • Set SaveUnitType[29] = Monster Hunter
      • Set SaveUnitType[30] = Thief
      • Set SaveUnitType[31] = Stalker
      • Set SaveUnitType[32] = Master Stalker
      • Set SaveUnitType[33] = Assassin
      • Set SaveUnitType[34] = Phantom Assassin
      • Set SaveUnitType[35] = Swordsman
      • Set SaveUnitType[36] = Crusader
      • Set SaveUnitType[37] = Avenger
      • Set SaveUnitType[38] = Imperial Knight
      • Set SaveUnitType[39] = Champion
      • Set SaveUnitType[40] = Witch Hunter
      • Set SaveUnitType[41] = Witcher
      • Set SaveUnitType[42] = Professional Witcher
      • Set SaveUnitType[43] = Inquisitor
      • Set SaveUnitType[44] = Grand Inquisitor
      • Set SaveUnitTypeMax = 99
      • -------- Store item types that can be saved here --------
      • Set SaveItemType[0] = (Item-type of No item)
      • Custom script: set udg_SaveItemType[1] = 'I02F'
      • Custom script: set udg_SaveItemType[2] = 'I029'
      • Custom script: set udg_SaveItemType[3] = 'I00G'
      • Custom script: set udg_SaveItemType[4] = 'I02H'
      • Custom script: set udg_SaveItemType[5] = 'I02C'
      • Custom script: set udg_SaveItemType[6] = 'I02G'
      • Custom script: set udg_SaveItemType[7] = 'I00H'
      • Custom script: set udg_SaveItemType[8] = 'I003'
      • Custom script: set udg_SaveItemType[9] = 'I00J'
      • Custom script: set udg_SaveItemType[10] = 'I00I'
      • Custom script: set udg_SaveItemType[11] = 'I001'
      • Custom script: set udg_SaveItemType[12] = 'I02D'
      • Custom script: set udg_SaveItemType[13] = 'I02A'
      • Custom script: set udg_SaveItemType[14] = 'I002'
      • Custom script: set udg_SaveItemType[15] = 'I02B'
      • Custom script: set udg_SaveItemType[16] = 'I02E'
      • Set SaveItemTypeMax = 999
      • -------- Store ability types that can be saved here --------
      • Custom script: set udg_SaveAbilityType[1] = 'A007'
      • Custom script: set udg_SaveAbilityType[2] = 'A006'
      • Custom script: set udg_SaveAbilityType[3] = 'A008'
      • Custom script: set udg_SaveAbilityType[4] = 'A009'
      • Set SaveAbilityTypeMax = 199
      • -------- --------
      • Custom script: call SaveHelper.Init()
      • Set Skypper = |cffffcc00Basic Questgiver Skypper|r
      • Set Arnold = |cffffcc00Arnold the Treasure Hunter|r
      • Set Squeaky = |cffffcc00Squeaky the Exotic Pandaren|r
      • Set Kiba = |cffffcc00Civil Agent Kiba|r
      • Set Kain = |cffffcc00Kain the Material Collector|r
      • For each (Integer A) from 1 to 10, do (Actions)
        • Loop - Actions
          • Set HeroRevivalPoint[(Integer A)] = (Center of Rez Point Snow <gen>)
      • Set GeneratorRegion = ItemGenerator <gen>
      • Set PlayerItemSpawn[1] = (Center of Red Item <gen>)
      • Set PlayerItemSpawn[2] = (Center of Blue Item <gen>)
      • Set PlayerItemSpawn[3] = (Center of Teal Item <gen>)
      • Set PlayerItemSpawn[4] = (Center of Purple Item <gen>)
      • Set PlayerItemSpawn[5] = (Center of Yellow Item <gen>)
      • Set PlayerItemSpawn[6] = (Center of Orange Item <gen>)
      • Set PlayerItemSpawn[7] = (Center of Green Item <gen>)
      • Set PlayerItemSpawn[8] = (Center of Pink Item <gen>)
      • Set PlayerItemSpawn[9] = (Center of Gray Item <gen>)
      • Set PlayerItemSpawn[10] = (Center of Light Blue Item <gen>)
      • -------- Locations to -enter --------
      • Set DungeonEnterLoc[1] = IceDungeon <gen>
      • Set DungeonEnterLoc[2] = CryptDungeonEntrance <gen>
      • Set DungeonEnterLoc[3] = GoldMineDungeon <gen>
      • Set DungeonEnterLoc[4] = MountainDungeonEntrance <gen>
      • Set DungeonEnterLoc[5] = PuzzleDungeonEntrance <gen>
      • Set DungeonEnterLoc[6] = NagaEntrance <gen>
      • Set DungeonEnterLoc[8] = HellEntrance <gen>
      • Set DungeonEnterLoc[9] = SewersEntrance <gen>
      • Set DungeonEnterLoc[10] = AbyssalEntrance <gen>
      • Set DungeonEnterLoc[11] = IllusionCityEntrance <gen>
      • Set DungeonEnterLoc[12] = DragonDungeonEntrance <gen>
      • -------- Spawn locations --------
      • Set DungeonSpawnLoc[1] = IceDungeonStart <gen>
      • Set DungeonSpawnLoc[2] = CryptEntranceSpawn <gen>
      • Set DungeonSpawnLoc[3] = GoldMineDungeonSpawn <gen>
      • Set DungeonSpawnLoc[4] = MountainDungeonEntranceSpawn <gen>
      • Set DungeonSpawnLoc[5] = PuzzleDungeonEntranceSpawn <gen>
      • Set DungeonSpawnLoc[6] = NagaEntranceSpawn <gen>
      • Set DungeonSpawnLoc[7] = TristramEntranceSpawn <gen>
      • Set DungeonSpawnLoc[8] = HellEntranceSpawn <gen>
      • Set DungeonSpawnLoc[9] = SewerSpawn <gen>
      • Set DungeonSpawnLoc[11] = IllusionCitySpawn <gen>
      • -------- Dungeon Regions --------
      • Set DungeonRegion[1] = IcyDungeonArea <gen>
      • Set DungeonRegion[2] = CryptDungeonArea <gen>
      • Set DungeonRegion[3] = GoldMineDungeonArea <gen>
      • Set DungeonRegion[4] = CentaurDungeon Area <gen>
      • -------- Dungeon Portals --------
      • Set DungeonPortalUnit[1] = Portal (Dungeon) 1109 <gen>
      • Set DungeonPortalUnit[2] = Portal (Dungeon) 1134 <gen>
      • Set DungeonPortalUnit[3] = Portal (Dungeon) 0390 <gen>
      • -------- Groups --------
      • Custom script: set udg_UnitCheck = CreateGroup()
      • Custom script: set udg_UnitPlayerCheck = CreateGroup()
      • Custom script: set udg_CombatGroup = CreateForce()
      • Custom script: set udg_TauntGroup = CreateGroup()
      • Custom script: set udg_hell1FixateGroup = CreateGroup()
      • -------- Player Colors --------
      • Set PlayerC[1] = |cffff0303
      • Set PlayerC[2] = |cff0042ff
      • Set PlayerC[3] = |cff1be7ba
      • Set PlayerC[4] = |cff550081
      • Set PlayerC[5] = |cfffefc00
      • Set PlayerC[6] = |cfffe890d
      • Set PlayerC[7] = |cff21bf00
      • Set PlayerC[8] = |cffe45caf
      • Set PlayerC[9] = |cff939596
      • Set PlayerC[10] = |cff7ebff1
      • Set PlayerC[11] = |cff106247
      • Set PlayerC[12] = |cff4f2b05
      • Set PlayerC[13] = |cff9c0000
      • Set PlayerC[14] = |cff0000c3
      • Set PlayerC[15] = |cff00ebff
      • Set PlayerC[16] = |cffbd00ff
      • Set PlayerC[17] = |cffecce87
      • Set PlayerC[18] = |cfff7a58b
      • Set PlayerC[19] = |cffbfff81
      • Set PlayerC[20] = |cffdbb8eb
      • Set PlayerC[21] = |cff4f5055
      • Set PlayerC[22] = |cffecf0ff
      • Set PlayerC[23] = |cff00781e
      • Set PlayerC[24] = |cffa56f34
      • Set PlayerC[25] = |cff2e2d2e
      • Set PlayerC[26] = |cff2e2d2e
      • Set PlayerC[27] = |cff2e2d2e
      • Set PlayerC[28] = |cff2e2d2e
      • -------- Player Stash --------
      • Set PlayerStashRegion[1] = PlayerStash1 <gen>
      • Set PlayerStashRegion[2] = PlayerStash2 <gen>
      • Set PlayerStashRegion[3] = PlayerStash3 <gen>
      • Set PlayerStashRegion[4] = PlayerStash4 <gen>
      • Set PlayerStashRegion[5] = PlayerStash5 <gen>
      • Set PlayerStashRegion[6] = PlayerStash6 <gen>
      • Set PlayerStashRegion[7] = PlayerStash7 <gen>
      • Set PlayerStashRegion[8] = PlayerStash8 <gen>
      • Set PlayerStashRegion[9] = PlayerStash9 <gen>
      • Set PlayerStashRegion[10] = PlayerStash10 <gen>
      • -------- Knockup System Hash --------
      • Hashtable - Create a hashtable
      • Set KUS_hash = (Last created hashtable)
      • -------- Knockback System Setup --------
      • Set FK_IntervalPerMove = 0.03
      • Trigger - Add to FK Loop <gen> the event (Time - Every FK_IntervalPerMove seconds of game time)
      • Custom script: call DestroyGroup(udg_InitTempGroup)
      • Custom script: set udg_InitTempGroup = null
      • -------- --------

(As a side note the desyncs were still happening even before I was using the LocalPlayer to disable user control)
  • Init 0
    • Events
      • Time - Elapsed game time is 0.00 seconds
    • Conditions
    • Actions
      • Custom script: local player p = GetLocalPlayer()
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • TestVersion Equal to True
        • Then - Actions
          • Set SaveLoad_MapVersion = 0.00
          • Visibility - Disable black mask
          • Visibility - Disable fog of war
          • Trigger - Turn on Test Leveling <gen>
          • Trigger - Turn on Test Gold <gen>
          • Trigger - Turn on Test Create Class <gen>
        • Else - Actions
          • Set SaveLoad_MapVersion = 0.01
          • Visibility - Disable black mask
          • Visibility - Disable fog of war
          • Visibility - Enable black mask
          • Visibility - Enable fog of war
      • Game - Hide creep camps on the minimap.
      • Custom script: if p != null then
      • Custom script: call EnableUserControl(false)
      • Custom script: endif

EDIT: Additionally I don't know if that matters but I use the Wc3 vex map optimizer to protect the map using the settings as shown in the pictures. I also import the latest common.j and blizzard.j

EDIT2: In a 9-man lobby that I tried earlier today it appears 2 got desynced so...
 

Attachments

  • Settings1.png
    Settings1.png
    22.2 KB · Views: 3
  • Settings Tweaks.png
    Settings Tweaks.png
    13.5 KB · Views: 3
Last edited:
Level 3
Joined
Nov 9, 2021
Messages
8
I recommend W3Protect - Warcraft 3 Map Protection

For optimizer, it is best to avoid settings related to triggers since some of the newer natives might mess up the optimizer.
Hey thank you for the recommendation I've already tried W3Protect but it messes up a lot of the custom trigger values, for example I use chopinski's item bonus system and whenever I protect using W3Protect the numbers just break and instead of giving a unit the specific bonus I set it to it gives +232732846237642, not sure why that is but yeah.

Also for the optimizer I think that's why we import common.j and blizzard .j so it recognizes the newer natives.
 
Do you know if this only happens when a user has saved data on disk? And in your map, do you automatically try to load the data at some point--or is it through a chat command/dialog? (e.g. -load) How did you know it was the save/load system in particular that causes the desync?

It is a little tedious, but you may want to try getting your computer set-up to run multiple clients so you can debug it further so you can debug it further. I'm not sure if this method still works:

Sadly, it is hard for us to know which code-paths are being followed with save/load systems since they are so map-specific. In my head, my main thought would be to try to defer the loading of the system until some point after map initialization to see if that helps (e.g. in case the BlzSendSyncData natives don't work reliably until the game starts, so try doing it after 1 second elapses), but I'm not super familiar with the save/load system so I'm not sure if it supports initializing it late. I don't see any obvious smoking gun though.
 
Level 21
Joined
Mar 16, 2008
Messages
952
You can run multiple clients on LAN through the BNET app, there's not much to it. Launch the game, minimize, launch the game again, minimize and open the first instance, select offline mode, done.
When I've been trying this recently, it only let's logged-in accounts join LAN lobbies. Is that just me doing something wrong? Or is Bliz requiring license to play LAN now?
 
Level 3
Joined
Nov 9, 2021
Messages
8
You can run multiple clients on LAN through the BNET app, there's not much to it. Launch the game, minimize, launch the game again, minimize and open the first instance, select offline mode, done.
I've already tried doing that but as Gnuoy said you can no longer run infinite lan clients, just ones that are logged in.
Do you know if this only happens when a user has saved data on disk? And in your map, do you automatically try to load the data at some point--or is it through a chat command/dialog? (e.g. -load) How did you know it was the save/load system in particular that causes the desync?

It is a little tedious, but you may want to try getting your computer set-up to run multiple clients so you can debug it further so you can debug it further. I'm not sure if this method still works:

Sadly, it is hard for us to know which code-paths are being followed with save/load systems since they are so map-specific. In my head, my main thought would be to try to defer the loading of the system until some point after map initialization to see if that helps (e.g. in case the BlzSendSyncData natives don't work reliably until the game starts, so try doing it after 1 second elapses), but I'm not super familiar with the save/load system so I'm not sure if it supports initializing it late. I don't see any obvious smoking gun though.
I've already tried moving the savehelper.init to after 2 seconds elapsed and it seemed to be desyncing again, as far as loading and saving goes it's always manual and the desync is just random and all over the place... As I said above it desynced 2 people out of 9 in a game I tested with and the other time it desynced 1 person in a 5 man lobby. so I at this point I am just searching for ways desyncs are caused. I've also seen that gameplay constants can cause desyncs such as guard distance and guard return distance etc I've reset them to defaults and desyncs still happen...

Does the issue only exist on protected version? Try Uncle method or arrange a 3 person party for testing on unprotected version and see if it is persistent
I am gonna try this also I thought about doing it next.

EDIT: Also I don't know if testing in LAN will do anything since it's all the same local player essentially. I tried with 6 clients multiple times and no desyncs seem to happen but in b.net it sometimes kicks people even as 4 so I am very confused only thing that's pretty much left to try is get people to try unprotected version.
 
Last edited:

Uncle

Warcraft Moderator
Level 73
Joined
Aug 10, 2018
Messages
7,860
I've already tried doing that but as Gnuoy said you can no longer run infinite lan clients, just ones that are logged in.

I've already tried moving the savehelper.init to after 2 seconds elapsed and it seemed to be desyncing again, as far as loading and saving goes it's always manual and the desync is just random and all over the place... As I said above it desynced 2 people out of 9 in a game I tested with and the other time it desynced 1 person in a 5 man lobby. so I at this point I am just searching for ways desyncs are caused. I've also seen that gameplay constants can cause desyncs such as guard distance and guard return distance etc I've reset them to defaults and desyncs still happen...


I am gonna try this also I thought about doing it next.

EDIT: Also I don't know if testing in LAN will do anything since it's all the same local player essentially. I tried with 6 clients multiple times and no desyncs seem to happen but in b.net it sometimes kicks people even as 4 so I am very confused only thing that's pretty much left to try is get people to try unprotected version.
I have three clients open right now, works as intended. I did have to click "Play Offline" twice, though.

Edit: Actually, it seems like the 3rd client can't join the game. That's annoying, but 2 players should really be able to recreate any desync assuming that your map is designed in a generic way.

LAN is over the network, meaning any code that causes something (unsafe) to change on one client but not others will cause a desync. The games are being synced to one another the same exact way it works online with other players. However, it's possible that bad ping is part of the issue, in which case I recommend installing a program called Clumsy that lets you simulate lag (it's perfectly safe and only affects your computer - no one else on your network will experience lag).
 
Last edited:
I've already tried moving the savehelper.init to after 2 seconds elapsed and it seemed to be desyncing again, as far as loading and saving goes it's always manual and the desync is just random and all over the place... As I said above it desynced 2 people out of 9 in a game I tested with and the other time it desynced 1 person in a 5 man lobby. so I at this point I am just searching for ways desyncs are caused. I've also seen that gameplay constants can cause desyncs such as guard distance and guard return distance etc I've reset them to defaults and desyncs still happen...
For that map, do you know if the desync happened after 2 seconds, or did it happen immediately? You said it happens randomly, so do the desyncs sometimes happen after the game has started? And are you sure it is a desync that is happening, or is it a crash? (desync => player will get disconnected and typically go back to the lobby, crash => game will completely close) It might seem like the same thing from the host's perspective (e.g. it will show that the player has left), so you might need to ask what happened for the users who disconnected.

But if you are looking for a list of things to rule out, I recommend checking this thread and try to see if any of your code qualifies:

As Uncle mentioned, LAN should be fine for testing out desyncs caused by the map's code, as those cases are enforced by Warcraft III's engine (rather than being dependent on your network connectivity/BNet). The most common cases involve using GetLocalPlayer() to create objects for one player but not the others, but there are quite a few other cases listed in that thread (particularly after reforged). So it is quite hard to come up with a comprehensive list. BUT it is possible that you wouldn't be able to reproduce it locally for other reasons (e.g. some cases in that thread^ mention caching issues, e.g. if they played a different map before loading yours).

I would first give that thread a look and see if there are any things that seem suspicious for your map. But otherwise, if you aren't able to reproduce it locally, I'd recommend trying to find a few friends (or hostages) to test a few variants of your map:
  1. First make sure at least one person can desync with the normal version of your map (this is critical--otherwise you'll likely get false negatives)
  2. Then try a variant where all optimizations/protection/third-party-tools are removed (as Daffa suggested)
  3. Then try a variant where ALL triggers are removed--see if you can get the desync to occur. If disconnects do not happen anymore with triggers removed, then you can be fairly confident your map's code is causing the desync (and you can rule out imports, gameplay constants, etc. being the cause)--and I would try some variants with certain code removed:
    1. Try one with the save/load system completely removed--see if you can get the desync to occur
    2. Try one with all initialization code removed--see if you can get the desync to occur
    3. etc.
  4. Then try a variant where ALL imports are removed (the map will likely have a bunch of invisible stuff, but it should at least load)--see if you can get the desync to occur
  5. Then try a variant where ALL custom objects are removed--see if you can get the desync to occur


You might've already did the steps above^ to narrow it down to the Codeless Save/Load system. If so, awesome! I'd next try to disable any of the side-effects that occur on initialization. This may break the system, but it might help you narrow down whether it is the system's initialization that is causing the issue vs. how you interact with the system, e.g.

In SyncHelper, temporarily disable the following functions like so:
JASS:
    function SyncString takes string s returns boolean
        // return BlzSendSyncData(SYNC_PREFIX, s)
        return false
    endfunction

    // ...

    private struct Sync extends array
        static trigger Trigger = CreateTrigger()
        // implement INITS
    endstruct

This will disable the sync logic and prevent registering the sync events.

Next, in FileIO, disable the following code in "onInit" by commenting it out like so:
JASS:
// Read check
// set File.ReadEnabled = File.open("FileTester.pld").write(SCOPE_PREFIX).readAndClose() == SCOPE_PREFIX

If you're still experiencing desyncs (and you're confident it is the Save/Load system that is the culprit), then that may point to an issue with how you're using the system. So I'd then try disabling all your custom triggers related to save/load logic.

If you aren't experiencing desyncs after the changes above, then it means that the Save/Load system initialization is causing some issues with players in your map and we can debug further.
 
Level 3
Joined
Nov 9, 2021
Messages
8
For that map, do you know if the desync happened after 2 seconds, or did it happen immediately? You said it happens randomly, so do the desyncs sometimes happen after the game has started? And are you sure it is a desync that is happening, or is it a crash? (desync => player will get disconnected and typically go back to the lobby, crash => game will completely close) It might seem like the same thing from the host's perspective (e.g. it will show that the player has left), so you might need to ask what happened for the users who disconnected.

But if you are looking for a list of things to rule out, I recommend checking this thread and try to see if any of your code qualifies:

As Uncle mentioned, LAN should be fine for testing out desyncs caused by the map's code, as those cases are enforced by Warcraft III's engine (rather than being dependent on your network connectivity/BNet). The most common cases involve using GetLocalPlayer() to create objects for one player but not the others, but there are quite a few other cases listed in that thread (particularly after reforged). So it is quite hard to come up with a comprehensive list. BUT it is possible that you wouldn't be able to reproduce it locally for other reasons (e.g. some cases in that thread^ mention caching issues, e.g. if they played a different map before loading yours).

I would first give that thread a look and see if there are any things that seem suspicious for your map. But otherwise, if you aren't able to reproduce it locally, I'd recommend trying to find a few friends (or hostages) to test a few variants of your map:
  1. First make sure at least one person can desync with the normal version of your map (this is critical--otherwise you'll likely get false negatives)
  2. Then try a variant where all optimizations/protection/third-party-tools are removed (as Daffa suggested)
  3. Then try a variant where ALL triggers are removed--see if you can get the desync to occur. If disconnects do not happen anymore with triggers removed, then you can be fairly confident your map's code is causing the desync (and you can rule out imports, gameplay constants, etc. being the cause)--and I would try some variants with certain code removed:
    1. Try one with the save/load system completely removed--see if you can get the desync to occur
    2. Try one with all initialization code removed--see if you can get the desync to occur
    3. etc.
  4. Then try a variant where ALL imports are removed (the map will likely have a bunch of invisible stuff, but it should at least load)--see if you can get the desync to occur
  5. Then try a variant where ALL custom objects are removed--see if you can get the desync to occur


You might've already did the steps above^ to narrow it down to the Codeless Save/Load system. If so, awesome! I'd next try to disable any of the side-effects that occur on initialization. This may break the system, but it might help you narrow down whether it is the system's initialization that is causing the issue vs. how you interact with the system, e.g.

In SyncHelper, temporarily disable the following functions like so:
JASS:
    function SyncString takes string s returns boolean
        // return BlzSendSyncData(SYNC_PREFIX, s)
        return false
    endfunction

    // ...

    private struct Sync extends array
        static trigger Trigger = CreateTrigger()
        // implement INITS
    endstruct

This will disable the sync logic and prevent registering the sync events.

Next, in FileIO, disable the following code in "onInit" by commenting it out like so:
JASS:
// Read check
// set File.ReadEnabled = File.open("FileTester.pld").write(SCOPE_PREFIX).readAndClose() == SCOPE_PREFIX

If you're still experiencing desyncs (and you're confident it is the Save/Load system that is the culprit), then that may point to an issue with how you're using the system. So I'd then try disabling all your custom triggers related to save/load logic.

If you aren't experiencing desyncs after the changes above, then it means that the Save/Load system initialization is causing some issues with players in your map and we can debug further.

First of all thank you very much for taking the time for such in depth reply!
-by the "random" desyncs I mean that it was usually with random amount of people but never will <= 3 people, so with 3 people I never got desyncs anything above and that's when desyncs start to happen, recently I've been testing with 5 people and desyncs do not seem to occur, need some more time to test out stuff.

-I'll try commenting what you suggested also and see if that fixes things!

-I for sure know the desyncs happened before the 2 seconds mark cuz in 2 seconds I create a multiboard for all players that are still in game, and the player who desynced naturally is not in the multiboard list.

-I've also checked the thread you linked but I will go through the list again in case I missed something, I'll keep this thread updated in case it's solved or the problem persists.
 
Last edited:
Top