1. Are you planning to upload your awesome spell or system to Hive? Please review the rules here.
    Dismiss Notice
  2. Updated Resource Submission Rules: All model & skin resource submissions must now include an in-game screenshot. This is to help speed up the moderation process and to show how the model and/or texture looks like from the in-game camera.
    Dismiss Notice
  3. DID YOU KNOW - That you can unlock new rank icons by posting on the forums or winning contests? Click here to customize your rank or read our User Rank Policy to see a list of ranks that you can unlock. Have you won a contest and still havn't received your rank award? Then please contact the administration.
    Dismiss Notice
  4. The Lich King demands your service! We've reached the 19th edition of the Icon Contest. Come along and make some chilling servants for the one true king.
    Dismiss Notice
  5. The 4th SFX Contest has started. Be sure to participate and have a fun factor in it.
    Dismiss Notice
  6. The poll for the 21st Terraining Contest is LIVE. Be sure to check out the entries and vote for one.
    Dismiss Notice
  7. The results are out! Check them out.
    Dismiss Notice
  8. Don’t forget to sign up for the Hive Cup. There’s a 555 EUR prize pool. Sign up now!
    Dismiss Notice
  9. The Hive Workshop Cup contest results have been announced! See the maps that'll be featured in the Hive Workshop Cup tournament!
    Dismiss Notice
  10. Check out the Staff job openings thread.
    Dismiss Notice
Dismiss Notice
60,000 passwords have been reset on July 8, 2019. If you cannot login, read this.

Trigger Viewer

Codeless Save Load v1.3.9.w3x
Variables
Initialization
Map Init
Map Start
SaveLoad - GUI
Save Init
SaveHelpLib
How to import
Save GUI
Load GUI
Load GUI2
Auto Save
SaveLoad - vJass
Save vJass
Load vJass
Codeless Save and Load
Demo
Dialog System
Core (Required)
Sync
SyncInteger
File IO
GameData
Optimal Save System 0.4
save system
bignum lib
Extra
PlayerUtils
GroupUtils
xebasic
How to:
Load without local files
Enable local files
Update
Enter map-specific custom script code below. This text will be included in the map script after variables are declared and before any trigger code.
//TESH.scrollpos=0
//TESH.alwaysfold=0
Name Type Is Array Initial Value
HeroXPConstant integer No
HeroXPLevelFactor integer No
HeroXPPrevLevelFactor integer No
HeroXPRequired integer No
LocalFiles_WarningMessage string No
MapName string No
SaveAbilityType abilcode Yes
SaveAbilityTypeMax integer No
SaveCount integer No
SaveHeroName boolean No
SaveItemType itemcode Yes
SaveItemTypeMax integer No
SaveLoad_HeroName boolean No
SaveLoad_Slot integer No
SaveLoadEvent real No
SaveLoadEvent_Code string No
SaveLoadEvent_Player player No
SaveMaxValue integer Yes
SavePlayerHero unit Yes
SaveShowCode boolean No
SaveTempInt integer No
SaveTempReal real No
SaveTempString string No
SaveTempUnit unit No
SaveUnitMaxStat integer No
SaveUnitType unitcode Yes
SaveUnitTypeMax integer No
SaveValue integer Yes
Map Init
  Events
    Map initialization
  Conditions
  Actions
    Visibility - Disable fog of war
    Visibility - Disable black mask
Map Start
  Events
    Time - Elapsed game time is 0.00 seconds
  Conditions
  Actions
    Game - Display to (All players) for 120.00 seconds the text: Press the |cffffcc00ESC|r key to bring up the dialog menu.Commands:|cffffcc00-|rsave|cffffcc00-|rload |cffffcc00<|rslot|cffffcc00>|r or -load |cffffcc00<|rcode|cffffcc00>|r
    -------- 1.28.0 check --------
    Custom script: if (OrderId("frostarmoron") == 852459) then
    Game - Display to (All players) the text: |cffe53b3bLoading does not work on patch 1.28.0|r
    Set LocalFiles_WarningMessage = |cffe53b3bLoading does not work on patch 1.28.0|r
    Custom script: elseif (not MemoryHacksEnabled() and not File.enabled) then
    -------- Local files check --------
    Custom script: call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60000, udg_LocalFiles_WarningMessage)
    Custom script: endif
    Player Group - Pick every player in (All players controlled by a User player) and do (Actions)
      Loop - Actions
        If (All Conditions are True) then do (Then Actions) else do (Else Actions)
          If - Conditions
            ((Picked player) slot status) Equal to Is playing
          Then - Actions
            Player - Set (Picked player) Food max to 100
            Player - Set (Picked player) Food cap to 100
            Custom script: set udg_SaveTempInt = GetRandomInt(1, udg_SaveUnitTypeMax)
            Unit - Create 1 SaveUnitType[SaveTempInt] for (Picked player) at ((Picked player) start location) facing Default building facing degrees
            Custom script: set udg_SavePlayerHero[GetPlayerId(GetEnumPlayer())] = bj_lastCreatedUnit
            Selection - Select (Last created unit) for (Picked player)
          Else - Actions
This is part of the codeless save demo.
Save Init
  Events
    Map initialization
  Conditions
  Actions
    -------- ------------------- --------
    -------- This willl be the directory the save codes will be saved to. --------
    -------- ------------------- --------
    Set MapName = CodelessSave
    -------- ------------------- --------
    -------- This message will display to players who don't have local files enabled --------
    -------- ------------------- --------
    Set LocalFiles_WarningMessage = |cffe53b3bYou need to enable local files to load your character from disk!
    Custom script: set udg_LocalFiles_WarningMessage = udg_LocalFiles_WarningMessage + "\nTo enable local files, please run: |cffffcc00" + File.localFileScriptName + "|cffe53b3b (remove the |cffffcc00.txt|r extension) from your |cffffcc00Warcraft III folder|r|cffe53b3b.\n\n"
    Set LocalFiles_WarningMessage = (LocalFiles_WarningMessage + |cff4ae84aYour progress will still be automatically saved.|r)
    Custom script: set udg_LocalFiles_WarningMessage = udg_LocalFiles_WarningMessage + "\n\n"
    -------- ------------------- --------
    -------- Show the save code --------
    -------- ------------------- --------
    Set SaveShowCode = True
    -------- ------------------- --------
    -------- Save hero name (only works with the save slots, not raw save codes) --------
    -------- Also make sure to set the object editor field "Text - Proper Names Used" to 999 for all of your heroes. --------
    -------- ------------------- --------
    Set SaveHeroName = True
    -------- ------------------- --------
    -------- Set these to the values they are in the Advanced -> Gameplay constants --------
    -------- Allows us to calculate how much XP a hero has --------
    -------- Note: You can also save EXP the easy way but it will generate a longer code. --------
    -------- ------------------- --------
    Set HeroXPConstant = 0
    Set HeroXPLevelFactor = 100
    Set HeroXPPrevLevelFactor = 1
    Set HeroXPRequired = 200
    -------- ------------------- --------
    -------- Max STR/AGI/INT --------
    -------- ------------------- --------
    Set SaveUnitMaxStat = 999
    -------- ------------------- --------
    -------- Store unit types that can be saved here --------
    -------- ------------------- --------
    Set SaveUnitType[0] = No unit-type
    Set SaveUnitType[1] = Tauren Chieftain
    Set SaveUnitType[2] = Lich
    Set SaveUnitType[3] = Demon Hunter
    Set SaveUnitType[4] = Naga Sea Witch
    Set SaveUnitType[5] = Beastmaster
    Set SaveUnitType[6] = Blood Mage
    Set SaveUnitType[7] = Demon Hunter (Demon Form)
    Set SaveUnitTypeMax = 7
    -------- ------------------- --------
    -------- Store item types that can be saved here --------
    -------- ------------------- --------
    Set SaveItemType[0] = (Item-type of No item)
    Set SaveItemType[1] = Crown of Kings +5
    Set SaveItemType[2] = Kelen's Dagger of Escape
    Set SaveItemType[3] = Mask of Death
    Set SaveItemType[4] = Orb of Frost
    Set SaveItemType[5] = Ring of Protection +5
    Set SaveItemType[6] = Blood Key
    Set SaveItemType[7] = Cheese
    Set SaveItemType[8] = Claws of Attack +15
    Set SaveItemTypeMax = 8
    -------- ------------------- --------
    -------- Store ability types that can be saved here --------
    -------- ------------------- --------
    Set SaveAbilityType[1] = Endurance Aura
    Set SaveAbilityType[2] = Reincarnation
    Set SaveAbilityType[3] = War Stomp
    Set SaveAbilityType[4] = Frost Nova
    Set SaveAbilityType[5] = Frost Armor (Autocast)
    Set SaveAbilityType[6] = Dark Ritual
    Set SaveAbilityType[7] = Death And Decay
    Set SaveAbilityType[8] = Mana Burn
    Set SaveAbilityType[9] = Immolation
    Set SaveAbilityType[10] = Evasion
    Set SaveAbilityType[11] = Metamorphosis
    Set SaveAbilityType[12] = Forked Lightning
    Set SaveAbilityType[13] = Frost Arrows
    Set SaveAbilityType[14] = Mana Shield
    Set SaveAbilityType[15] = Tornado
    Set SaveAbilityType[16] = Summon Bear
    Set SaveAbilityType[17] = Summon Quilbeast
    Set SaveAbilityType[18] = Summon Hawk
    Set SaveAbilityType[19] = Stampede
    Set SaveAbilityType[20] = Flame Strike
    Set SaveAbilityType[21] = Banish
    Set SaveAbilityType[22] = Siphon Mana
    Set SaveAbilityType[23] = Phoenix
    Set SaveAbilityType[24] = Shockwave
    Set SaveAbilityTypeMax = 24
    -------- ------------------- --------
    -------- Automatically copy variables --------
    -------- ------------------- --------
    Set SaveLoadEvent = -1.00
    Set SaveCount = 0
    Set SaveValue[0] = 0
    Set SaveMaxValue[0] = 0
    Set SaveTempInt = 0
    Set SavePlayerHero[0] = No unit
    Set SaveLoadEvent_Code =
    Set SaveLoadEvent_Player = Player 1 (Red)
library SaveHelper requires Savecode, Sync, FileIO
   
    // helper for GUI
   
    globals
        integer array CurrentCharSaveSlot
        private real array HeroProperName_Data
        private unit array HeroProperName
        private string array TempHeroName
    endglobals

    function GetStringLine takes string source, integer whichLine returns string
        local integer length = StringLength(source)
        local integer i = 0
        local string c = ""
        local integer currentline = 0
        local string currentlinestr=""
       
        loop
            exitwhen i > length
           
            set c = SubString(source, i, i + 2)
           
            if (c == "|n") then
                set currentline=currentline+1
                if (currentline==whichLine) then
                    return currentlinestr
                else
                    set currentlinestr = ""
                endif
            else
                set currentlinestr = currentlinestr + SubString(source, i, i + 1)
            endif
           
            set i = i + 1
        endloop
       
        return SubString(currentlinestr, 1, 999)
    endfunction
   
    function SaveCode_AddValue takes integer value, integer maxvalue returns nothing
        set udg_SaveCount = udg_SaveCount + 1
        set udg_SaveValue[udg_SaveCount] = value
        set udg_SaveMaxValue[udg_SaveCount] = maxvalue
    endfunction
   
    function SaveCode_LoadValue takes integer savecode, integer index returns Savecode
        return Savecode(savecode).Decode(udg_SaveMaxValue[index])
    endfunction
   
    function SaveCode_LoadNextValue takes nothing returns nothing
        set udg_SaveValue[udg_SaveCount] = SaveCode_LoadValue(udg_SaveTempInt, udg_SaveCount)
    endfunction
   
    function SaveCode_U2I takes integer id returns integer
        local integer i = 0
        loop
            exitwhen i > udg_SaveUnitTypeMax
            if (id == udg_SaveUnitType[i]) then
                return i
            endif
            set i = i + 1
        endloop
        return 0
    endfunction
   
    function SaveCode_I2I takes integer id returns integer
        local integer i = 0
        loop
            exitwhen i > udg_SaveItemTypeMax
            if (id == udg_SaveItemType[i]) then
                return i
            endif
            set i = i + 1
        endloop
        return 0
    endfunction
   
    function FindLevelXP 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
    endfunction
   
    function DeleteCharSlot takes player p, integer slot returns nothing
        local File file
        local string s
        if (GetLocalPlayer() == p) then
            call GameData.delete("Char"+I2S(slot))
        endif
    endfunction
   
    function SaveCode_Save takes player p, integer slot returns nothing
        local string  s
        local integer i = 0
        local integer pid = GetPlayerId(p)
        local Savecode savecode
        local Savecode loadcode
        local string fname = "Char"+I2S(slot)
        local File file

        if (udg_SaveCount == -1) then
            return
        endif

        if (GetLocalPlayer() == p) then
            if (slot == 10) then // autosave
                set fname = "Autosave"
            else
                set s = GameData.read(fname)
               
                if (slot < 10 and slot != CurrentCharSaveSlot[pid] and s != null and StringLength(s) > 0) then
                    call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 30, "That save slot is in use.")
                    return
                endif
               
                if (slot != CurrentCharSaveSlot[pid]) then
                    //call DeleteCharSlot(p, CurrentCharSaveSlot[pid])
                endif
            endif
        endif
       
        if (slot < 10) then
            set CurrentCharSaveSlot[pid] = slot
        endif

        set savecode = Savecode.create()
       
        loop
            exitwhen i > udg_SaveCount
            call savecode.Encode(udg_SaveValue[i], udg_SaveMaxValue[i])
            set i = i + 1
        endloop

        set s = savecode.Save(p, 1)

        if (GetLocalPlayer() == p) then
            if (udg_SaveHeroName) then
                call GameData.write(fname, GetObjectName(udg_SaveUnitType[udg_SaveValue[udg_SaveCount]]) + " (" + GetHeroProperName(udg_SavePlayerHero[pid]) + ")|n" + s)
            else
                call GameData.write(fname, GetObjectName(udg_SaveUnitType[udg_SaveValue[udg_SaveCount]]) + "|n" + s)
            endif
        endif
       
        if (slot != 10 and udg_SaveShowCode) then
            call DisplayTimedTextToPlayer(p, 0, 0, 0, Savecode_colorize(s))
        endif
       
        set udg_SaveCount = -1
        set udg_SaveValue[0] = -1
        set udg_SaveMaxValue[0] = -1
        set udg_SaveLoad_Slot = -1
    endfunction

    private function OnRecieveString takes nothing returns boolean
        local SyncData d = GetSyncedData()
        set udg_SaveLoadEvent_Code = d.readString(0)
        set udg_SaveLoadEvent_Player = d.from
        set udg_SaveLoadEvent = 1.
        set udg_SaveLoadEvent = -1
        call d.destroy()
        return false
    endfunction
   
    function t_SaveCode_LoadHeroWithProperName takes nothing returns nothing
        local integer pid = R2I(HeroProperName_Data[0])
        local player p = Player(pid)
        local string s = TempHeroName[pid]
        local integer i = 0
        local integer a = 0
        local integer l = StringLength(s)
        local unit u = null
        local integer id = CurrentCharSaveSlot[JASS_MAX_ARRAY_SIZE-1]
        local real x = HeroProperName_Data[2]
        local real y = HeroProperName_Data[3]
        local real f = HeroProperName_Data[4]
       
        loop
            exitwhen i > l or SubString(s, i, i + 1) == "("
            set i = i + 1
        endloop
       
        if (i >= l) then
            set HeroProperName[pid] = CreateUnit(p, id, x, y, f)
            return
        endif
       
        set a = i
        loop
            exitwhen SubString(s, a, a + 1) == ")"
            set a = a + 1
        endloop
       
        set s = SubString(s, i + 1, a)
       
        set i = 0
       
        loop
            exitwhen i > 50
           
            if (u != null) then
                call RemoveUnit(u)
                set u = null
            endif
           
            set u = CreateUnit(p, id, x, y, f)
           
            if (not IsUnitType(u, UNIT_TYPE_HERO)) then
                set HeroProperName[pid] = u
                set u = null
                return
            endif
           
            if (GetHeroProperName(u) == s) then
                set HeroProperName[pid] = u
                set u = null
                return
            endif

            set i = i + 1
        endloop
       
        set HeroProperName[pid] = u
        set u = null
    endfunction
   
    function SaveCode_LoadHeroWithProperName takes player p, integer id, real x, real y, real f returns unit
        local integer pid = GetPlayerId(p)
       
        set CurrentCharSaveSlot[JASS_MAX_ARRAY_SIZE-1] = id
       
        set HeroProperName_Data[0] = I2R(pid)
        //set HeroProperName_Data[1] = id
        set HeroProperName_Data[2] = x
        set HeroProperName_Data[3] = y
        set HeroProperName_Data[4] = f
       
        call ExecuteFunc("t_SaveCode_LoadHeroWithProperName")
       
        return HeroProperName[pid]
    endfunction
   
    function SaveCode_ReadSlot takes player p, integer slot returns string
        local File file
        local string val = ""
        local string header = ""
        local string line = ""
        local integer i = 0
        local string fname = "Char" + I2S(slot)
       
        // load latest save file from disk (only for the triggering player)
        if (GetLocalPlayer() == p) then
            if (slot == 10) then
                set fname = "Autosave"
            endif
           
            set val = GameData.read(fname)
            set header = GetStringLine(val, 1)
           
            if (header == null or StringLength(header) <= 0) then
                if (slot != 10) then
                    call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "Slot #" + I2S(slot+1) + " is emtpy.")
                else
                    call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "Your autosave slot is empty.")
                endif
            else
                set TempHeroName[GetPlayerId(p)]=header

            endif
        endif
       
        return val
    endfunction
   
    function SaveCode_Load takes player p, integer slot returns nothing
        local integer id = GetPlayerId(p)
        local string val = SaveCode_ReadSlot(p, slot)
        local SyncData data = SyncData.create(Player(id))
       
        call data.addString(val, 64)
        call data.addEventListener(Filter(function OnRecieveString))
        call data.start()
       
        //return val
    endfunction

    function SaveCharToSlot takes unit u, integer slot returns nothing
        local integer pid = GetPlayerId(GetOwningPlayer(u))
        local unit temp = udg_SavePlayerHero[pid]
       
        set udg_SaveLoad_Slot = slot
       
        set udg_SavePlayerHero[pid] = u
        if (TriggerEvaluate(gg_trg_Save_GUI)) then
            call TriggerExecute(gg_trg_Save_GUI)
        endif
        set udg_SavePlayerHero[pid] = temp
    endfunction
   
    function GetSaveSlotTitle takes player p, integer slot returns string
        local File file
        local string header = null
        local string fname
       
        if (GetLocalPlayer() == p) then
            if (slot == 10) then
                set fname = "Autosave"
            else
                set fname = "Char" + I2S(slot)
            endif
            set header = GetStringLine(GameData.read(fname), 1)
        endif
       
        return header
    endfunction
   
    function LoadSaveSlot_OnRecieveString takes nothing returns boolean
        local SyncData data = GetSyncedData()
        local integer id = GetPlayerId(data.from)

        local Savecode loadcode
       
        if (udg_SaveHeroName) then
            set TempHeroName[id] = data.readString(0)
            set udg_SaveLoadEvent_Code = data.readString(1)
        else
            set udg_SaveLoadEvent_Code = data.readString(0)
        endif

        set udg_SaveLoadEvent_Player = data.from
        set udg_SaveTempReal = data.timeElapsed
        set udg_SaveLoadEvent = 1.
        set udg_SaveLoadEvent = -1
       
        call data.destroy()
       
        return false
    endfunction

    function LoadSaveSlot takes player p, integer slot returns boolean
        local integer id
        local string val = ""
        local string header = ""
        local integer i = 0
        local File file
        local string line
        local SyncData data
        local string fname
       
        if (i > 10) then
            return false
        endif
       
        // load latest save file from disk (only for the triggering player)
        if (GetLocalPlayer() == p) then
            if (slot == 10) then
                set fname = "Autosave"
            else
                set fname = "Char" + I2S(slot)
            endif
           
            set val = GameData.read(fname)
            set header = GetStringLine(val, 1)
            set val = GetStringLine(GameData.read(fname), 2)
           
            if (header == null or StringLength(header) <= 0) then
                if (slot != 10) then
                    call DisplayTextToPlayer(p, 0, 0, "Slot #" + I2S(slot+1) + " is emtpy.")
                else
                    call DisplayTextToPlayer(p, 0, 0, "Your autosave slot is empty.")
                endif
            else
                set TempHeroName[GetPlayerId(p)]=header

            endif
        endif
       
        set id = GetPlayerId(p)

        if (GetLocalPlayer() == p and val != "") then
            call DisplayTextToPlayer(p, 0, 0, "Loading your " + header)
        endif

        set data = SyncData.create(Player(id))
       
        if (udg_SaveHeroName) then
            call data.addString(header, 32)
        endif
       
        call data.addString(val, 64)
        call data.addEventListener(Filter(function LoadSaveSlot_OnRecieveString))
        call data.start()

        set CurrentCharSaveSlot[id] = slot
       
        return false
    endfunction
   
endlibrary
Copy the folders "Codeless Save and Load" , and "SaveLoadGUI" into your map.
Save GUI
  Events
    Player - Player 1 (Red) types a chat message containing -save as An exact match
    Player - Player 2 (Blue) types a chat message containing -save as An exact match
    Player - Player 3 (Teal) types a chat message containing -save as An exact match
    Player - Player 4 (Purple) types a chat message containing -save as An exact match
    Player - Player 5 (Yellow) types a chat message containing -save as An exact match
    Player - Player 6 (Orange) types a chat message containing -save as An exact match
    Player - Player 7 (Green) types a chat message containing -save as An exact match
    Player - Player 8 (Pink) types a chat message containing -save as An exact match
    Player - Player 9 (Gray) types a chat message containing -save as An exact match
    Player - Player 10 (Light Blue) types a chat message containing -save as An exact match
    Player - Player 11 (Dark Green) types a chat message containing -save as An exact match
  Conditions
    (SavePlayerHero[((Player number of (Triggering player)) - 1)] is A Hero) Equal to True
  Actions
    -------- ------------------- --------
    -------- NOTE: You must load values in the reverse order you saved them in. This is why we save the unit type last. --------
    -------- ------------------- --------
    Set SaveTempUnit = SavePlayerHero[((Player number of (Triggering player)) - 1)]
    Set SaveCount = -1
    -------- ------------------- --------
    -------- Save Abilities --------
    -------- ------------------- --------
    For each (Integer SaveTempInt) from 0 to SaveAbilityTypeMax, do (Actions)
      Loop - Actions
        If (All Conditions are True) then do (Then Actions) else do (Else Actions)
          If - Conditions
            (Level of SaveAbilityType[SaveTempInt] for SaveTempUnit) Greater than 0
          Then - Actions
            -------- Save level of ability --------
            Set SaveCount = (SaveCount + 1)
            Set SaveValue[SaveCount] = (Level of SaveAbilityType[SaveTempInt] for SaveTempUnit)
            Set SaveMaxValue[SaveCount] = 10
            -------- Save the array index --------
            Set SaveCount = (SaveCount + 1)
            Set SaveValue[SaveCount] = SaveTempInt
            Set SaveMaxValue[SaveCount] = SaveAbilityTypeMax
          Else - Actions
    -------- Save the number of abilities the unit has --------
    Set SaveCount = (SaveCount + 1)
    Set SaveValue[SaveCount] = (SaveCount / 2)
    Set SaveMaxValue[SaveCount] = SaveAbilityTypeMax
    -------- ------------------- --------
    -------- Save Skill Points --------
    -------- ------------------- --------
    Set SaveCount = (SaveCount + 1)
    Set SaveValue[SaveCount] = (Unspent skill points of SaveTempUnit)
    Set SaveMaxValue[SaveCount] = 999
    -------- ------------------- --------
    -------- Save Items --------
    -------- ------------------- --------
    For each (Integer SaveTempInt) from 0 to 5, do (Actions)
      Loop - Actions
        Set SaveCount = (SaveCount + 1)
        Custom script: set udg_SaveValue[udg_SaveCount] = SaveCode_I2I(GetItemTypeId(UnitItemInSlot(udg_SaveTempUnit, udg_SaveTempInt)))
        Set SaveMaxValue[SaveCount] = SaveItemTypeMax
    -------- ------------------- --------
    -------- Save Attributes --------
    -------- ------------------- --------
    Set SaveCount = (SaveCount + 1)
    Set SaveValue[SaveCount] = (Strength of SaveTempUnit (Exclude bonuses))
    Set SaveMaxValue[SaveCount] = 999
    -------- ------------------- --------
    Set SaveCount = (SaveCount + 1)
    Set SaveValue[SaveCount] = (Agility of SaveTempUnit (Exclude bonuses))
    Set SaveMaxValue[SaveCount] = 999
    -------- ------------------- --------
    Set SaveCount = (SaveCount + 1)
    Set SaveValue[SaveCount] = (Intelligence of SaveTempUnit (Exclude bonuses))
    Set SaveMaxValue[SaveCount] = 999
    -------- ------------------- --------
    -------- Save Experience (%) and Level --------
    -------- ------------------- --------
    Set SaveCount = (SaveCount + 1)
    Custom script: set udg_SaveValue[udg_SaveCount] = R2I( (GetHeroXP(udg_SaveTempUnit)) / FindLevelXP(GetHeroLevel(udg_SaveTempUnit)) * 100) // percentage
    Set SaveMaxValue[SaveCount] = 100
    -------- ------------------- --------
    Set SaveCount = (SaveCount + 1)
    Set SaveValue[SaveCount] = (Hero level of SaveTempUnit)
    Set SaveMaxValue[SaveCount] = 100
    -------- ------------------- --------
    -------- Save Unit Type --------
    -------- ------------------- --------
    Set SaveCount = (SaveCount + 1)
    Custom script: set udg_SaveValue[udg_SaveCount] = SaveCode_U2I(GetUnitTypeId(udg_SaveTempUnit))
    Set SaveMaxValue[SaveCount] = SaveUnitTypeMax
    -------- ------------------- --------
    -------- Save to disk --------
    -------- ------------------- --------
    If (All Conditions are True) then do (Then Actions) else do (Else Actions)
      If - Conditions
        SaveLoad_Slot Less than 0
      Then - Actions
        Set SaveLoad_Slot = 11
      Else - Actions
    Custom script: call SaveCode_Save(GetTriggerPlayer(), udg_SaveLoad_Slot)
Load GUI
  Events
    Player - Player 1 (Red) types a chat message containing -load as A substring
    Player - Player 2 (Blue) types a chat message containing -load as A substring
    Player - Player 3 (Teal) types a chat message containing -load as A substring
    Player - Player 4 (Purple) types a chat message containing -load as A substring
    Player - Player 5 (Yellow) types a chat message containing -load as A substring
    Player - Player 6 (Orange) types a chat message containing -load as A substring
    Player - Player 7 (Green) types a chat message containing -load as A substring
    Player - Player 8 (Pink) types a chat message containing -load as A substring
    Player - Player 9 (Gray) types a chat message containing -load as A substring
    Player - Player 10 (Light Blue) types a chat message containing -load as A substring
    Player - Player 11 (Dark Green) types a chat message containing -load as A substring
    Player - Player 12 (Brown) types a chat message containing -load as A substring
  Conditions
    (Substring((Entered chat string), 1, 5)) Equal to -load
  Actions
    Set SaveTempString = (Substring((Entered chat string), 7, 999))
    Set SaveTempInt = (Integer(SaveTempString))
    If (All Conditions are True) then do (Then Actions) else do (Else Actions)
      If - Conditions
        (Length of SaveTempString) Equal to 1
      Then - Actions
        Set SaveTempInt = (SaveTempInt - 1)
      Else - Actions
        If (All Conditions are True) then do (Then Actions) else do (Else Actions)
          If - Conditions
            (Length of SaveTempString) Greater than 1
          Then - Actions
            Set SaveLoadEvent_Code = SaveTempString
            Set SaveLoadEvent_Player = (Triggering player)
            Set SaveLoadEvent = 1.00
            Set SaveLoadEvent = 0.00
            Skip remaining actions
          Else - Actions
            Set SaveTempInt = 11
    Custom script: if (not File.enabled) then
    Custom script: call DisplayTimedTextToPlayer(GetTriggerPlayer(),0,0,60000, udg_LocalFiles_WarningMessage)
    Custom script: endif
    Custom script: call SaveCode_Load(GetTriggerPlayer(), udg_SaveTempInt)
Load GUI2
  Events
    Game - SaveLoadEvent becomes Equal to 1.00
  Conditions
  Actions
    Set SaveCount = -1
    -------- ------------------- --------
    -------- NOTE: You must load values in the reverse order you saved them in --------
    -------- ------------------- --------
    -------- Validate --------
    -------- ------------------- --------
    Custom script: set udg_SaveTempInt = integer(Savecode.create())
    Custom script: if not (Savecode(udg_SaveTempInt).Load(udg_SaveLoadEvent_Player, udg_SaveLoadEvent_Code, 1)) then
    Game - Display to (Player group(SaveLoadEvent_Player)) the text: Invalid load code (check your name).
    Skip remaining actions
    Custom script: endif
    Custom script: call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "Loaded " + User[udg_SaveLoadEvent_Player].nameColored + "'s character in " + R2S(udg_SaveTempReal) + " seconds!")
    -------- ------------------- --------
    -------- Load Hero --------
    -------- ------------------- --------
    Unit - Remove SavePlayerHero[((Player number of SaveLoadEvent_Player) - 1)] from the game
    -------- ------------------- --------
    Set SaveCount = (SaveCount + 1)
    Set SaveMaxValue[SaveCount] = SaveUnitTypeMax
    Custom script: call SaveCode_LoadNextValue()
    -------- ------------------- --------
    If (All Conditions are True) then do (Then Actions) else do (Else Actions)
      If - Conditions
        (SaveUnitType[SaveValue[SaveCount]] is A Hero) Equal to True
        SaveHeroName Equal to True
      Then - Actions
        Custom script: set udg_SaveTempUnit = SaveCode_LoadHeroWithProperName(udg_SaveLoadEvent_Player, udg_SaveUnitType[udg_SaveValue[udg_SaveCount]], 0, 0, 0)
      Else - Actions
        Unit - Create 1 SaveUnitType[SaveValue[SaveCount]] for SaveLoadEvent_Player at (Center of (Playable map area)) facing Default building facing degrees
        Set SaveTempUnit = (Last created unit)
    Set SavePlayerHero[((Player number of SaveLoadEvent_Player) - 1)] = SaveTempUnit
    Selection - Select SaveTempUnit for SaveLoadEvent_Player
    -------- ------------------- --------
    -------- Load Experience and Level --------
    -------- ------------------- --------
    Set SaveCount = (SaveCount + 1)
    Set SaveMaxValue[SaveCount] = 100
    Custom script: call SaveCode_LoadNextValue()
    -------- ------------------- --------
    Set SaveCount = (SaveCount + 1)
    Set SaveMaxValue[SaveCount] = 100
    Custom script: call SaveCode_LoadNextValue()
    Custom script: set udg_SaveTempReal = FindLevelXP(udg_SaveValue[udg_SaveCount-1])
    Hero - Set SaveTempUnit experience to (Integer((((Real(SaveValue[SaveCount])) / 100.00) x SaveTempReal))), Hide level-up graphics
    If (All Conditions are True) then do (Then Actions) else do (Else Actions)
      If - Conditions
        (Hero level of SaveTempUnit) Not equal to SaveValue[(SaveCount - 1)]
      Then - Actions
        Hero - Set SaveTempUnit Hero-level to SaveValue[(SaveCount - 1)], Hide level-up graphics
      Else - Actions
    -------- ------------------- --------
    -------- Load Attributes --------
    -------- ------------------- --------
    Set SaveCount = (SaveCount + 1)
    Set SaveMaxValue[SaveCount] = 999
    Custom script: call SaveCode_LoadNextValue()
    Hero - Modify Intelligence of SaveTempUnit: Set to SaveValue[SaveCount]
    -------- ------------------- --------
    Set SaveCount = (SaveCount + 1)
    Set SaveMaxValue[SaveCount] = 999
    Custom script: call SaveCode_LoadNextValue()
    Hero - Modify Agility of SaveTempUnit: Set to SaveValue[SaveCount]
    -------- ------------------- --------
    Set SaveCount = (SaveCount + 1)
    Set SaveMaxValue[SaveCount] = 999
    Custom script: call SaveCode_LoadNextValue()
    Hero - Modify Strength of SaveTempUnit: Set to SaveValue[SaveCount]
    -------- ------------------- --------
    -------- Load Items --------
    -------- ------------------- --------
    For each (Integer A) from 0 to 5, do (Actions)
      Loop - Actions
        Set SaveCount = (SaveCount + 1)
        Set SaveMaxValue[SaveCount] = SaveItemTypeMax
        Custom script: call SaveCode_LoadNextValue()
        Hero - Create SaveItemType[SaveValue[SaveCount]] and give it to SaveTempUnit
    -------- ------------------- --------
    -------- Load Skill Points --------
    -------- ------------------- --------
    Set SaveCount = (SaveCount + 1)
    Set SaveMaxValue[SaveCount] = 999
    Custom script: call SaveCode_LoadNextValue()
    Hero - Modify unspent skill points of SaveTempUnit: Set to 0 points
    Hero - Modify unspent skill points of SaveTempUnit: Set to (SaveValue[SaveCount] - (Unspent skill points of SaveTempUnit)) points
    -------- ------------------- --------
    -------- Load Abilities --------
    -------- ------------------- --------
    Set SaveCount = (SaveCount + 1)
    Set SaveMaxValue[SaveCount] = SaveAbilityTypeMax
    Custom script: call SaveCode_LoadNextValue()
    Set SaveTempReal = (Real(SaveValue[SaveCount]))
    For each (Integer A) from 0 to (Integer(SaveTempReal)), do (Actions)
      Loop - Actions
        Set SaveCount = (SaveCount + 1)
        Set SaveMaxValue[SaveCount] = SaveAbilityTypeMax
        Custom script: call SaveCode_LoadNextValue()
        Unit - Add SaveAbilityType[SaveValue[SaveCount]] to SaveTempUnit
        Set SaveCount = (SaveCount + 1)
        Set SaveMaxValue[SaveCount] = 10
        Custom script: call SaveCode_LoadNextValue()
        Unit - Set level of SaveAbilityType[SaveValue[(SaveCount - 1)]] for SaveTempUnit to SaveValue[SaveCount]
    Custom script: call Savecode(udg_SaveTempInt).destroy()
Auto Save
  Events
    Time - Every 10.00 seconds of game time
  Conditions
  Actions
    Player Group - Pick every player in (All players controlled by a User player) and do (Actions)
      Loop - Actions
        If (All Conditions are True) then do (Then Actions) else do (Else Actions)
          If - Conditions
            ((Picked player) slot status) Equal to Is playing
          Then - Actions
            Custom script: call SaveCharToSlot(udg_SavePlayerHero[GetPlayerId(GetEnumPlayer())], 10)
          Else - Actions
library SampleSavevJass initializer Init requires SaveHelper, FileIO

    // uses GUI variables
   
    private function Actions takes nothing returns boolean
        local integer i = 0
        local integer l = 0
        local integer c = 0
        local Savecode savecode = Savecode.create()
        local string s
        local player p = GetTriggerPlayer()
        local integer pid = GetPlayerId(p)
        local unit u = udg_SavePlayerHero[pid]
        local integer uidSmall = SaveCode_U2I(GetUnitTypeId(u))
        local string fname = "CharVJASS"
       
        // save abilities
        loop
            exitwhen i > udg_SaveAbilityTypeMax
            set l = GetUnitAbilityLevel(u, udg_SaveAbilityType[i])
           
            if (l > 0) then
                call savecode.Encode(l, 10)
                call savecode.Encode(i, udg_SaveAbilityTypeMax)
               
                set c = c + 1
            endif
           
            set i = i + 1
        endloop
       
        // save amount of skills and skill points
        call savecode.Encode(c, udg_SaveAbilityTypeMax)
        call savecode.Encode(GetHeroSkillPoints(u), 999)

        // save items
        set i = 0
        loop
            exitwhen i > 5
            set l = GetItemTypeId(UnitItemInSlot(u, i))
           
            call savecode.Encode(SaveCode_I2I(l), udg_SaveItemTypeMax)
           
            set i = i + 1
        endloop
       
        // save stats
        call savecode.Encode(GetHeroStr(u, false), 999)
        call savecode.Encode(GetHeroAgi(u, false), 999)
        call savecode.Encode(GetHeroInt(u, false), 999)
       
        // save xp
        call savecode.Encode(R2I( (GetHeroXP(u)) / FindLevelXP(GetHeroLevel(u)) * 100), 100) // percentage
        call savecode.Encode(GetHeroLevel(u), 100)
        call savecode.Encode(uidSmall, udg_SaveUnitTypeMax)
       
        // generate code and save to disk
        set s = savecode.Save(p, 1)
       
        if (GetLocalPlayer() == p) then
            if (udg_SaveHeroName) then
                call GameData.write(fname, GetObjectName(udg_SaveUnitType[udg_SaveValue[udg_SaveCount]]) + " (" + GetHeroProperName(udg_SavePlayerHero[pid]) + ")|n" + s)
            else
                call GameData.write(fname, GetObjectName(udg_SaveUnitType[udg_SaveValue[udg_SaveCount]]) + "|n" + s)
            endif
        endif
       
        call DisplayTimedTextToPlayer(p, 0, 0, 0, Savecode_colorize(s))
       
        set u = null
       
        return false
    endfunction
   
    private function Init takes nothing returns nothing
        local integer i = 0
        local trigger t = CreateTrigger()
       
        loop
            exitwhen i > 11
           
            call TriggerRegisterPlayerChatEvent(t, Player(i), "-save", true )
           
            set i = i + 1
        endloop
       
        call TriggerAddCondition(t, Filter(function Actions))
    endfunction

endlibrary
library SampleLoadvJass initializer Init requires Sync, SaveHelper, PlayerUtils
   
    function LoadHeroFromString takes player p, string s returns nothing
        local unit u
        local integer array values
        local integer i = GetPlayerId(p)
       
        local Savecode loadcode
       
        set loadcode = Savecode.create()
       
        // check the code
        if (not loadcode.Load(p, s, 1)) then
            if (s != "") then
                call DisplayTextToPlayer(p, 0 , 0, "invalid code: " + s)
            endif
            return
        endif

        // display success
        call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "Loaded " +  User(i).nameColored + "'s character")

        // remove old hero
        call RemoveUnit(udg_SavePlayerHero[i])
        set udg_SavePlayerHero[i] = null
       
        // create new one
        set u = CreateUnit(p, udg_SaveUnitType[loadcode.Decode(udg_SaveUnitTypeMax)], 0, 0, 0)
        set udg_SavePlayerHero[i] = u
       
        // load XP %
        set values[0] = loadcode.Decode(100)
        set values[1] = loadcode.Decode(100)
        set values[2] = R2I((values[1] / 100.) * FindLevelXP(values[0]))

        call SetHeroXP(u, values[2], false)
       
        // load hero level
        if (GetHeroLevel(u) != values[0]) then
            call SetHeroLevel(u, values[0], false)
        endif
       
        // attributes
        call SetHeroInt(u, loadcode.Decode(999), false)
        call SetHeroAgi(u, loadcode.Decode(999), false)
        call SetHeroStr(u, loadcode.Decode(999), false)
       
        // items
        set i=0
        loop
            exitwhen i > 5
            call UnitAddItemToSlotById(u, udg_SaveItemType[loadcode.Decode(udg_SaveItemTypeMax)], 5-i)
            set i = i + 1
        endloop
       
        // skill points
        set values[0] = loadcode.Decode(999)
        call UnitModifySkillPoints(u, 0)
        call UnitModifySkillPoints(u, values[0] - GetHeroSkillPoints(u))
       
        // abilities
        set values[1] = loadcode.Decode(udg_SaveAbilityTypeMax)
        set i = 0
        loop
            exitwhen i == values[1]
            set values[0] = udg_SaveAbilityType[loadcode.Decode(udg_SaveAbilityTypeMax)]
            call UnitAddAbility(u, values[0])
            call SetUnitAbilityLevel(u, values[0], loadcode.Decode(10))
            set i = i + 1
        endloop
       
        if (GetLocalPlayer() == p) then
            call ClearSelection()
            call SelectUnit(u, true)
        endif
       
        call DisplayTextToPlayer(p, 0, 0, "|cff009f00Character loaded!|r")
    endfunction
   
    private function OnReceiveCode takes nothing returns boolean
        local SyncData d = GetSyncedData()
        call LoadHeroFromString(d.from, d.readString(0))
        call d.destroy()
        return false
    endfunction
   
    private function Actions takes nothing returns boolean
        local player p = GetTriggerPlayer()
        local string s = SubString(GetEventPlayerChatString(), 0, 6)

        local SyncData data
       
        if (s == "-load ") then
            set s = SubString(GetEventPlayerChatString(), 6, 999)
            if (S2I(s) > 0) then
                set s = SaveCode_ReadSlot(p, S2I(s)-1)
               
                set data = SyncData.create(p)
               
                call data.addString(s, 64)
                call data.addEventListener(Filter(function OnReceiveCode))
                call data.start()
            else
                set s = SubString(GetEventPlayerChatString(), 6, 999)
               
                call LoadHeroFromString(p, s)
            endif
        endif
       
       
        return false
    endfunction
   
    private function Init takes nothing returns nothing
        local trigger t = CreateTrigger()
        local integer i = 0
       
        loop
            exitwhen i > 11
           
            call TriggerRegisterPlayerChatEvent(t, Player(i), "-load", false )
           
            set i = i + 1
        endloop
       
        call TriggerAddCondition(t, Filter(function Actions))
    endfunction

endlibrary
library SampleDialogSystem initializer Init requires SaveHelper
   
    globals
        private dialog array Dialog
        private button array Button
        private button array PlayerButton[25][10]
       
        private trigger T=CreateTrigger()
    endglobals
   
    private function GetTitle takes player p, integer slot returns string
        local string s = GetSaveSlotTitle(p, slot)
        if (s == null or s == "") then
            return "Empty"
        endif
        return s
    endfunction
   
    private function OnButtonClick takes nothing returns boolean
        local button b = GetClickedButton()
        local player p = GetTriggerPlayer()
        local string s = ""
        local integer pid = GetPlayerId(p)
        local boolean safe = MemoryHacksEnabled() or (File.enabled and GetLocalPlayer() == p)
        local User u = User(pid)
       
        if (b == Button[(u.id*12)+0]) then // save char
            call DialogClear(Dialog[(u.id*12)+2])
            call DialogSetMessage(Dialog[(u.id*12)+2], "|cffff8000Save Character|r")
           
            if (not safe) then
                call ClearTextMessages()
                call DisplayTimedTextToPlayer(p, 0, 0, 60000, udg_LocalFiles_WarningMessage)
            endif
           
            set PlayerButton[pid][10] = DialogAddButton(Dialog[(u.id*12)+2], "|cffffcc00Slot #1 - |cffffffff" + GetTitle(p, 0) + "|r", 0)
            set PlayerButton[pid][11] = DialogAddButton(Dialog[(u.id*12)+2], "|cffffcc00Slot #2 - |cffffffff" + GetTitle(p, 1), 0)
            set PlayerButton[pid][12] = DialogAddButton(Dialog[(u.id*12)+2], "|cffffcc00Slot #3 - |cffffffff" + GetTitle(p, 2), 0)
            set PlayerButton[pid][13] = DialogAddButton(Dialog[(u.id*12)+2], "|cffffcc00Slot #4 - |cffffffff" + GetTitle(p, 3), 0)
            set PlayerButton[pid][14] = DialogAddButton(Dialog[(u.id*12)+2], "|cffffcc00Slot #5 - |cffffffff" + GetTitle(p, 4), 0)
           
            call DialogAddButton(Dialog[(u.id*12)+2], "|cffff8000Close", 0)
            call DialogDisplay(GetLocalPlayer(), Dialog[(u.id*12)+2], safe)
        elseif(b == Button[(u.id*12)+1]) then // load char
            if (not safe) then
                call ClearTextMessages()
                call ClearTextMessages()
                call DisplayTimedTextToPlayer(p, 0, 0, 60000, udg_LocalFiles_WarningMessage)
            endif
       
            call DialogClear(Dialog[(u.id*12)+1])
            call DialogSetMessage(Dialog[(u.id*12)+1], "|cffff8000Load Character|r")
           
            set PlayerButton[pid][20] = DialogAddButton(Dialog[(u.id*12)+1], "|cffffcc00Autosave - |cffffffff" + GetTitle(p, 10), 0)
            set PlayerButton[pid][0] = DialogAddButton(Dialog[(u.id*12)+1], "|cffffcc00Slot #1 - |cffffffff" + GetTitle(p, 0), 0)
            set PlayerButton[pid][1] = DialogAddButton(Dialog[(u.id*12)+1], "|cffffcc00Slot #2 - |cffffffff" + GetTitle(p, 1), 0)
            set PlayerButton[pid][2] = DialogAddButton(Dialog[(u.id*12)+1], "|cffffcc00Slot #3 - |cffffffff" + GetTitle(p, 2), 0)
            set PlayerButton[pid][3] = DialogAddButton(Dialog[(u.id*12)+1], "|cffffcc00Slot #4 - |cffffffff" + GetTitle(p, 3), 0)
            set PlayerButton[pid][4] = DialogAddButton(Dialog[(u.id*12)+1], "|cffffcc00Slot #5 - |cffffffff" + GetTitle(p, 4), 0)
           
            call DialogAddButton(Dialog[(u.id*12)+1], "|cffff8000Close", 0)
            call DialogDisplay(p, Dialog[(u.id*12)+1], safe)
        elseif(b == Button[(u.id*12)+2]) then // load char
            if (not safe) then
                call ClearTextMessages()
                call DisplayTimedTextToPlayer(p,0,0,60000, udg_LocalFiles_WarningMessage)
            endif
           
            call DialogClear(Dialog[(u.id*12)+3])
            call DialogSetMessage(Dialog[(u.id*12)+3], "|cffff8000Delete Character|r")
           
            set PlayerButton[pid][5] = DialogAddButton(Dialog[(u.id*12)+3], "|cffff0000Delete - |cffffffff" + GetTitle(p, 0), 0)
            set PlayerButton[pid][6] = DialogAddButton(Dialog[(u.id*12)+3], "|cffff0000Delete - |cffffffff" + GetTitle(p, 1), 0)
            set PlayerButton[pid][7] = DialogAddButton(Dialog[(u.id*12)+3], "|cffff0000Delete - |cffffffff" + GetTitle(p, 2), 0)
            set PlayerButton[pid][8] = DialogAddButton(Dialog[(u.id*12)+3], "|cffff0000Delete - |cffffffff" + GetTitle(p, 3), 0)
            set PlayerButton[pid][9] = DialogAddButton(Dialog[(u.id*12)+3], "|cffff0000Delete - |cffffffff" + GetTitle(p, 4), 0)
           
            call DialogAddButton(Dialog[(u.id*12)+3], "|cffff8000Close", 0)
            call DialogDisplay(p, Dialog[(u.id*12)+3], safe)
        elseif(b == PlayerButton[pid][0]) then
            call LoadSaveSlot(p, 0)
        elseif(b == PlayerButton[pid][1]) then
            call LoadSaveSlot(p, 1)
        elseif(b == PlayerButton[pid][2]) then
            call LoadSaveSlot(p, 2)
        elseif(b == PlayerButton[pid][3]) then
            call LoadSaveSlot(p, 3)
        elseif(b == PlayerButton[pid][4]) then
            call LoadSaveSlot(p, 4)
        elseif(b == PlayerButton[pid][20]) then
            call LoadSaveSlot(p, 10)
        elseif(b == PlayerButton[pid][5]) then
            call DeleteCharSlot(p, 0)
        elseif(b == PlayerButton[pid][6]) then
            call DeleteCharSlot(p, 1)
        elseif(b == PlayerButton[pid][7]) then
            call DeleteCharSlot(p, 2)
        elseif(b == PlayerButton[pid][8]) then
            call DeleteCharSlot(p, 3)
        elseif(b == PlayerButton[pid][9]) then
            call DeleteCharSlot(p, 4)
        elseif(b == PlayerButton[pid][10]) then
            call SaveCharToSlot(udg_SavePlayerHero[pid], 0)
        elseif(b == PlayerButton[pid][11]) then
            call SaveCharToSlot(udg_SavePlayerHero[pid], 1)
        elseif(b == PlayerButton[pid][12]) then
            call SaveCharToSlot(udg_SavePlayerHero[pid], 2)
        elseif(b == PlayerButton[pid][13]) then
            call SaveCharToSlot(udg_SavePlayerHero[pid], 3)
        elseif(b == PlayerButton[pid][14]) then
            call SaveCharToSlot(udg_SavePlayerHero[pid], 4)
        endif
       
        return false
    endfunction
   
    private function ShowMenu takes nothing returns boolean
        local player p = GetTriggerPlayer()
        local integer i = GetPlayerId(p)
       
        if (GetLocalPlayer() == p) then
            call DialogSetMessage(Dialog[(i*12)+0], "|cffff8000Main Menu|r")
            call DialogDisplay(p, Dialog[(i*12)+0], true)
        endif
       
        return false
    endfunction
   
    private function InitDialog takes nothing returns nothing
        local integer i = 0
        local trigger t = CreateTrigger()
        local User u
       
        loop
            exitwhen i == User.AmountPlaying
           
            set u = User.fromPlaying(i)
           
            set Dialog[(u.id*12)+0] = DialogCreate()
            set Button[(u.id*12)+0] = DialogAddButton(Dialog[(u.id*12)+0], "|cffffcc00Save Character", 0)
            set Button[(u.id*12)+1] = DialogAddButton(Dialog[(u.id*12)+0], "|cffffcc00Load Character", 0)
            set Button[(u.id*12)+2] = DialogAddButton(Dialog[(u.id*12)+0], "|cffffcc00Delete Character", 0)
            call DialogAddButton(Dialog[(u.id*12)+0], "|cffff8000Close", 0)

            set Dialog[(u.id*12)+1] = DialogCreate()
            set Dialog[(u.id*12)+2] = DialogCreate()
            set Dialog[(u.id*12)+3] = DialogCreate()
       
            call TriggerRegisterDialogEvent(t, Dialog[(u.id*12)+0])
            call TriggerRegisterDialogEvent(t, Dialog[(u.id*12)+1])
            call TriggerRegisterDialogEvent(t, Dialog[(u.id*12)+2])
            call TriggerRegisterDialogEvent(t, Dialog[(u.id*12)+3])
       
            set i = i + 1
        endloop
       
        call TriggerAddCondition(t, Filter(function OnButtonClick))
    endfunction
   
    private function Init takes nothing returns nothing
        local trigger t  = CreateTrigger()
        local integer i  = 0
       
        loop
            set CurrentCharSaveSlot[i] = -1
            call TriggerRegisterPlayerEvent(t, Player(i), EVENT_PLAYER_END_CINEMATIC)
            set i = i + 1
           
            exitwhen i == bj_MAX_PLAYER_SLOTS
        endloop
       
        call TriggerAddCondition(t, Filter(function ShowMenu))

        call TimerStart(CreateTimer(), 0, false, function InitDialog)
    endfunction

endlibrary
library Sync requires SyncInteger, optional PlayerUtils
/***************************************************************
*
*   v1.2.5, by TriggerHappy
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*
*   This library allows you to quickly synchronize async data such as,
*   camera position, or a the contents of a local file, to all players
*   in the map by using the game cache.
*
*   Full Documentation: -http://www.hiveworkshop.com/forums/pastebin.php?id=p4f84s
*
*   _________________________________________________________________________
*   1. Installation
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*   Copy the script to your map and save it (requires JassHelper *or* JNGP)
*
*       SyncInteger: https://www.hiveworkshop.com/threads/syncinteger.278674/
*       PlayerUtils: https://www.hiveworkshop.com/threads/playerutils.278559/
*   _________________________________________________________________________
*   2. API
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*       struct SyncData
*
*           method start takes nothing returns nothing
*           method startChunk takes integer i, integer end returns nothing
*           method refresh takes nothing returns nothing
*           method destroy takes nothing returns nothing
*
*           method addInt takes integer i returns nothing
*           method addReal takes integer i returns nothing
*           method addString takes string s, integer len returns nothing
*           method addBool takes booleanflag returns nothing
*
*           method readInt takes integer index returns integer
*           method readReal takes integer index returns integer
*           method readString takes integer index returns string
*           method readBool takes integer index returns boolean
*
*           method hasInt takes integer index returns boolean
*           method hasReal takes integer index returns boolean
*           method hasString takes integer index returns boolean
*           method hasBool takes integer index returns boolean
*
*           method isPlayerDone takes player p returns boolean
*           method isPlayerIdDone takes integer pid returns boolean
*
*           method addEventListener takes filterfunc func returns nothing
*
*           ---------
*
*           filterfunc onComplete
*           filterfunc onError
*           filterfunc onUpdate
*           trigger trigger
*
*           readonly player from
*
*           readonly real timeStarted
*           readonly real timeFinished
*           readonly real timeElapsed
*
*           readonly integer intCount
*           readonly integer boolCount
*           readonly integer strCount
*           readonly integer realCount
*           readonly integer playersDone
*
*           readonly boolean buffering
*
*           readonly static integer last
*           readonly static player LocalPlayer
*           readonly static boolean Initialized
*
*           static method create takes player from returns SyncData
*           static method destroy takes nothing returns nothing
*           static method gameTime takes nothing returns real
*
*       function GetSyncedData takes nothing returns SyncData
*
***************************************************************/


    globals
        // characters that can be synced (ascii)
        private constant string ALPHABET                    = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"

        // safe characters for use in game cache keys
        // (case sensitive)
        private constant string SAFE_KEYS                   = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`{|}~"

        // how fast the buffer updates
        private constant real UPDATE_PERIOD                 = 0.03125

        // automatically recycle indices when the syncing player leaves
        private constant boolean AUTO_DESTROY_ON_LEAVE      = true

        // automatically stop buffering when an error occurs
        private constant boolean STOP_BUFFERING_ON_ERROR    = true

        // preload game cache key strings on init
        private constant boolean PRELOAD_STR_CACHE          = true

        // size of the alphabet
        private constant integer ALPHABET_BASE              = StringLength(ALPHABET)

        // stop reading the string buffer when reaching this char
        private constant string TERM_CHAR                   = "`"

        // maximum number of strings *per instance*
        private constant integer MAX_STRINGS                = 8192

        // filenames for gc (short names have faster sync time)
        private constant string CACHE_FILE                  = "i.w3v"
        private constant string CACHE_FILE_STR              = "s.w3v"

        // don't edit below this line
        constant integer EVENT_SYNC_CACHE       = 3
        constant integer SYNC_ERROR_TIMEOUT     = 1
        constant integer SYNC_ERROR_PLAYERLEFT  = 2
       
        private SelectionSync Synchronizer
    endglobals

    //**************************************************************

    function GetSyncedData takes nothing returns SyncData
        return SyncData(SyncData.Last)
    endfunction

    public function I2Char takes string alphabet, integer i returns string
        return SubString(alphabet, i, i + 1)
    endfunction

    public function Char2I takes string alphabet, string c returns integer
        local integer i = 0
        local string s
        local integer l = StringLength(alphabet)
        loop
            set s = I2Char(alphabet, i)
            exitwhen i == l
            if (s == c) then
                return i
            endif
            set i = i + 1
        endloop
        return 0
    endfunction

    public function ConvertBase takes string alphabet, integer i returns string
        local integer b
        local string s = ""
        local integer l = StringLength(alphabet)
        if i < l then
            return I2Char(alphabet, i)
        endif
        loop
            exitwhen i <= 0
            set b = i - ( i / l ) * l
            set s = I2Char(alphabet, b) + s
            set i = i / l
        endloop
        return s
    endfunction

    public function PopulateString takes string s, integer makeLen returns string
        local integer i = 0
        local integer l = StringLength(s)
        if (l == makeLen) then
            return s
        endif
        set l = makeLen-l
        loop
            exitwhen i > l
            set s = s + TERM_CHAR
            set i = i + 1
        endloop
        return s
    endfunction

    //**************************************************************

    globals
        // string table keys
        private constant integer KEY_STR_POS = (0*MAX_STRINGS)
        private constant integer KEY_STR_LEN = (1*MAX_STRINGS)

        // pending data storage space
        private constant integer KEY_STR_CACHE = (2*MAX_STRINGS)
    endglobals

    struct SyncData

        real timeout
        filterfunc onComplete
        filterfunc onError
        filterfunc onUpdate
        trigger trigger

        readonly integer lastError

        readonly player from

        readonly real timeStarted
        readonly real timeFinished
        readonly real timeElapsed

        readonly integer intCount
        readonly integer boolCount
        readonly integer strCount
        readonly integer realCount
        readonly integer playersDone

        readonly boolean buffering

        readonly static boolean Initialized = false
        readonly static integer Last        = 0
        readonly static player LocalPlayer
        readonly static integer LocalPlayerID

        private static integer Running   = 0
        private static real timeCounter  = 0.00
        private static trigger EventTrig = CreateTrigger()

        private static hashtable Table
        private static hashtable CharTable
        private static gamecache array Cache
        private static integer array PendingCount
        private static timer Elapsed
        private static timer BufferTimer
        private static integer AlphaHash
     
        private integer strBufferLen
        private trigger eventTrig
        private string mkey
        private boolean localFinished

        private thistype next
        private thistype prev
     
        static method bool2I takes boolean b returns integer
            if (b) then
                return 1
            endif
            return 0
        endmethod
     
        private static method hashString takes string c returns integer
            return StringHash(I2S(bool2I(StringCase(c, true) == c)) + c)
        endmethod

        static method char2I takes string alphabet, string c returns integer // requires preloading table with data
            return LoadInteger(SyncData.CharTable, .AlphaHash, .hashString(c))
        endmethod

        private method resetVars takes nothing returns nothing
            set this.intCount       = 0
            set this.strCount       = 0
            set this.boolCount      = 0
            set this.realCount      = 0
            set this.playersDone    = 0
            set this.strBufferLen   = 0
            set this.timeStarted    = 0
            set this.timeFinished   = 0
            set this.lastError      = 0
            set this.onComplete     = null
            set this.onError        = null
            set this.onUpdate       = null
            set this.timeout        = 0.00
            set this.buffering      = false
            set this.localFinished  = false
            set this.trigger        = null
        endmethod

        private static method getKey takes integer pos returns string
            local string position=""
   
            if (HaveSavedString(Table, KEY_STR_CACHE, pos)) then
                return LoadStr(Table, KEY_STR_CACHE, pos)
            endif
   
            set position = ConvertBase(SAFE_KEYS, pos)
            call SaveStr(Table, KEY_STR_CACHE, pos, position)
   
            return position
        endmethod

        static method create takes player from returns thistype
            local thistype this

            // Player has to be playing because of GetLocalPlayer use.
            if (GetPlayerController(from) != MAP_CONTROL_USER or GetPlayerSlotState(from) != PLAYER_SLOT_STATE_PLAYING) then
                return 0
            endif

            set this = thistype.allocate()
 
            set this.from   = from
            set this.mkey   = getKey(this-1)

            call this.resetVars()

            set thistype(0).next.prev = this
            set this.next = thistype(0).next
            set thistype(0).next = this

            set this.prev = 0

            return this
        endmethod

        method refresh takes nothing returns nothing
            local integer i = 0
            local integer p = 0
       
            loop
                static if (LIBRARY_PlayerUtils) then
                    exitwhen i == User.AmountPlaying
                    set p = User.fromPlaying(i).id
                else
                    exitwhen i == bj_MAX_PLAYER_SLOTS
                    set p = i
                endif

                call RemoveSavedInteger(Table, this, KEY_STR_POS + p)
                call RemoveSavedInteger(Table, this, KEY_STR_LEN + p)
                call RemoveSavedBoolean(Table, p, this) // playerdone

                set i = i + 1
            endloop

            call FlushStoredMission(Cache[0], this.mkey)
            call FlushStoredMission(Cache[1], this.mkey)

            call this.resetVars()
        endmethod

        method destroy takes nothing returns nothing
            if (this.eventTrig != null) then
                call DestroyTrigger(this.eventTrig)
                set this.eventTrig=null
            endif

            call this.refresh()

            set this.next.prev = this.prev
            set this.prev.next = this.next

            call this.deallocate()
        endmethod

        method hasInt takes integer index returns boolean
            return HaveStoredInteger(Cache[0], this.mkey, getKey(index))
        endmethod

        method hasReal takes integer index returns boolean
            return HaveStoredReal(Cache[0], this.mkey, getKey(index))
        endmethod

        method hasBool takes integer index returns boolean
            return HaveStoredBoolean(Cache[0], this.mkey, getKey(index))
        endmethod

        method hasString takes integer index returns boolean
            local integer i = LoadInteger(Table, this, KEY_STR_POS+index)
            if (index > 0 and i == 0) then
                return false
            endif
            return HaveStoredInteger(Cache[1], this.mkey, getKey(i + LoadInteger(Table, this, KEY_STR_LEN+index)))
        endmethod

        method addInt takes integer i returns nothing
            local string position=getKey(intCount)
   
            if (LocalPlayer == this.from) then
                call StoreInteger(Cache[0], this.mkey, position, i)
            endif
   
            set intCount=intCount+1
        endmethod

        method addReal takes real i returns nothing
            local string position=getKey(realCount)
   
            if (LocalPlayer == this.from) then
                call StoreReal(Cache[0], this.mkey, position, i)
            endif
   
            set realCount=realCount+1
        endmethod

        method addBool takes boolean flag returns nothing
            local string position=getKey(boolCount)
   
            if (LocalPlayer == this.from) then
                call StoreBoolean(Cache[0], this.mkey, position, flag)
            endif
   
            set boolCount=boolCount+1
        endmethod

        // SyncStoredString doesn't work
        method addString takes string s, integer length returns nothing
            local string position
            local integer i = 0
            local integer strPos = 0
            local integer strLen = 0

            if (StringLength(s) < length) then
                set s = PopulateString(s, length)
            endif
 
            // store the string position in the table
            if (strCount == 0) then
                call SaveInteger(Table, this, KEY_STR_POS, 0)
            else
                set strLen = LoadInteger(Table, this, KEY_STR_LEN + (strCount-1)) + 1
                set strPos = LoadInteger(Table, this, KEY_STR_POS + (strCount-1)) + strLen

                call SaveInteger(Table, this, KEY_STR_POS + strCount, strPos)
            endif

            // convert each character in the string to an integer
            loop
                exitwhen i > length

                set position = getKey(strPos + i)

                if (LocalPlayer == this.from) then
                    call StoreInteger(Cache[1], this.mkey, position, .char2I(ALPHABET, SubString(s, i, i + 1)))
                endif

                set i = i + 1
            endloop

            set strBufferLen = strBufferLen + length
            call SaveInteger(Table, this, KEY_STR_LEN+strCount, length) // store the length as well
            set strCount=strCount+1
        endmethod

        method readInt takes integer index returns integer
            return GetStoredInteger(Cache[0], this.mkey, getKey(index))
        endmethod

        method readReal takes integer index returns real
            return GetStoredReal(Cache[0], this.mkey, getKey(index))
        endmethod

        method readBool takes integer index returns boolean
            return GetStoredBoolean(Cache[0], this.mkey, getKey(index))
        endmethod

        method readString takes integer index returns string
            local string s = ""
            local string c
            local integer i = 0
            local integer strLen = LoadInteger(Table, this, KEY_STR_LEN+index)
            local integer strPos
   
            if (not hasString(index)) then
                return null
            endif

            set strLen = LoadInteger(Table, this, KEY_STR_LEN+index)
            set strPos = LoadInteger(Table, this, KEY_STR_POS+index)
   
            loop
                exitwhen i > strLen
       
                set c = I2Char(ALPHABET, GetStoredInteger(Cache[1], this.mkey, getKey(strPos + i)))

                if (c == TERM_CHAR) then
                    return s
                endif

                set s = s + c
                set i = i + 1
            endloop

            return s
        endmethod

        private method fireListeners takes nothing returns nothing
            set Last = this

            if (this.eventTrig != null) then
                call TriggerEvaluate(this.eventTrig)
            endif

            if (this.trigger != null and TriggerEvaluate(this.trigger)) then
                call TriggerExecute(this.trigger)
            endif
        endmethod

        private method fireEvent takes filterfunc func returns nothing
            set Last = this

            call TriggerAddCondition(EventTrig, func)
            call TriggerEvaluate(EventTrig)
            call TriggerClearConditions(EventTrig)
        endmethod

        method addEventListener takes filterfunc func returns nothing
            if (this.eventTrig == null) then
                set this.eventTrig = CreateTrigger()
            endif
            call TriggerAddCondition(this.eventTrig, func)
        endmethod

        public static method gameTime takes nothing returns real
            return timeCounter + TimerGetElapsed(Elapsed)
        endmethod

        private method error takes integer errorId returns nothing
            set this.lastError = errorId

            if (this.onError != null) then
                call this.fireEvent(this.onError)
            endif

            call this.fireListeners()

            static if (STOP_BUFFERING_ON_ERROR) then
                set this.buffering = false
            endif
        endmethod

        private static method readBuffer takes nothing returns nothing
            local boolean b = true
            local integer i = 0
            local thistype data = thistype(0).next

            loop
                exitwhen data == 0

                // find the nearest instance that is still buffering
                loop
                    exitwhen data.buffering or data == 0
                    set data=data.next
                endloop

                // if none are found, exit
                if (not data.buffering) then
                    return
                endif

                set data.timeElapsed = data.timeElapsed + UPDATE_PERIOD

                if (data.onUpdate != null) then
                    call data.fireEvent(data.onUpdate)
                endif

                if (data.timeout > 0 and data.timeElapsed > data.timeout) then
                    call data.error(SYNC_ERROR_TIMEOUT)
                endif

                // if the player has left, destroy the instance
                if (GetPlayerSlotState(data.from) != PLAYER_SLOT_STATE_PLAYING) then
                    call data.error(SYNC_ERROR_PLAYERLEFT)
                    static if (AUTO_DESTROY_ON_LEAVE) then
                        call data.destroy()
                    endif
                endif

                set b = true

                // make sure all integers have been synced
                if (data.intCount > 0 and  not data.hasInt(data.intCount-1)) then
                    set b = false
                endif

                // make sure all reals have been synced
                if (data.realCount > 0 and not data.hasReal(data.realCount-1)) then
                    set b = false
                endif

                // check strings too
                if (data.strCount > 0 and not data.hasString(data.strCount-1)) then
                    set b = false
                endif

                // and booleans
                if (data.boolCount > 0 and not data.hasBool(data.boolCount-1)) then
                    set b = false
                endif

                // if everything has been synced
                if (b) then

                    if (not data.localFinished) then // async
                        set data.localFinished = true

                        // notify everyone that the local player has recieved all of the data
                        call Synchronizer.syncValue(LocalPlayer, data)
                    endif
               
                endif

                set data = data.next
            endloop
        endmethod

        method startChunk takes integer i, integer end returns nothing
            local integer n = 0
            local integer j = 0
            local integer p = 0
            local string position

            // Begin syncing
            loop
                exitwhen i > end

                set position = LoadStr(Table, KEY_STR_CACHE, i)
     
                if (i < intCount and LocalPlayer == this.from) then
                    call SyncStoredInteger(Cache[0], this.mkey, position)
                endif
                if (i < realCount and LocalPlayer == this.from) then
                    call SyncStoredReal(Cache[0], this.mkey, position)
                endif
                if (i < boolCount and LocalPlayer == this.from) then
                    call SyncStoredBoolean(Cache[0], this.mkey, position)
                endif
     
                if (i < strCount and LocalPlayer == this.from) then
                    set n = LoadInteger(Table, this, KEY_STR_LEN + i)
                    set p = LoadInteger(Table, this, KEY_STR_POS + i)
         
                    set j = 0
         
                    loop
                        exitwhen j > n
             
                        set position = LoadStr(Table, KEY_STR_CACHE, p + j)

                        if (LocalPlayer == this.from) then
                            call SyncStoredInteger(Cache[1], this.mkey, position)
                        endif

                        set j = j + 1
                    endloop
                endif
     
                set i = i + 1
            endloop
   
            if (this.timeStarted != 0.00) then
                return
            endif

            set this.timeStarted = gameTime()
            set this.playersDone = 0
            set this.buffering   = true
            set this.timeElapsed = (UPDATE_PERIOD - TimerGetElapsed(BufferTimer)) * -1
   
            if (Running==0) then
                call TimerStart(BufferTimer, UPDATE_PERIOD, true, function thistype.readBuffer)
            endif

            set Running=Running+1
        endmethod

        method start takes nothing returns nothing
            local integer l = intCount

            // Find the highest count
            if (l < realCount) then
                set l = realCount
            endif
            if (l < strCount) then
                set l = strCount
            endif
            if (l < boolCount) then
                set l = boolCount
            endif

            call startChunk(0, l)
        endmethod

        method isPlayerIdDone takes integer pid returns boolean
            return LoadBoolean(Table, pid, this)
        endmethod

        method isPlayerDone takes player p returns boolean
            return isPlayerIdDone(GetPlayerId(p))
        endmethod

        private static method updateStatus takes nothing returns boolean
            local integer i = 0
            local integer p = GetSyncedPlayerId()
            local boolean b = true
            local boolean c = true
            local thistype data = GetSyncedInteger()
            local triggercondition tc
           
            if (GetSyncedInstance() != Synchronizer or not data.buffering) then
                return false
            endif
     
            set data.playersDone = data.playersDone + 1
            call SaveBoolean(Table, p, data, true) // set playerdone

            // check if everyone has received the data
            loop
                static if (LIBRARY_PlayerUtils) then
                    exitwhen i == User.AmountPlaying
                    set p = User.fromPlaying(i).id
                    set c = User.fromPlaying(i).isPlaying
                else
                    exitwhen i == bj_MAX_PLAYER_SLOTS
                    set p = i
                    set c = (GetPlayerController(Player(p)) == MAP_CONTROL_USER and GetPlayerSlotState(Player(p)) == PLAYER_SLOT_STATE_PLAYING)
                endif
       
                if (c and not data.isPlayerIdDone(p)) then
                    set b = false // someone hasn't
                endif

                set i = i + 1
            endloop

            // if everyone has recieved the data
            if (b) then
                set Running = Running-1

                if (Running == 0) then
                    call PauseTimer(BufferTimer)
                endif
     
                set data.buffering    = false
                set data.timeFinished = gameTime()
                set data.timeElapsed  = data.timeFinished - data.timeStarted
         
                // fire events
                if (data.onComplete != null) then
                    call data.fireEvent(data.onComplete)
                endif

                call data.fireListeners()
                call SyncInteger_FireEvents(EVENT_SYNC_CACHE)
            endif

            return false
        endmethod

        private static method trackTime takes nothing returns nothing
            set timeCounter = timeCounter + 10
        endmethod

        private static method preloadChar2I takes nothing returns nothing
            local integer i = 0
            local string c
         
            set .AlphaHash = .hashString(ALPHABET)
         
            loop
                exitwhen i >= ALPHABET_BASE

                set c = I2Char(ALPHABET, i)

                call SaveInteger(SyncData.CharTable, .AlphaHash, .hashString(c), Char2I(ALPHABET, c))

                set i = i + 1
            endloop
        endmethod

        private static method onInit takes nothing returns nothing
            set Synchronizer = SelectionSync.create()
           
            set Table = InitHashtable()
            set CharTable = InitHashtable()
         
            set Cache[0] = InitGameCache(CACHE_FILE)
            set Cache[1] = InitGameCache(CACHE_FILE_STR)

            set Elapsed     = CreateTimer()
            set BufferTimer = CreateTimer()

            static if (LIBRARY_PlayerUtils) then
                set LocalPlayer   = User.Local
                set LocalPlayerID = User.fromLocal().id
            else
                set LocalPlayer   = GetLocalPlayer()
                set LocalPlayerID = GetPlayerId(LocalPlayer)
            endif

            call OnSyncInteger(Filter(function thistype.updateStatus))
            call TimerStart(Elapsed, 10., true, function thistype.trackTime)
   
            static if (PRELOAD_STR_CACHE) then
                loop
                    exitwhen Last == ALPHABET_BASE
                    call getKey(Last)
                    set Last = Last + 1
                endloop
                set Last = 0
            endif
   
            call preloadChar2I()

            set Initialized = true
        endmethod

    endstruct

endlibrary
- Added RemoveSyncEvent
- OnSyncInteger returns trigger condition (for use with RemoveSyncEvent)
- Other minor changes
- Added IsPlayerIdSyncing
library SyncInteger uses optional UnitDex /*or any unit indexer*/, optional GroupUtils, optional xebasic, optional PlayerUtils
/***************************************************************
*
*   v1.2.0, by TriggerHappy
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*
*   This library allows you to send integers to all other players.
*
*   _________________________________________________________________________
*   1. Installation
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*   Copy the script to your map and save it (requires JassHelper *or* JNGP)
*   _________________________________________________________________________
*   2. How it works
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*       1. Creates {DUMMY_COUNT} units and assigns {BASE} of them an integer from 0-{BASE}.
*          The 2nd to last dummy is used to signal when the sequence of numbers is over and
*          the last dummy signifies a negative number.
*
*       2. Breaks down the number you want to sync to one or more {BASE} integers,
*          then selects each dummy unit assoicated with that integer.
*
*       4. The selection event fires for all players when the selection has been sycned
*
*       5. The ID of the selected unit is one of the {BASE} numbers. The current
*          total (starts at 0) is multiplied by {BASE} and the latest synced integer is
*          added to that. The process will repeat until it selects the 2nd to last dummy,
*          and the total is our result.
*   _________________________________________________________________________
*   3. Proper Usage
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*       - Avoid the SyncSelections native. It may cause the thread to hang or
*         make some units un-able to move.
*
*       - Dummies must be select-able (no locust)
*
*       - Run the script in debug mode while testing
*
*   _________________________________________________________________________
*   4. Struct API
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*       struct SelectionSync
*          
*           static method create takes nothing returns thistype
*
*           method syncValue takes player p, integer number returns boolean
*           method destroy takes nothing returns nothing
*   _________________________________________________________________________
*   5. Function API
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*       function SyncInteger takes player p, integer number returns boolean
*
*       function GetSyncedInteger takes nothing returns integer
*       function GetSyncedInstance takes nothing returns integer
*       function GetSyncedPlayer takes nothing returns player
*       function GetSyncedPlayerId takes nothing returns integer
*       function IsPlayerSyncing takes player p returns boolean
*       function IsPlayerIdSyncing takes integer pid returns boolean
*       function IsSyncEnabled takes nothing returns boolean
*       function SyncIntegerToggle takes boolean flag returns nothing
*       function SyncIntegerEnable takes nothing returns nothing
*       function SyncIntegerDisable takes nothing returns nothing
*
*       function OnSyncInteger takes code func returns triggercondition
*       function RemoveSyncEvent takes triggercondition action returns nothing
*       function TriggerRegisterSyncEvent takes trigger t, integer eventtype returns nothing
*
*       function SyncTerminate takes boolean destroyEvent returns nothing
*
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*   -http://www.hiveworkshop.com/threads/syncinteger.278674/
*
*/


        globals
            // create a struct instance for global use
            public constant boolean DEFAULT_INSTANCE   = true
           
            // owner of the dummy units
            public constant player DUMMY_PLAYER        = Player(PLAYER_NEUTRAL_PASSIVE)
 
            // dummy can *not* have locust (must be selectabe)
            // basically anything should work (like 'hfoo')
            public constant integer DUMMY_ID           = 'hfoo' // XE_DUMMY_UNITID
 
            // dummy ghost ability
            public constant integer DUMMY_ABILITY      = 'Aeth'

            // allow debug messages (also requries JassHelper Debug Mode)
            public constant boolean ALLOW_DEBUGGING    = true
 
            // higher == more dummies but less selections (faster)
            public constant integer BASE               = 10
           
            // two higher than BASE (jasshelper doesn't allow BASE + 2)
            public constant integer DUMMY_COUNT        = 12
           
            // endconfig

            constant integer EVENT_SYNC_INTEGER = 1
           
            public integer DefaultInstance = 0
           
            private trigger OnSelectTrigger = CreateTrigger()
            private trigger EventTrig       = CreateTrigger()
            private real FireEvent          = 0
 
            private group SelectionGroup

            private integer LastPlayer
            private integer LastSync
            private integer LastInstance
            private player LocalPlayer
            private integer array ActiveSyncs
           
            private real DUMMY_X = 0
            private real DUMMY_Y = 0
           
            private integer array DummyInstance

        endglobals

        function GetSyncedInteger takes nothing returns integer
            return LastSync
        endfunction

        function GetSyncedPlayer takes nothing returns player
            return Player(LastPlayer)
        endfunction
       
        function GetSyncedInstance takes nothing returns integer
            return LastInstance
        endfunction
   
        function GetSyncedPlayerId takes nothing returns integer
            return LastPlayer
        endfunction
       
        function IsPlayerIdSyncing takes integer pid returns boolean
            return ActiveSyncs[pid] > 0
        endfunction
       
        function IsPlayerSyncing takes player p returns boolean
            return ActiveSyncs[GetPlayerId(p)] > 0
        endfunction
       
        function IsSyncEnabled takes nothing returns boolean
            return IsTriggerEnabled(OnSelectTrigger)
        endfunction

        function SyncIntegerEnable takes nothing returns nothing
            call EnableTrigger(OnSelectTrigger)
        endfunction

        function SyncIntegerDisable takes nothing returns nothing
            call DisableTrigger(OnSelectTrigger)
        endfunction

        function SyncIntegerToggle takes boolean flag returns nothing
            if (flag) then
                call EnableTrigger(OnSelectTrigger)
            else
                call DisableTrigger(OnSelectTrigger)
            endif
        endfunction

        function OnSyncInteger takes filterfunc func returns triggercondition
            return TriggerAddCondition(EventTrig, func)
        endfunction

        function RemoveSyncEvent takes triggercondition action returns nothing
           call TriggerRemoveCondition(EventTrig, action)
        endfunction

        function TriggerRegisterSyncEvent takes trigger t, integer eventtype returns nothing
            call TriggerRegisterVariableEvent(t, SCOPE_PREFIX + "FireEvent", EQUAL, eventtype)
        endfunction
   
        public function FireEvents takes real eventtype returns nothing
            set FireEvent = eventtype
            set FireEvent = 0
        endfunction
       
        // This function is called when a unit is selected.
        private function OnSelect takes nothing returns boolean
            local unit u        = GetTriggerUnit()
            local player p      = GetTriggerPlayer()
            local integer id    = GetPlayerId(p)
            local integer udata = GetUnitUserData(u)
            local SelectionSync this = DummyInstance[udata]
            local boolean isNeg = (this.Dummy[DUMMY_COUNT-1] == u)
            local integer index = this.DummyID[udata] - 1
           
            if (this <= 0) then
                return false
            endif
           
            if (isNeg) then
                set this.SyncingValue[id] = this.SyncingValue[id]*-1
            endif

            if (isNeg or this.Dummy[DUMMY_COUNT-2] == u) then
                set ActiveSyncs[id] = ActiveSyncs[id] - 1
               
                // The number is finished syncing, fire the events
                set LastPlayer   = id
                set LastSync     = this.SyncingValue[id]
                set LastInstance = this
                set FireEvent    = EVENT_SYNC_INTEGER
               
                call TriggerEvaluate(EventTrig)
               
                // Reset variables
                set FireEvent = 0
                set this.SyncingValue[id] = -1
            else
           
                if (this.SyncingValue[id] == -1) then
                    set this.SyncingValue[id] = 0
                endif
               
                // Build the number we are trying to sync
                set this.SyncingValue[id] = this.SyncingValue[id] * BASE + index
            endif
   
            set u = null
   
            return false
        endfunction
       
        private keyword SyncIntegerInit
       
        // This struct allows us to dynamically create a group of units
        // which we can use to synchronize our integer through unit selections.
        struct SelectionSync
           
            public unit array Dummy[DUMMY_COUNT]
            public integer array DummyID[DUMMY_COUNT]
            public integer array SyncingValue[12]
           
            public static method debugger takes boolean b, string s returns nothing
                static if (ALLOW_DEBUGGING and DEBUG_MODE) then
                    if (b) then
                        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "|c00FF0000" + SCOPE_PREFIX + s + "|r")
                    endif
                endif
            endmethod
       
            static method create takes nothing returns thistype
                local thistype this = thistype.allocate()
                local integer i = 0
                local integer uid
               
                debug call .debugger(OnSelectTrigger == null, "[SelectionSync.create()] OnSelectTrigger is null and has no events attached to it.")
                debug call .debugger(this.Dummy[0] != null, "[SelectionSync.create()] Dummy not null!")
               
                loop
                    exitwhen i >= DUMMY_COUNT
                   
                    set this.Dummy[i] = CreateUnit(DUMMY_PLAYER, DUMMY_ID, DUMMY_X, DUMMY_Y, i)
           
                    set uid = GetUnitUserData(this.Dummy[i])
                 
                    if (uid == 0) then
                        set uid = ( (this-1) * DUMMY_COUNT ) + (i + 1)
                        call SetUnitUserData(this.Dummy[i], uid)
                    endif
                 
                    debug call .debugger((i == 0) and (this.Dummy[i] == null), "[SelectionSync.create()] Dummy unit is null (check DUMMY_ID).")
                    debug call .debugger((i == 0) and (GetUnitAbilityLevel(this.Dummy[i], 'Aloc') > 0), "[SelectionSync.create()] Dummy units must be selectable (detected locust).")
                 
                    set this.DummyID[uid] = i + 1
                    set DummyInstance[uid] = this

                    call UnitAddAbility(this.Dummy[i], DUMMY_ABILITY)
                    call PauseUnit(this.Dummy[i], true)
                    call SetUnitScale(this.Dummy[i], 0, 0, 0)
                 
                    set i = i + 1
                endloop
               
                return this
            endmethod
           
            method syncValue takes player p, integer number returns boolean
                local integer x = number
                local integer i = 0
                local integer d = BASE
                local integer j = 0
                local integer n = 0
                local integer l = 0
                local integer playerId = GetPlayerId(p)
                local unit u
                local unit last

                debug call .debugger(OnSelectTrigger == null, "[SelectionSync.syncValue()] OnSelectTrigger is destroyed.")
                debug call .debugger(IsSyncEnabled() == false, "[SelectionSync.syncValue()] OnSelectTrigger is disabled.")
       
                if (not IsSyncEnabled()) then
                    return false
                endif
       
                // Check if the number is negative
                if (number < 0) then
                    set d = DUMMY_COUNT-1
                    set number = number * -1
                endif
     
                loop
                    set x = x/(BASE)
                    exitwhen x==0
                    set i=i+1
                endloop
       
                // Count how many units are selected
                call GroupEnumUnitsSelected(SelectionGroup, p, null)
                set bj_groupCountUnits = 0

                set u = FirstOfGroup(SelectionGroup)
                loop
                    exitwhen u == null
                    set last = u
                    call GroupRemoveUnit(SelectionGroup, u)
                    set bj_groupCountUnits = bj_groupCountUnits + 1
                    set u = FirstOfGroup(SelectionGroup)
                endloop
           
                // If the queue is full, de-select the last unit which
                // will allow us to select a dummy, and hopefully
                // avoid a flickering effect.
                if (bj_groupCountUnits >= 12 and LocalPlayer == p) then
                    call SelectUnit(last, false)
                endif

                set j=R2I(Pow(BASE, i))

                loop
                    set n = j
                    set x = number/n
                    set j = j / BASE
               
                    if (LocalPlayer == p) then
                        call SelectUnit(this.Dummy[x], true)
                        call SelectUnit(this.Dummy[x], false)
                    endif
           
                    set number = number-x*n

                    exitwhen i == 0
           
                    set i = i - 1
                endloop
     
                if (LocalPlayer == p) then
                    call SelectUnit(this.Dummy[d], true)
                    call SelectUnit(this.Dummy[d], false)
                 
                    if (bj_groupCountUnits >= 12) then
                        call SelectUnit(last, true)
                    endif
                endif

                set u = null
                set last = null
               
                set ActiveSyncs[playerId] = ActiveSyncs[playerId] + 1
               
                return true
            endmethod
           
            method destroy takes nothing returns nothing
                local integer i = 0
     
                loop
                    exitwhen i >= DUMMY_COUNT
                    call RemoveUnit(this.Dummy[i])
                    set this.Dummy[i] = null
                    set i = i + 1
                endloop
            endmethod
           
            implement SyncIntegerInit
        endstruct
       
        function SyncInteger takes player p, integer number returns boolean
            debug call SelectionSync.debugger(DefaultInstance == 0, "[SyncInteger()] DefaultInstance is not initialized (make sure DEFAULT_INSTANCE is true")
           
            return SelectionSync(DefaultInstance).syncValue(p, number)
        endfunction
       
        function SyncTerminate takes boolean destroyEvents returns boolean
            local integer i = 0
           
            if (OnSelectTrigger == null and EventTrig == null) then
                return false
            endif
           
            if (destroyEvents) then
                call DestroyTrigger(OnSelectTrigger)
                call DestroyTrigger(EventTrig)
                set OnSelectTrigger = null
                set EventTrig = null
           
                static if not LIBRARY_GroupUtils then
                    call DestroyGroup(SelectionGroup)
                    set SelectionGroup = null
                endif
            else
                call SyncIntegerDisable()
            endif
           
            if (DefaultInstance > 0) then
                call SelectionSync(DefaultInstance).destroy()
            endif
           
            return true
        endfunction

        //===========================================================================
        private module SyncIntegerInit
            private static method onInit takes nothing returns nothing
                local integer i = 0
                local integer j
               
                loop
                    call TriggerRegisterPlayerUnitEvent(OnSelectTrigger, Player(i), EVENT_PLAYER_UNIT_SELECTED, null)
         
                    set i = i + 1
                    exitwhen i==bj_MAX_PLAYER_SLOTS
                endloop

                call TriggerAddCondition(OnSelectTrigger, Filter(function OnSelect))
     
                static if (LIBRARY_GroupUtils) then
                    set SelectionGroup=ENUM_GROUP
                else
                    set SelectionGroup=CreateGroup()
                endif

                static if (LIBRARY_PlayerUtils) then
                    set LocalPlayer=User.Local
                else
                    set LocalPlayer=GetLocalPlayer()
                endif
           
                set DUMMY_X = GetCameraBoundMaxX() + 2000
                set DUMMY_Y = GetCameraBoundMaxY() + 2000
               
                static if (DEFAULT_INSTANCE) then
                    set DefaultInstance = SelectionSync.create()
                endif
            endmethod
        endmodule
       
endlibrary
// few modifications by triggerhappy

library FileIO /* v2.0.0.1
*************************************************************************************
*
*   Used read/write data to/from files
*
************************************************************************************
*
*   struct File extends array
*
*       Description
*       -----------------------
*
*           This is used to read data from files and write data to files
*
*       Creators/Destructors
*       -----------------------
*
*           static method open takes string mapName, string fileName, integer flag returns File
*               -   Used to open a file. Pass read/write flag in to open file for reading or writing.
*
*           method close takes nothing returns nothing
*
*       Fields
*       -----------------------
*
*           readonly boolean enabled
*               -   This is a local value. It is true if the player can read files, false if the player can't read files.
*           readonly string localFileScriptName
*               -   This contains the name and path to a bat file that is automatically created
*               -   on the player's computer in the event that they can't read files. Output this filename
*               -   and tell them to run the script so that they can read files.
*
*           integer Flag.READ
*           integer Flag.WRITE
*               -   These are the read/write flags. Pass these into File.open
*
*       Methods
*       -----------------------
*
*           method read takes nothing returns string
*               -    Reads next line from file. Returns null when there are no more lines.
*
*           method write takes string data returns nothing
*               -   Writes line to file. The only limit is wc3 max string size.
*
************************************************************************************/

    //Tests if the player can read files
    private module LocalFileTestInit
        private static method onInit takes nothing returns nothing
            call TimerStart(CreateTimer(),0,false,function thistype.init)
        endmethod
    endmodule
    private struct LocalFileTest extends array
        //private static constant string FLAG_FOLDER = "Flag"
        private static constant string FLAG_FILE = "flag.pld"
       
        readonly static boolean success = false
       
        private static method testForLocalEnabled takes nothing returns nothing
            local string playerName = GetPlayerName(GetLocalPlayer())
           
            call Preloader(FLAG_FILE)
           
            set success = GetPlayerName(GetLocalPlayer()) != playerName
           
            call SetPlayerName(GetLocalPlayer(), playerName)
        endmethod
       
        private static method writeLocalFileTest takes nothing returns nothing
            call PreloadGenClear()
            call PreloadGenStart()
            call Preload("\")\r\n\tcall SetPlayerName(GetLocalPlayer(), \"FLAG TEST LOCAL CHECK\")\r\n//")
            call Preload("\" )\r\nendfunction\r\nfunction AAA takes nothing returns nothing \r\n//")
            call PreloadGenEnd(FLAG_FILE)
        endmethod
       
        private static method writeEnableLocalRegistry takes string fname returns nothing
            call PreloadGenClear()
            call PreloadGenStart()
            call Preload("\")\r\necho Set Reg = CreateObject(\"wscript.shell\") > download.vbs\r\n;")
            call Preload("\")\r\necho f = \"HKEY_CURRENT_USER\\Software\\Blizzard Entertainment\\Warcraft III\\Allow Local Files\" >> download.vbs\r\n;")
            call Preload("\")\r\necho f = Replace(f,\"\\\",Chr(92)) >> download.vbs\r\n;") //"
            call Preload("\")\r\necho Reg.RegWrite f, 1, \"REG_DWORD\" >> download.vbs\r\n;")
            call Preload("\")\r\nstart download.vbs\r\n;")
            call PreloadGenEnd(fname)
        endmethod
       
        private static method init takes nothing returns nothing
            call DestroyTimer(GetExpiredTimer())
       
            call writeLocalFileTest()
            call testForLocalEnabled()
           
            if (not success) then
                call writeEnableLocalRegistry(File.localFileScriptName)
                call writeEnableLocalRegistry("C:\\!! AllowLocalFiles\\" + File.localFileScriptName) // for pre 128
                //call writeEnableLocalRegistry("C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\" + File.localFileScriptName)
            endif
        endmethod
   
        implement LocalFileTestInit
    endstruct
   
    private struct FlagG extends array
        static constant integer READ = 1
        static constant integer WRITE = 2
    endstruct
   
    private module FileInit
        private static method onInit takes nothing returns nothing
            call init()
        endmethod
    endmodule
   
    struct File extends array
        private static constant integer MAX_LINE_LENGTH = 209           //max amount of data per line
        private static hashtable stringTable                            //stores lines from file, 16 lines per file
   
        private static integer instanceCount = 0
        private static integer array recycler
       
        private string mapName
        private string fileName
       
        private string data                                             //data buffer
       
        private integer fileIndex                                       //current file index, -2147483648 to 2147483647
        private integer dataIndex                                       //current data index for file, 0-15
       
        private integer flag                                            //read/write flag
       
        private static string array extra0                              //extra 0s on string lengths to make digits = 4
       
        static method operator Flag takes nothing returns FlagG
            return 0
        endmethod
   
        static method operator enabled takes nothing returns boolean
            return LocalFileTest.success
        endmethod
       
        static method operator localFileScriptName takes nothing returns string
            return "AllowLocalFiles.bat.txt"
        endmethod
       
        static method operator localFileScriptNameFull takes nothing returns string
            return localFileScriptName
        endmethod
       
        static method open takes string mapName, string fileName, integer flag returns File
            local thistype this = recycler[0]
           
            if (0 == this) then
                set this = instanceCount + 1
                set instanceCount = this
            else
                set recycler[0] = recycler[this]
            endif
           
            set fileIndex = -2147483648
           
            set this.mapName = mapName
            set this.fileName = fileName
           
            set this.flag = flag
           
            if (flag == Flag.READ) then
                //if reading, go to previous file index so that the reader can auto load up the file
                set fileIndex = fileIndex - 1
                set dataIndex = 16
            elseif (flag == Flag.WRITE) then
                //open file for writing
                call PreloadGenClear()
                call PreloadGenStart()
            endif
           
            return this
        endmethod
       
        //loads file and returns lines out of file
        private static string array pname
        private method loadline takes nothing returns nothing
            local integer i
           
            //if there are no more lines in the file, load next file
            if (16 == dataIndex) then
                set dataIndex = 0
                set fileIndex = fileIndex + 1
               
                //store current player names
                set i = 15
                loop
                    set pname[i] = GetPlayerName(Player(i))
               
                    exitwhen 0 == i
                    set i = i - 1
                endloop
               
                //load file (sets the player names to lines in file)
                call Preloader(mapName + "_" + fileName)
               
                //flush file buffer
                call FlushChildHashtable(stringTable, this)
               
                //load lines from file to file buffer and return player names to normal
                set i = 15
                loop
                    if (pname[i] != GetPlayerName(Player(i))) then
                        call SaveStr(stringTable, this, i, SubString(GetPlayerName(Player(i)), 1, StringLength(GetPlayerName(Player(i)))))
                        call SetPlayerName(Player(i), pname[i])
                    endif
               
                    exitwhen 0 == i
                    set i = i - 1
                endloop
            endif
           
            //add next line of file to data
            set this.data = this.data + LoadStr(stringTable, this, dataIndex)
            set dataIndex = dataIndex + 1
        endmethod
       
        method read takes nothing returns string
            local integer length
            local string data = ""
           
            debug if (flag != Flag.READ) then
                debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60000,"FILE IO ERROR: ATTEMPT TO READ TO FILE OPEN FOR WRITING")
                debug return null
            debug endif
           
            //if there is no data at the moment, get next line from file
            if (StringLength(this.data) < 4) then
                call loadline()
               
                //if there is no next line, return null
                if (StringLength(this.data) < 4) then
                    return null
                endif
            endif
           
            //get the length of the data (# of characters that make it up)
            set length = S2I(SubString(this.data, 0, 4))
            set this.data = SubString(this.data, 4, StringLength(this.data))
           
            loop
                if (length > StringLength(this.data)) then
                    //if the length is greater than the data currently in the buffer, dump the
                    //entire buffer to the data being returned and get the next line
                    set length = length - StringLength(this.data)
                    set data = data + this.data
                    set this.data = ""
                    call loadline()
                else
                    //if the length is less than the data in the buffer, dump that data
                    //from the buffer
                    set data = data + SubString(this.data, 0, length)
                    set this.data = SubString(this.data, length, StringLength(this.data))
                    set length = 0
                   
                    return data
                endif
            endloop
           
            return null
        endmethod
       
        method write takes string data returns nothing
            local integer length = StringLength(data)
            local integer digits = 0
            local boolean done = false
           
            debug if (flag != Flag.WRITE) then
                debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60000,"FILE IO ERROR: ATTEMPT TO WRITE TO FILE OPEN FOR READING")
                debug return
            debug endif
           
            //first, retrieve the # of digits for the length
            loop
                set digits = digits + 1
                set length = length/10
                exitwhen 0 == length
            endloop
           
            //add the extra digits to and length to buffer
            set this.data = this.data + extra0[digits] + I2S(StringLength(data))
           
            loop
                if (StringLength(data) > 400) then
                    //if the length of the data is greater than 400, throw first 400 chars into buffer
                    set this.data = this.data + SubString(data, 0, 400)
                    set data = SubString(data, 400, StringLength(data))
                else
                    //if the length isn't greater than 400, throw it all into the buffer
                    set this.data = this.data + data
                    set data = ""
                    set done = true
                endif
               
                loop
                    //throw the data into file in sets of MAX_LINE_LENGTH chars until there is not
                    //enough data in the buffer to completely fill a line
                    exitwhen StringLength(this.data) < MAX_LINE_LENGTH
                    call Preload("\")\r\n\tcall SetPlayerName(Player("+I2S(dataIndex)+"), \" "+SubString(this.data, 0, MAX_LINE_LENGTH)+"\")\r\n//")
                    set this.data = SubString(this.data, MAX_LINE_LENGTH, StringLength(this.data))
                   
                    set dataIndex = dataIndex + 1
                    if (dataIndex == 16) then
                        //start a new file
                       
                        set dataIndex = 0
                       
                        call Preload( "\" )\r\nendfunction\r\nfunction AAA takes nothing returns nothing \r\n//")
                        call PreloadGenEnd(mapName + "_" + fileName)
                       
                        set fileIndex = fileIndex + 1
                        call PreloadGenClear()
                        call PreloadGenStart()
                    endif
                endloop
               
                exitwhen done
            endloop
        endmethod
       
        method close takes nothing returns nothing
            if (flag == Flag.READ) then
                //flush the read buffer
                call FlushChildHashtable(stringTable, this)
            elseif (flag == Flag.WRITE) then
                //write remaining of data to file and close it out
                call Preload("\")\r\n\tcall SetPlayerName(Player("+I2S(dataIndex)+"), \" "+data+"\")\r\n//")
                call Preload( "\" )\r\nendfunction\r\nfunction AAA takes nothing returns nothing \r\n//")
                call PreloadGenEnd(mapName + "_" + fileName)
            endif
           
            //deallocate and reset
            set recycler[this] = recycler[0]
            set recycler[0] = this
           
            set dataIndex = 0
            set data = ""
        endmethod
       
        private static method init takes nothing returns nothing
            set extra0[1] = "000"
            set extra0[2] = "00"
            set extra0[3] = "0"
            set extra0[4] = ""
           
            set stringTable = InitHashtable()
        endmethod
       
        implement FileInit
    endstruct
endlibrary
library GameDataWrapper uses FileIO
   
    globals
        private constant string MEMHAX_FILENAME = "./MapData.ini"
    endglobals
   
    private constant function GetMapName takes nothing returns string
        return udg_MapName
    endfunction
   
    constant function MemoryHacksEnabled takes nothing returns boolean
        static if LIBRARY_MemoryHacks then
            return true
        else
            return false
        endif
    endfunction
   
    struct GameData
   
        static method write takes string skey, string value returns nothing

            static if LIBRARY_MemoryHacks then
                call WriteStringToFile(MEMHAX_FILENAME, GetMapName(), skey, value)
            else
                local File f = File.open(GetMapName(), skey + ".pld", File.Flag.WRITE)
                call f.write(value)
                call f.close()
            endif

        endmethod

        static method read takes string skey returns string

            static if LIBRARY_MemoryHacks then
                return ReadStringFromFile(MEMHAX_FILENAME, GetMapName(), skey, "")
            else
                local File f = File.open(GetMapName(), skey + ".pld", File.Flag.READ)
                local string contents = f.read()
                call f.close()
                return contents
            endif

        endmethod
       
        static method delete takes string skey returns nothing
            call write(skey, "")
        endmethod

    endstruct

endlibrary
by Pipedream
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 "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
    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
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

 
library PlayerUtils
/**************************************************************
*
*   v1.2.8 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 = 15
       
        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"
         
            set thistype.first = User.NULL

            loop
                exitwhen i == 12

                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
library GroupUtils initializer Init requires optional xebasic
//******************************************************************************
//* BY: Rising_Dusk
//*
//* This library is a combination of several features relevant to groups. First
//* and foremost, it contains a group stack that you can access dynamic groups
//* from. It also provides means to refresh groups and clear any shadow
//* references within them. The included boolexprs are there for backwards
//* compatibility with maps that happen to use them. Since the 1.24c patch,
//* null boolexprs used in GroupEnumUnits* calls no longer leak, so there is no
//* performance gain to using the BOOLEXPR_TRUE constant.
//*
//* Instead of creating/destroying groups, we have moved on to recycling them.
//* NewGroup pulls a group from the stack and ReleaseGroup adds it back. Always
//* remember to call ReleaseGroup on a group when you are done using it. If you
//* fail to do so enough times, the stack will overflow and no longer work.
//*
//* GroupRefresh cleans a group of any shadow references which may be clogging
//* its hashtable. If you remove a unit from the game who is a member of a unit
//* group, it will 'effectively' remove the unit from the group, but leave a
//* shadow in its place. Calling GroupRefresh on a group will clean up any
//* shadow references that may exist within it. It is only worth doing this on
//* groups that you plan to have around for awhile.
//*
//* Constants that can be used from the library:
//*     [group]    ENUM_GROUP      As you might expect, this group is good for
//*                                when you need a group just for enumeration.
//*     [boolexpr] BOOLEXPR_TRUE   This is a true boolexpr, which is important
//*                                because a 'null' boolexpr in enumeration
//*                                calls results in a leak. Use this instead.
//*     [boolexpr] BOOLEXPR_FALSE  This exists mostly for completeness.
//*
//* This library also includes a simple implementation of a group enumeration
//* call that factors collision of units in a given area of effect. This is
//* particularly useful because GroupEnumUnitsInRange doesn't factor collision.
//*
//* In your map, you can just replace all instances of GroupEnumUnitsInRange
//* with GroupEnumUnitsInArea with identical arguments and your spells will
//* consider all units colliding with the area of effect. After calling this
//* function as you would normally call GroupEnumUnitsInRange, you are free to
//* do anything with the group that you would normally do.
//*
//* If you don't use xebasic in your map, you may edit the MAX_COLLISION_SIZE
//* variable below and the library will use that as the added radius to check.
//* If you use xebasic, however, the script will automatically use xe's
//* collision size variable.
//*
//* You are also able to use GroupUnitsInArea. This function returns all units
//* within the area, no matter what they are, which can be convenient for those
//* instances where you actually want that.
//*
//* Example usage:
//*     local group MyGroup = NewGroup()
//*     call GroupRefresh(MyGroup)
//*     call ReleaseGroup(MyGroup)
//*     call GroupEnumUnitsInArea(ENUM_GROUP, x, y, 350., BOOLEXPR_TRUE)
//*     call GroupUnitsInArea(ENUM_GROUP, x, y, 350.)
//*
globals
    //If you don't have xebasic in your map, this value will be used instead.
    //This value corresponds to the max collision size of a unit in your map.
    private constant real    MAX_COLLISION_SIZE = 197.
    //If you are insane and don't care about any of the protection involved in
    //this library, but want this script to be really fast, set this to true.
    private constant boolean LESS_SAFETY        = false
endglobals

globals
    //* Constants that are available to the user
    group    ENUM_GROUP     = CreateGroup()
    boolexpr BOOLEXPR_TRUE  = null
    boolexpr BOOLEXPR_FALSE = null
endglobals

globals
    //* Hashtable for debug purposes
    private hashtable     ht     = InitHashtable()
    //* Temporary references for GroupRefresh
    private boolean       Flag   = false
    private group         Refr   = null
    //* Arrays and counter for the group stack
    private group   array Groups
    private integer       Count  = 0
    //* Variables for use with the GroupUnitsInArea function
    private real          X      = 0.
    private real          Y      = 0.
    private real          R      = 0.
    private hashtable     H      = InitHashtable()
endglobals

private function HookDestroyGroup takes group g returns nothing
    if g == ENUM_GROUP then
        call BJDebugMsg(SCOPE_PREFIX+"Warning: ENUM_GROUP destroyed")
    endif
endfunction

debug hook DestroyGroup HookDestroyGroup

private function AddEx takes nothing returns nothing
    if Flag then
        call GroupClear(Refr)
        set Flag = false
    endif
    call GroupAddUnit(Refr, GetEnumUnit())
endfunction
function GroupRefresh takes group g returns nothing
    set Flag = true
    set Refr = g
    call ForGroup(Refr, function AddEx)
    if Flag then
        call GroupClear(g)
    endif
endfunction

function NewGroup takes nothing returns group
    if Count == 0 then
        set Groups[0] = CreateGroup()
    else
        set Count = Count - 1
    endif
    static if not LESS_SAFETY then
        call SaveInteger(ht, 0, GetHandleId(Groups[Count]), 1)
    endif
    return Groups[Count]
endfunction
function ReleaseGroup takes group g returns boolean
    local integer id = GetHandleId(g)
    static if LESS_SAFETY then
        if g == null then
            debug call BJDebugMsg(SCOPE_PREFIX+"Error: Null groups cannot be released")
            return false
        elseif Count == 8191 then
            debug call BJDebugMsg(SCOPE_PREFIX+"Error: Max groups achieved, destroying group")
            call DestroyGroup(g)
            return false
        endif
    else
        if g == null then
            debug call BJDebugMsg(SCOPE_PREFIX+"Error: Null groups cannot be released")
            return false
        elseif not HaveSavedInteger(ht, 0, id) then
            debug call BJDebugMsg(SCOPE_PREFIX+"Error: Group not part of stack")
            return false
        elseif LoadInteger(ht, 0, id) == 2 then
            debug call BJDebugMsg(SCOPE_PREFIX+"Error: Groups cannot be multiply released")
            return false
        elseif Count == 8191 then
            debug call BJDebugMsg(SCOPE_PREFIX+"Error: Max groups achieved, destroying group")
            call DestroyGroup(g)
            return false
        endif
        call SaveInteger(ht, 0, id, 2)
    endif
    call GroupClear(g)
    set Groups[Count] = g
    set Count         = Count + 1
    return true
endfunction

private function Filter takes nothing returns boolean
    return IsUnitInRangeXY(GetFilterUnit(), X, Y, R)
endfunction

private function HookDestroyBoolExpr takes boolexpr b returns nothing
    local integer bid = GetHandleId(b)
    if HaveSavedHandle(H, 0, bid) then
        //Clear the saved boolexpr
        call DestroyBoolExpr(LoadBooleanExprHandle(H, 0, bid))
        call RemoveSavedHandle(H, 0, bid)
    endif
endfunction

hook DestroyBoolExpr HookDestroyBoolExpr

private constant function GetRadius takes real radius returns real
    static if LIBRARY_xebasic then
        return radius+XE_MAX_COLLISION_SIZE
    else
        return radius+MAX_COLLISION_SIZE
    endif
endfunction

function GroupEnumUnitsInArea takes group whichGroup, real x, real y, real radius, boolexpr filter returns nothing
    local real    prevX = X
    local real    prevY = Y
    local real    prevR = R
    local integer bid   = 0
   
    //Set variables to new values
    set X = x
    set Y = y
    set R = radius
    if filter == null then
        //Adjusts for null boolexprs passed to the function
        set filter = Condition(function Filter)
    else
        //Check for a saved boolexpr
        set bid = GetHandleId(filter)
        if HaveSavedHandle(H, 0, bid) then
            //Set the filter to use to the saved one
            set filter = LoadBooleanExprHandle(H, 0, bid)
        else
            //Create a new And() boolexpr for this filter
            set filter = And(Condition(function Filter), filter)
            call SaveBooleanExprHandle(H, 0, bid, filter)
        endif
    endif
    //Enumerate, if they want to use the boolexpr, this lets them
    call GroupEnumUnitsInRange(whichGroup, x, y, GetRadius(radius), filter)
    //Give back original settings so nested enumerations work
    set X = prevX
    set Y = prevY
    set R = prevR
endfunction

function GroupUnitsInArea takes group whichGroup, real x, real y, real radius returns nothing
    local real prevX = X
    local real prevY = Y
    local real prevR = R
   
    //Set variables to new values
    set X = x
    set Y = y
    set R = radius
    //Enumerate
    call GroupEnumUnitsInRange(whichGroup, x, y, GetRadius(radius), Condition(function Filter))
    //Give back original settings so nested enumerations work
    set X = prevX
    set Y = prevY
    set R = prevR
endfunction

private function True takes nothing returns boolean
    return true
endfunction
private function False takes nothing returns boolean
    return false
endfunction
private function Init takes nothing returns nothing
    set BOOLEXPR_TRUE  = Condition(function True)
    set BOOLEXPR_FALSE = Condition(function False)
endfunction
endlibrary
library xebasic
//**************************************************************************
//
// xebasic 0.4
// =======
// XE_DUMMY_UNITID : Rawcode of the dummy unit in your map. It should
//                   use the dummy.mdx model, so remember to import it as
//                   well, just use copy&paste to copy the dummy from the
//                   xe map to yours, then change the rawcode.
//
// XE_HEIGHT_ENABLER: Medivh's raven form ability, you may need to change
//                    this rawcode to another spell that morphs into a flier
//                    in case you modified medivh's spell in your map.
//
// XE_TREE_RECOGNITION: The ancients' Eat tree ability, same as with medivh
//                      raven form, you might have to change it.
//
// XE_ANIMATION_PERIOD: The global period of animation used by whatever
//                      timer that depends on it, if you put a low value
//                      the movement will look good but it may hurt your
//                      performance, if instead you use a high value it
//                      will not lag but will be fast.
//
// XE_MAX_COLLISION_SIZE: The maximum unit collision size in your map, if
//                        you got a unit bigger than 197.0 it would be
//                        a good idea to update this constant, since some
//                        enums will not find it. Likewise, if none of
//                        your units can go bellow X and X is much smaller
//                        than 197.0, it would be a good idea to update
//                        as well, since it will improve the performance
//                        those enums.
//
// Notice you probably don't have to update this library, unless I specify
// there are new constants which would be unlikely.
//
//**************************************************************************

//===========================================================================
globals
   constant integer XE_DUMMY_UNITID       = 'e000'
   constant integer XE_HEIGHT_ENABLER     = 'Amrf'
   constant integer XE_TREE_RECOGNITION   = 'Aeat'
   constant real    XE_ANIMATION_PERIOD   =  0.025
   constant real    XE_MAX_COLLISION_SIZE =  197.0
endglobals

endlibrary


 
This works for everyone playing your map, and for versions 1.26 & 1.27.

Download the latest modified world editor (Jass NewGen Pack) with memory access support.

http://www.hiveworkshop.com/threads/memoryhacks-newgen-integration.289868/

Open the map in that editor, save, and test the map. Now you and all your players can load without having local files enabled.
The easiest way is to enable local files through JNGP's Extension tab.

But, for the average player who is playing your map:

- The game will display a message saying you need to enable local files
- Double click the generated file the game tells you to run (AllowLocalFiles.bat). It will be in your WC3 folder as well as in your C: drive
- Restart WC3
http://www.hiveworkshop.com/threads/codeless-save-and-load-multiplayer-v1-3-1.278664/