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

Bnet Game Lag / Latency Makes Loop Miss Players

Level 18
Joined
Mar 16, 2008
Messages
721
When a player is lagging it seems to "skip" that player and not load the save of the lagging player. Does that make sense? Similar to how Waits don't work on Bnet Not sure if it's this trigger or something deeper in the load / save system. My conclusion is some functions won't work if a player briefly disconnects and some will work not matter what.

Does anyone know if there is a chance this function, or some other part of the save load system or this trigger specifically, could be one of these functions that could malfunction when in an online/multiplayer/player briefly disconnects/lags situation?

  • Custom Auto Load Repeat
    • Events
      • Time - load_save_file_timer expires
    • Conditions
    • Actions
      • ...
  • Custom Load Event
    • Events
      • Game - SaveLoadEvent becomes Equal to 1.00
    • Conditions
    • Actions
      • ...
 
Last edited:
Level 18
Joined
Mar 16, 2008
Messages
721
The Trigger does go though the loop 9/10 times, seems to bug out if any player is lagging though.

  • Custom Auto Load Start
    • Events
      • Time - Elapsed game time is 1.50 seconds
    • Conditions
    • Actions
      • Player Group - Pick every player in players_grp and do (Actions)
        • Loop - Actions
          • Player Group - Add (Picked player) to load_save_file_group
      • Countdown Timer - Start load_save_file_timer as a Repeating timer that will expire in 0.40 seconds
  • Custom Auto Load Repeat
    • Events
      • Time - load_save_file_timer expires
    • Conditions
    • Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Number of players in load_save_file_group) Greater than 0
        • Then - Actions
          • Set VariableSet SaveLoadEvent_Player = (Random player from load_save_file_group)
          • Set VariableSet SaveLoadEvent_PN = (Player number of SaveLoadEvent_Player)
          • Player Group - Remove SaveLoadEvent_Player from load_save_file_group.
          • Custom script: call LoadSaveSlot(udg_SaveLoadEvent_Player, 1)
        • Else - Actions
          • -------- All players have loaded: --------
          • Custom script: call DestroyForce( udg_load_save_file_group )
          • Countdown Timer - Pause (Expiring timer)
          • Custom script: call DestroyTimer( GetExpiredTimer() )
  • Save Init
    • Events
      • Map initialization
    • Conditions
    • Actions
      • -------- This willl be the directory the save codes will be saved to. Set it to your Map Name. --------
      • Set VariableSet MapName = defenseoffelwood_prestige_final
      • -------- ------------------- --------
      • -------- This message will display to players who don't have local files enabled --------
      • Set VariableSet LocalFiles_WarningMessage = |cffe53b3bYou need to enable local files to load your character from disk on patches prior to 1.30!
      • Custom script: set udg_LocalFiles_WarningMessage = udg_LocalFiles_WarningMessage + "\n\n"
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 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 GetMapName takes nothing returns string
            return udg_MapName
        endmethod
       
        static method GUILoadNext takes nothing returns nothing
            set udg_SaveValue[udg_SaveCount] = Savecode(udg_SaveTempInt).Decode(udg_SaveMaxValue[udg_SaveCount])
        endmethod

    endstruct
   
    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)
       
        set udg_SaveLoadEvent_Code = data
        set udg_SaveLoadEvent_Player = p
        set udg_SaveLoadEvent = 1.
        set udg_SaveLoadEvent = -1
       
    endfunction
   
    function LoadSaveSlot takes player p, integer slot returns nothing
        local SaveFile savefile = SaveFile(slot)
        local string s
        local User user = User[p]

        if (SaveHelper.IsUserLoading(user)) then
            call DisplayTextToPlayer(p, 0, 0, "Please wait while your character synchronizes.")
        else
            set s = savefile.getData()
            if s != null then
                if (GetLocalPlayer() == p) then
                    call SyncString(s)
                endif
                call ClearTextMessages()
                call DisplayTimedTextToPlayer(p, 0, 0, 15, "Synchronizing with other players...")
                call SaveHelper.SetSaveSlot(user, slot)
                else
                    call DisplayTimedTextToPlayer(p, 0, 0, 10, "The |cff00ff00Save|r file was not found.")
                endif
            endif

    endfunction

    private function Init takes nothing returns nothing
        call OnSyncString(function LoadSaveSlot_OnLoad)
    endfunction
   
endlibrary
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 create takes player p, string title, integer slot, string data returns thistype
            if (GetLocalPlayer() == p) then
                call FileIO_Write(.getPath(slot), title + "\n" + data)
            endif
            return slot
        endmethod
       
        static method clear takes player p, integer slot returns thistype
            if (GetLocalPlayer() == p) then
                call FileIO_Write(.getPath(slot), "")
            endif
            return slot
        endmethod
       
        static method exists takes integer slot returns boolean // async
            return FileIO_Read(.getPath(slot)) != null
        endmethod
       
        method getLines takes integer line, boolean includePrevious 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
           
            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)
        endmethod
       
        method getTitle takes nothing returns string // async
            return .getLines(0, false)
        endmethod
       
        method getData takes nothing returns string // async
            return .getLines(1, false)
        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 = true
    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
 
        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 PlayerUtils
/**************************************************************
*
*   v1.2.9 by TriggerHappy
*
*   This library provides a struct which caches data about players
*   as well as provides functionality for manipulating player colors.
*
*   Constants
*   ------------------
*
*       force FORCE_PLAYING - Player group of everyone who is playing.
*
*   Struct API
*   -------------------
*     struct User
*
*       static method fromIndex takes integer i returns User
*       static method fromLocal takes nothing returns User
*       static method fromPlaying takes integer id returns User
*
*       static method operator []    takes integer id returns User
*       static method operator count takes nothing returns integer
*
*       method operator name         takes nothing returns string
*       method operator name=        takes string name returns nothing
*       method operator color        takes nothing returns playercolor
*       method operator color=       takes playercolor c returns nothing
*       method operator defaultColor takes nothing returns playercolor
*       method operator hex          takes nothing returns string
*       method operator nameColored  takes nothing returns string
*
*       method toPlayer takes nothing returns player
*       method colorUnits takes playercolor c returns nothing
*
*       readonly string originalName
*       readonly boolean isPlaying
*       readonly static player Local
*       readonly static integer LocalId
*       readonly static integer AmountPlaying
*       readonly static playercolor array Color
*       readonly static player array PlayingPlayer
*
**************************************************************/

    globals
        // automatically change unit colors when changing player color
        private constant boolean AUTO_COLOR_UNITS = true
  
        // use an array for name / color lookups (instead of function calls)
        private constant boolean ARRAY_LOOKUP     = false
  
        // this only applies if ARRAY_LOOKUP is true
        private constant boolean HOOK_SAFETY      = false // disable for speed, but only use the struct to change name/color safely
  
        constant force FORCE_PLAYING = CreateForce()
  
        private string array Name
        private string array Hex
        private string array OriginalHex
        private playercolor array CurrentColor
    endglobals

    private keyword PlayerUtilsInit

    struct User extends array
    
        static constant integer NULL = bj_MAX_PLAYER_SLOTS
      
        readonly player handle
        readonly integer id
        readonly thistype next
        readonly thistype prev

        readonly string originalName
        readonly boolean isPlaying
  
        readonly static thistype first
        readonly static thistype last
        readonly static player Local
        readonly static integer LocalId
        readonly static integer AmountPlaying = 0
        readonly static playercolor array Color

        static if not (LIBRARY_GroupUtils) then
            readonly static group ENUM_GROUP = CreateGroup()
        endif

        private static thistype array PlayingPlayer
        private static integer array PlayingPlayerIndex
  
        // similar to Player(#)
        static method fromIndex takes integer i returns thistype
            return thistype(i)
        endmethod
  
        // similar to GetLocalPlayer
        static method fromLocal takes nothing returns thistype
            return thistype(thistype.LocalId)
        endmethod
  
        // access active players array
        static method fromPlaying takes integer index returns thistype
            return PlayingPlayer[index]
        endmethod
  
        static method operator [] takes player p returns thistype
            return thistype(GetPlayerId(p))
        endmethod
  
        method toPlayer takes nothing returns player
            return this.handle
        endmethod
    
        method operator name takes nothing returns string
            static if (ARRAY_LOOKUP) then
                return Name[this]
            else
                return GetPlayerName(this.handle)
            endif
        endmethod
  
        method operator name= takes string newName returns nothing
            call SetPlayerName(this.handle, newName)
            static if (ARRAY_LOOKUP) then
                static if not (HOOK_SAFETY) then
                    set Name[this] = newName
                endif
            endif
        endmethod
  
        method operator color takes nothing returns playercolor
            static if (ARRAY_LOOKUP) then
                return CurrentColor[this]
            else
                return GetPlayerColor(this.handle)
            endif
        endmethod
  
        method operator hex takes nothing returns string
            return OriginalHex[GetHandleId(this.color)]
        endmethod
  
        method operator color= takes playercolor c returns nothing
            call SetPlayerColor(this.handle, c)
      
            static if (ARRAY_LOOKUP) then
                set CurrentColor[this] = c
                static if not (HOOK_SAFETY) then
                    static if (AUTO_COLOR_UNITS) then
                        call this.colorUnits(color)
                    endif
                endif
            endif
        endmethod
  
        method operator defaultColor takes nothing returns playercolor
            return Color[this]
        endmethod
  
        method operator nameColored takes nothing returns string
            return hex + this.name + "|r"
        endmethod
  
        method colorUnits takes playercolor c returns nothing
            local unit u
      
            call GroupEnumUnitsOfPlayer(ENUM_GROUP, this.handle, null)
      
            loop
                set u = FirstOfGroup(ENUM_GROUP)
                exitwhen u == null
                call SetUnitColor(u, c)
                call GroupRemoveUnit(ENUM_GROUP, u)
            endloop
        endmethod
  
        static method onLeave takes nothing returns boolean
            local thistype p  = thistype[GetTriggerPlayer()]
            local integer i   = .PlayingPlayerIndex[p.id]
      
            // clean up
            call ForceRemovePlayer(FORCE_PLAYING, p.toPlayer())
      
            // recycle index
            set .AmountPlaying = .AmountPlaying - 1
            set .PlayingPlayerIndex[i] = .PlayingPlayerIndex[.AmountPlaying]
            set .PlayingPlayer[i] = .PlayingPlayer[.AmountPlaying]
          
            if (.AmountPlaying == 1) then
                set p.prev.next = User.NULL
                set p.next.prev = User.NULL
            else
                set p.prev.next = p.next
                set p.next.prev = p.prev
            endif

            set .last = .PlayingPlayer[.AmountPlaying]
          
            set p.isPlaying = false
      
            return false
        endmethod
  
        implement PlayerUtilsInit
  
    endstruct

    private module PlayerUtilsInit
        private static method onInit takes nothing returns nothing
            local trigger t = CreateTrigger()
            local integer i = 0
            local thistype p
      
            set thistype.Local   = GetLocalPlayer()
            set thistype.LocalId = GetPlayerId(thistype.Local)
      
            set OriginalHex[0]  = "|cffff0303"
            set OriginalHex[1]  = "|cff0042ff"
            set OriginalHex[2]  = "|cff1ce6b9"
            set OriginalHex[3]  = "|cff540081"
            set OriginalHex[4]  = "|cfffffc01"
            set OriginalHex[5]  = "|cfffe8a0e"
            set OriginalHex[6]  = "|cff20c000"
            set OriginalHex[7]  = "|cffe55bb0"
            set OriginalHex[8]  = "|cff959697"
            set OriginalHex[9]  = "|cff7ebff1"
            set OriginalHex[10] = "|cff106246"
            set OriginalHex[11] = "|cff4e2a04"
           
            if (bj_MAX_PLAYERS > 12) then
                set OriginalHex[12] = "|cff9B0000"
                set OriginalHex[13] = "|cff0000C3"
                set OriginalHex[14] = "|cff00EAFF"
                set OriginalHex[15] = "|cffBE00FE"
                set OriginalHex[16] = "|cffEBCD87"
                set OriginalHex[17] = "|cffF8A48B"
                set OriginalHex[18] = "|cffBFFF80"
                set OriginalHex[19] = "|cffDCB9EB"
                set OriginalHex[20] = "|cff282828"
                set OriginalHex[21] = "|cffEBF0FF"
                set OriginalHex[22] = "|cff00781E"
                set OriginalHex[23] = "|cffA46F33"
            endif
        
            set thistype.first = User.NULL

            loop
                exitwhen i == bj_MAX_PLAYERS

                set p         = User(i)
                set p.handle  = Player(i)
                set p.id      = i
          
                set thistype.Color[i] = GetPlayerColor(p.handle)
                set CurrentColor[i] = thistype.Color[i]
            
                if (GetPlayerController(p.handle) == MAP_CONTROL_USER and GetPlayerSlotState(p.handle) == PLAYER_SLOT_STATE_PLAYING) then

                    set .PlayingPlayer[AmountPlaying] = p
                    set .PlayingPlayerIndex[i] = .AmountPlaying
                  
                   set .last = i
                  
                    if (.first == User.NULL) then
                        set .first = i
                        set User(i).next = User.NULL
                        set User(i).prev = User.NULL
                    else
                        set User(i).prev = PlayingPlayer[AmountPlaying-1].id
                        set PlayingPlayer[AmountPlaying-1].next = User(i)
                        set User(i).next = User.NULL
                    endif

                    set p.isPlaying = true
              
                    call TriggerRegisterPlayerEvent(t, p.handle, EVENT_PLAYER_LEAVE)
                    call ForceAddPlayer(FORCE_PLAYING, p.handle)
              
                    set Hex[p] = OriginalHex[GetHandleId(thistype.Color[i])]
              
                    set .AmountPlaying = .AmountPlaying + 1

                endif
          
                set Name[p] = GetPlayerName(p.handle)
                set p.originalName=Name[p]
          
                set i = i + 1
            endloop
      
            call TriggerAddCondition(t, Filter(function thistype.onLeave))
        endmethod
    endmodule

    //===========================================================================


    static if (ARRAY_LOOKUP) then
        static if (HOOK_SAFETY) then
            private function SetPlayerNameHook takes player whichPlayer, string name returns nothing
                set Name[GetPlayerId(whichPlayer)] = name
            endfunction
      
            private function SetPlayerColorHook takes player whichPlayer, playercolor color returns nothing
                local User p = User[whichPlayer]
          
                set Hex[p] = OriginalHex[GetHandleId(color)]
                set CurrentColor[p] = color
          
                static if (AUTO_COLOR_UNITS) then
                    call p.colorUnits(color)
                endif
            endfunction
      
            hook SetPlayerName SetPlayerNameHook
            hook SetPlayerColor SetPlayerColorHook
        endif
    endif

endlibrary
JASS:
library Savecode requires BigNum

    private constant function uppercolor takes nothing returns string
        return "|cffff0000"
    endfunction

    private constant function lowercolor takes nothing returns string
        return "|cff00ff00"
    endfunction

    private constant function numcolor takes nothing returns string
        return "|cff0000ff"
    endfunction

    private function player_charset takes nothing returns string
        return "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
    endfunction

    private function player_charsetlen takes nothing returns integer
        return StringLength(player_charset())
    endfunction

    private function charset takes nothing returns string
        return "!#$%&'()*+,-.0123456789:;=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_abcdefghijklmnopqrstuvwxyz{|}`"
    endfunction

    private function charsetlen takes nothing returns integer
        return StringLength(charset())
    endfunction

    private function BASE takes nothing returns integer
        return charsetlen()
    endfunction

    private constant function HASHN takes nothing returns integer
        return 5000 //1./HASHN() is the probability of a random code being valid
    endfunction

    private constant function MAXINT takes nothing returns integer
        return 2147483647
    endfunction

    private function player_chartoi takes string c returns integer
        local integer i = 0
        local string cs = player_charset()
        local integer len = player_charsetlen()
        loop
            exitwhen i>=len or c == SubString(cs,i,i+1)
            set i = i + 1
        endloop
        return i
    endfunction

    private function chartoi takes string c returns integer
        local integer i = 0
        local string cs = charset()
        local integer len = charsetlen()
        loop
            exitwhen i>=len or c == SubString(cs,i,i+1)
            set i = i + 1
        endloop
        return i
    endfunction

    private function itochar takes integer i returns string
        return SubString(charset(),i,i+1)
    endfunction

    //You probably want to use a different char set for this
    //Also, use a hash that doesn't suck so much
    private function scommhash takes string s returns integer
        local integer array count
        local integer i = 0
        local integer len = StringLength(s)
        local integer x
        set s = StringCase(s,true)
        loop
            exitwhen i >= len
            set x = player_chartoi(SubString(s,i,i+1))
            set count[x] = count[x] + 1
            set i = i + 1
        endloop
        set i = 0
        set len = player_charsetlen()
        set x = 0
        loop
            exitwhen i>= len
            set x = count[i]*count[i]*i+count[i]*x+x+199
    //      call BJDebugMsg(I2S(x)+" "+I2S(count[i]))
    //      call TriggerSleepAction(0.)
            set i = i + 1
        endloop
        if x < 0 then
            set x = -x
        endif
        return x
    endfunction

    private function modb takes integer x returns integer
        if x >= BASE() then
            return x - BASE()
        elseif x < 0 then
            return x + BASE()
        else
            return x
        endif
    endfunction

    struct Savecode
        real digits     //logarithmic approximation
        BigNum bignum
       
        static method create takes nothing returns Savecode
            local Savecode sc = Savecode.allocate()
            set sc.digits = 0.
            set sc.bignum = BigNum.create(BASE())
            return sc
        endmethod
       
        method onDestroy takes nothing returns nothing
            call .bignum.destroy()
        endmethod

        method Encode takes integer val, integer max returns nothing
            set .digits = .digits + log(max+1,BASE())
            call .bignum.MulSmall(max+1)
            call .bignum.AddSmall(val)
        endmethod
       
        method Decode takes integer max returns integer
            return .bignum.DivSmall(max+1)
        endmethod
       
        method IsEmpty takes nothing returns boolean
            return .bignum.IsZero()
        endmethod
       
        method Length takes nothing returns real
            return .digits
        endmethod
       
        method Clean takes nothing returns nothing
            call .bignum.Clean()
        endmethod
       
            //These functions get too intimate with BigNum_l
        method Pad takes nothing returns nothing
            local BigNum_l cur = .bignum.list
            local BigNum_l prev
            local integer maxlen = R2I(1.0 + .Length())
           
            loop
                exitwhen cur == 0
                set prev = cur
                set cur = cur.next
                set maxlen = maxlen - 1
            endloop
            loop
                exitwhen maxlen <= 0
                set prev.next = BigNum_l.create()
                set prev = prev.next
                set maxlen = maxlen - 1
            endloop
        endmethod
       
        method ToString takes nothing returns string
            local BigNum_l cur = .bignum.list
            local string s = ""
            loop
                exitwhen cur == 0
                set s = itochar(cur.leaf) + s
                set cur = cur.next
            endloop
            return s
        endmethod
       
        method FromString takes string s returns nothing
            local integer i = StringLength(s)-1
            local BigNum_l cur = BigNum_l.create()
            set .bignum.list = cur
            loop
                set cur.leaf = chartoi(SubString(s,i,i+1))     
                exitwhen i <= 0
                set cur.next = BigNum_l.create()
                set cur = cur.next
                set i = i - 1
            endloop
        endmethod
       
        method Hash takes nothing returns integer
            local integer hash = 0
            local integer x
            local BigNum_l cur = .bignum.list
            loop
                exitwhen cur == 0
                set x = cur.leaf
                set hash = ModuloInteger(hash+79*hash/(x+1) + 293*x/(1+hash - (hash/BASE())*BASE()) + 479,HASHN())
                set cur = cur.next
            endloop
            return hash
        endmethod

        //this is not cryptographic which is fine for this application
        //sign = 1 is forward
        //sign = -1 is backward
        method Obfuscate takes integer key, integer sign returns nothing
            local integer seed = GetRandomInt(0,MAXINT())
            local integer advance
            local integer x
            local BigNum_l cur = .bignum.list
       
       
            if sign == -1 then
                call SetRandomSeed(.bignum.LastDigit())
                set cur.leaf = modb(cur.leaf + sign*GetRandomInt(0,BASE()-1))
                set x = cur.leaf
            endif
           
            call SetRandomSeed(key)
            loop
                exitwhen cur == 0
               
                if sign == -1 then
                    set advance = cur.leaf
                endif
                set cur.leaf = modb(cur.leaf + sign*GetRandomInt(0,BASE()-1))
                if sign == 1 then
                    set advance = cur.leaf
                endif
                set advance = advance + GetRandomInt(0,BASE()-1)
                call SetRandomSeed(advance)
               
                set x = cur.leaf
                set cur = cur.next
            endloop
           
            if sign == 1 then
                call SetRandomSeed(x)
                set .bignum.list.leaf = modb(.bignum.list.leaf + sign*GetRandomInt(0,BASE()-1))
            endif
           
            call SetRandomSeed(seed)
        endmethod
       
        method Dump takes nothing returns nothing
            local BigNum_l cur = .bignum.list
            local string s = ""
            set s = "max: "+R2S(.digits)
           
            loop
                exitwhen cur == 0
                set s = I2S(cur.leaf)+" "+s
                set cur = cur.next
            endloop
            call BJDebugMsg(s)
        endmethod
       
        method Save takes player p, integer loadtype returns string
            local integer key = scommhash(GetPlayerName(p))+loadtype*73
            local string s
            local integer hash
            call .Clean()
            set hash = .Hash()
            call .Encode(hash,HASHN())
            call .Clean()
           
            /////////////////////// Save code information.  Comment out next two lines in implementation
            //call BJDebugMsg("Expected length: " +I2S(R2I(1.0+.Length())))
            //call BJDebugMsg("Room left in last char: "+R2S(1.-ModuloReal((.Length()),1)))
            ///////////////////////
           
            call .Pad()
            call .Obfuscate(key,1)
            return .ToString()
        endmethod
       
        method Load takes player p, string s, integer loadtype returns boolean
            local integer ikey = scommhash(GetPlayerName(p))+loadtype*73
            local integer inputhash
           
            call .FromString(s)
            call .Obfuscate(ikey,-1)
            set inputhash = .Decode(HASHN())
           
            call .Clean()
           
            return inputhash == .Hash()
        endmethod
    endstruct
    private function isupper takes string c returns boolean
        return c == StringCase(c,true)
    endfunction

    private function ischar takes string c returns boolean
        return S2I(c) == 0 and c!= "0"
    endfunction

    private function chartype takes string c returns integer
        if(ischar(c)) then
            if isupper(c) then
                return 0
            else
                return 1
            endif
        else
            return 2
        endif
    endfunction

    private function testchar takes string c returns nothing
        if(ischar(c)) then
            if isupper(c) then
                call BJDebugMsg(c+" isupper")
            else
                call BJDebugMsg(c+" islower")
            endif
        else
            call BJDebugMsg(c+ " isnumber")
        endif
    endfunction

    public function colorize takes string s returns string
        local string out = ""
        local integer i = 0
        local integer len = StringLength(s)
        local integer ctype
        local string c
        loop
            exitwhen i >= len
            set c = SubString(s,i,i+1)
            set ctype = chartype(c)
            if ctype == 0 then
                set out = out + uppercolor()+c+"|r"
            elseif ctype == 1 then
                set out = out + lowercolor()+c+"|r"
            else
                set out = out + numcolor()+c+"|r"
            endif
            set i = i + 1
        endloop
        return out
    endfunction

    private function prop_Savecode takes nothing returns boolean
        local string s
        local Savecode loadcode

    //--- Data you want to save ---
        local integer medal1 = 10
        local integer medal2 = 3
        local integer medalmax = 13
        local integer XP = 1337
        local integer XPmax = 1000000

        local Savecode savecode = Savecode.create()

        call SetPlayerName(Player(0),"yomp")
        call SetPlayerName(Player(1),"fruitcup")

        call savecode.Encode(medal1,medalmax)
        call savecode.Encode(medal2,medalmax)
        call savecode.Encode(XP,XPmax)

    //--- Savecode_save generates the savecode for a specific player ---
        set s = savecode.Save(Player(0),1)
        call savecode.destroy()
    //  call BJDebugMsg("Savecode: " + Savecode_colorize(s))

    //--- User writes down code, inputs again ---

        set loadcode = Savecode.create()
        if loadcode.Load(Player(0),s,1) then
    //      call BJDebugMsg("load ok")
        else
            call BJDebugMsg("load failed")  
            return false
        endif

    //Must decode in reverse order of encodes

    //               load object : max value that data can take
        if XP != loadcode.Decode(XPmax) then
            return false
        elseif medal2 != loadcode.Decode(medalmax) then
            return false
        elseif medal1 != loadcode.Decode(medalmax) then
            return false
        endif
        call loadcode.destroy()
        return true
    endfunction

endlibrary

//===========================================================================
function InitTrig_save_system takes nothing returns nothing
endfunction
JASS:
library BigNum

//prefer algebraic approach because of real subtraction issues
function log takes real y, real base returns real
    local real x
    local real factor = 1.0
    local real logy = 0.0
    local real sign = 1.0
    if(y < 0.) then
        return 0.0
    endif
    if(y < 1.) then
        set y = 1.0/y
        set sign = -1.0
    endif
    //Chop out powers of the base
    loop
        exitwhen y < 1.0001    //decrease this ( bounded below by 1) to improve precision
        if(y > base) then
            set y = y / base
            set logy = logy + factor
        else
            set base = SquareRoot(base)     //If you use just one base a lot, precompute its squareroots
            set factor = factor / 2.
        endif
    endloop
    return sign*logy
endfunction

struct BigNum_l
    integer leaf
    BigNum_l next
    debug static integer nalloc = 0
   
    static method create takes nothing returns BigNum_l
        local BigNum_l bl = BigNum_l.allocate()
        set bl.next = 0
        set bl.leaf = 0
        debug set BigNum_l.nalloc = BigNum_l.nalloc + 1
        return bl
    endmethod
    method onDestroy takes nothing returns nothing
        debug set BigNum_l.nalloc = BigNum_l.nalloc - 1
    endmethod
   
    //true:  want destroy
    method Clean takes nothing returns boolean
        if .next == 0 and .leaf == 0 then
            return true
        elseif .next != 0 and .next.Clean() then
            call .next.destroy()
            set .next = 0
            return .leaf == 0
        else
            return false
        endif
    endmethod
   
    method DivSmall takes integer base, integer denom returns integer
        local integer quotient
        local integer remainder = 0
        local integer num
       
        if .next != 0 then
            set remainder = .next.DivSmall(base,denom)
        endif
       
        set num = .leaf + remainder*base
        set quotient = num/denom
        set remainder = num - quotient*denom
        set .leaf = quotient
        return remainder
    endmethod
endstruct

struct BigNum
    BigNum_l list
    integer base
   
    static method create takes integer base returns BigNum
        local BigNum b = BigNum.allocate()
        set b.list = 0
        set b.base = base
        return b
    endmethod

    method onDestroy takes nothing returns nothing
        local BigNum_l cur = .list
        local BigNum_l next
        loop
            exitwhen cur == 0
            set next = cur.next
            call cur.destroy()
            set cur = next
        endloop
    endmethod
   
    method IsZero takes nothing returns boolean
        local BigNum_l cur = .list
        loop
            exitwhen cur == 0
            if cur.leaf != 0 then
                return false
            endif
            set cur = cur.next
        endloop
        return true
    endmethod
   
    method Dump takes nothing returns nothing
        local BigNum_l cur = .list
        local string s = ""
        loop
            exitwhen cur == 0
            set s = I2S(cur.leaf)+" "+s
            set cur = cur.next
        endloop
        call BJDebugMsg(s)
    endmethod
   
    method Clean takes nothing returns nothing
        local BigNum_l cur = .list
        call cur.Clean()
    endmethod
   
    //fails if bignum is null
    //BASE() + carry must be less than MAXINT()
    method AddSmall takes integer carry returns nothing
        local BigNum_l next
        local BigNum_l cur = .list
        local integer sum
       
        if cur == 0 then
            set cur = BigNum_l.create()
            set .list = cur
        endif
       
        loop
            exitwhen carry == 0
            set sum = cur.leaf + carry
            set carry = sum / .base
            set sum = sum - carry*.base
            set cur.leaf = sum
           
            if cur.next == 0 then
                set cur.next = BigNum_l.create()
            endif
            set cur = cur.next
        endloop
    endmethod
   
    //x*BASE() must be less than MAXINT()
    method MulSmall takes integer x returns nothing
        local BigNum_l cur = .list
        local integer product
        local integer remainder
        local integer carry = 0
        loop
            exitwhen cur == 0 and carry == 0
            set product = x * cur.leaf + carry
            set carry = product/.base
            set remainder = product - carry*.base
            set cur.leaf = remainder
            if cur.next == 0 and carry != 0 then
                set cur.next = BigNum_l.create()
            endif
            set cur = cur.next
        endloop
    endmethod
   
    //Returns remainder
    method DivSmall takes integer denom returns integer
        return .list.DivSmall(.base,denom)
    endmethod
   
    method LastDigit takes nothing returns integer
        local BigNum_l cur = .list
        local BigNum_l next
        loop
            set next = cur.next
            exitwhen next == 0
            set cur = next
        endloop
        return cur.leaf
    endmethod
endstruct

private function prop_Allocator1 takes nothing returns boolean
    local BigNum b1
    local BigNum b2
    set b1 = BigNum.create(37)
    call b1.destroy()
    set b2 = BigNum.create(37)
    call b2.destroy()
    return b1 == b2
endfunction

private function prop_Allocator2 takes nothing returns boolean
    local BigNum b1
    local boolean b = false
    set b1 = BigNum.create(37)
    call b1.AddSmall(17)
    call b1.MulSmall(19)
    debug if BigNum_l.nalloc < 1 then
    debug     return false
    debug endif
    call b1.destroy()
    debug set b = BigNum_l.nalloc == 0
    return b
endfunction

private function prop_Arith takes nothing returns boolean
    local BigNum b1
    set b1 = BigNum.create(37)
    call b1.AddSmall(73)
    call b1.MulSmall(39)
    call b1.AddSmall(17)
    //n = 2864
    if b1.DivSmall(100) != 64 then
        return false
    elseif b1.DivSmall(7) != 0 then
        return false
    elseif b1.IsZero() then
        return false
    elseif b1.DivSmall(3) != 1 then
        return false
    elseif b1.DivSmall(3) != 1 then
        return false
    elseif not b1.IsZero() then
        return false
    endif
    return true
endfunction

endlibrary

//===========================================================================
function InitTrig_bignum_lib takes nothing returns nothing
endfunction

Currently suspect this Trigger to not work properly on lagging bnet games.
  • Custom Load Event
    • Events
      • Game - SaveLoadEvent becomes Equal to 1.00
    • Conditions
    • Actions
      • ...
 
Last edited:
Top