• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[Script] A Lua save/load system similar to TriggerHappy's GUI-friendly system

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,535
I realize this is a mighty large request and I don't really expect anyone to respond, but I figured it's worth a shot.

After seeing threads like these: [Lua/Typescript] "Codeless" Saving/Synchronized loading of huge amounts of data.

I feel as though it'd be great to have a Save/Load system compatible with Lua. It doesn't need to be GUI-friendly but that would be a plus.

I've tried converting these systems to Lua myself but a lot of it goes way over my head.

At this point I'd be willing to pay someone for a system that works.
 
Last edited:
Level 2
Joined
Feb 13, 2020
Messages
19
Shameless bump. This thread didn't get enough attention. There's a community outcry for a LUA Save/Load. Show some effort, you big brainy HWS masterminds!
 
Level 2
Joined
Feb 13, 2020
Messages
19
This will be my last bump for this request. It has several hundreds of views already, so if no result comes from this, I don't think any further bumping will help. Thanks to all who read and considered!
 
Level 14
Joined
Feb 7, 2020
Messages
387
Here you go. This system is made from scratch, so it does not map to how TH's Codeless system currently works. It stores the basics of heroes: items, unit type, abilities, ability levels, and player data (gold, lumber, food - can be toggled).

I'm short on time right now, so here are some caveats:
  • only loads from files, needs additional work if you want to also allow manual code input (chat string).
  • it requires storing IDs via Lua.
  • you can only load once per slot (not sure how to fix; this is the same for TH's Codeless system on 1.32 - not sure if always the case or if it's a new bug with preloading).
  • the included data encryption is very dumb, but I don't have the time to invest in figuring out how that all works (maybe someone else can translate an existing JASS library for save codes into Lua?).
Save a character with -save X (e.g. -save 1), load a character with -load X (e.g. -load 1)

It could potentially go into the resources section but it needs some QA first and tune ups (and probably real encryption that is less janky).

Let me know if you use it and run into any bugs.
 

Attachments

  • lua_save_load_v1.0a.w3m
    54.5 KB · Views: 51

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,535
Here you go. This system is made from scratch, so it does not map to how TH's Codeless system currently works. It stores the basics of heroes: items, unit type, abilities, ability levels, and player data (gold, lumber, food - can be toggled).

I'm short on time right now, so here are some caveats:
  • only loads from files, needs additional work if you want to also allow manual code input (chat string).
  • it requires storing IDs via Lua.
  • you can only load once per slot (not sure how to fix; this is the same for TH's Codeless system on 1.32 - not sure if always the case or if it's a new bug with preloading).
  • the included data encryption is very dumb, but I don't have the time to invest in figuring out how that all works (maybe someone else can translate an existing JASS library for save codes into Lua?).
Save a character with -save X (e.g. -save 1), load a character with -load X (e.g. -load 1)

It could potentially go into the resources section but it needs some QA first and tune ups (and probably real encryption that is less janky).

Let me know if you use it and run into any bugs.
This works great, thank you. But would it be possible for you to send me a more barebones version? I'm struggling to get it to save/load a single integer. In other words, I just want to save the player gold, nothing hero related.

I'm messing around with it now but I seem to have made a mistake somewhere when trying to remove everything hero related.

Sorry for the late response.
 
Last edited:

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,535
I'm having issues with loading when a file doesn't exist. So at the start of the game (after a few seconds has passed), I attempt to Load every player:
  • Load
    • Events
    • Conditions
    • Actions
      • Player Group - Pick every player in (All players) and do (Actions)
        • Loop - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • ((Picked player) controller) Equal to User
              • ((Picked player) slot status) Equal to Is playing
            • Then - Actions
              • Custom script: debug(function()
              • -------- load the file: --------
              • Custom script: local id = pnum( GetEnumPlayer() )
              • Custom script: udg_save_file_slot[id] = 0
              • Custom script: file:load( GetEnumPlayer(), udg_save_file_slot[id], 1.5 )
              • Custom script: end)
            • Else - Actions
The issue is that some players don't have a save file yet and when trying to load a non-existent save file it gives me an error about a missing string.
See attached picture for the error.

So my goal is to make an MMR system that automatically saves/loads. I wanted it to work like this:
When the Host picks Ranked mode the Load trigger is called, loading each player's MMR.
A moment after that each player's MMR is subtracted by 25 and Saved automatically.
When a player wins the game their MMR is increased by 50 and Saved automatically.

This results in Losing/Leaving players receiving -25 MMR and Winning players receiving 25 MMR.

But the automatic loading doesn't work unless they already have a file. I guess it has something to do with file:load function.
 

Attachments

  • error.png
    error.png
    61.7 KB · Views: 28
Last edited:
Level 14
Joined
Feb 7, 2020
Messages
387
I think this version should fix the issue with trying to load an empty file.

This is the new load function in case you changed anything in it:
Lua:
function file:load(player, _file_slot, _timeout)
    local file_slot  = _file_slot or 0
    if is_local(player) then
        Preloader(file.folder.."\\"..file.name..tostring(file_slot)..".pld")
        file:read(player)
    end
    if file.data and file.data ~= "" then
        if file_slot <= file.capacity then
            if not file.in_progress then
                file.player      = player
                local timeout    = _timeout or 1.5
                local pid        = pnum(player)
                file.in_progress = true
                file:reset_abils(player)
                debug(function()
                    timed(timeout, function()
                        if not udg_save_load_hero[pid] and file.in_progress then
                            file.in_progress = false
                            if is_local(player) then
                                print("Load Timeout: Failed to load character from file slot "..tostring(file_slot)..". File is corrupt.")
                            end
                        end
                    end)
                    if is_local(player) then
                        file:sync()
                    end
                    file.in_progress = false
                end)
            elseif is_local(player) then
                print(msg.on_conflict)
            end
        elseif is_local(player) then
            print(msg.over_capacity)
        end
        return true
    else
        return false
    end
end

The file:read portion now runs first followed by a check on the data read from the selected _file_slot.

If this doesn't fix it I'll need you to DM me the map file so I can see the compiled script and debug error.
 

Attachments

  • lua_save_load_v1.0b.w3m
    54.5 KB · Views: 15

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,535
Thank you! Is the file:load function the only thing you changed? Or should I re-import the entire system?

I'm messing around with it now, I'll let you know how it goes.

Edit: So far so good in singleplayer tests. I'll be testing it with my friend sometime tomorrow if not sooner.
 
Last edited:
Level 2
Joined
Feb 13, 2020
Messages
19
Planetary, is there any chance I can get your Discord? I'll start a 3-man group conversation to help find a solution. If we're constantly awaiting single responses on a forum it's bound to take a long time. If you don't mind, of course! Thanks a billion for your efforts already.
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,535
I tested this version with multiplayer and I think the desync is fixed. Changed around the empty check.

You just need to merge over the new file:add_sync_action() and file:load(player, _file_slot, _timeout) functions.
The desyncing stopped with this new version but I'm back to square one with it failing to load between games when online. I'm probably just doing something wrong... I hate to ask for more favors after you've helped so much but would you be willing to send me a version with only save Gold implemented? None of the hero stuff or other resources, just the ability to save player gold. I've tried this myself and I was sure I did it right but alas no luck. Part of me thinks this is the root of the problem.

Also, could it have anything to do with me using a Pick Every Player function to save/load every player all at once? Does that cause syncing issues? Should I try to space things out with a Timer or something? Again, thanks for all of the help and if it seems like too much work then don't worry about it.
 
Level 14
Joined
Feb 7, 2020
Messages
387
The desyncing stopped with this new version but I'm back to square one with it failing to load between games when online. I'm probably just doing something wrong... I hate to ask for more favors after you've helped so much but would you be willing to send me a version with only save Gold implemented? None of the hero stuff or other resources, just the ability to save player gold. I've tried this myself and I was sure I did it right but alas no luck. Part of me thinks this is the root of the problem.

Also, could it have anything to do with me using a Pick Every Player function to save/load every player all at once? Does that cause syncing issues? Should I try to space things out with a Timer or something? Again, thanks for all of the help and if it seems like too much work then don't worry about it.

I added options to disable each individual save group. Below is a template that I tried out which only saved gold successfully.

I cannot speak to your issue with loading files in different games. I am currently not able to duplicate that problem. Is this on 1.32?

If the file is present for a player, it should load with the file:load function or fail quietly.

Lua:
    -- save player state settings:
    char.save_unit       = false
    char.save_gold       = true
    char.save_lumber     = false
    char.save_food       = false
    char.save_items      = true -- requires char.save_unit
    char.save_abils      = true -- requires char.save_unit
    char.save_xp         = true -- requires char.save_unit
    char.save_attr       = true -- requires char.save_unit
    char.save_levels     = true -- requires char.save_unit

Alternatively, a quick Discord group conversation should fix this right up. Am I right, guys?

I'm jumping on this when I have time and when the debugging mood allows. I prefer to not have another Discord distraction.

edit: version e nullifies data on save when certain features get disabled.
 

Attachments

  • lua_save_load_v1.0d.w3m
    55 KB · Views: 13
  • lua_save_load_v1.0e.w3m
    55.5 KB · Views: 13
Last edited:

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,535
I'll be testing this with Ravecharmer tomorrow. Thank you for all of the help and don't worry about the Discord I'm sure we'll have this sorted out soon enough. I'm fairly confident this time it will work, I think I found some errors on my end and fixed them.
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,535
I'm on 1.32 as well.

So the saving/loading is working properly aside from one last issue, the data seems to be getting mixed up during the loading process. I've tested it many times with Ravecharmer and we keep loading each other's save files.

I know this has something to do with the fact that I'm using a For Loop to save/load each player all in one go because when manually typing in -save/-load it works without any issues (although we intentionally spaced out our command timings while testing these so...)

What I have right now that seems to be working is a 1 second timer delay between saving/loading each player. This produces the proper load results.

That being said, I'm not entirely sure if it's just saving that needs the delay, or loading, or both but I'm using it for both just to be safe.

I wonder if instead of using a static 1 second timer it would be better/faster to catch when the syncing process is finished for a save/load and begin the process for the next player, maybe after a short 0.1s delay or something. It's weird but these delays seem to help. In other words, I want to lessen the delay to as low as possible, because having to wait 1 second per player is a bit rough and I imagine the syncing process could last longer than that.
 
Last edited:
Level 14
Joined
Feb 7, 2020
Messages
387
Hmm.

I overlooked an issue with a timeout feature to prevent bad files from stalling the system, which is why you had to add a delay. While a file is loading, the system is temporarily disabled (which is why trying to instantly load all 10 files causes issues). I suspect the rapid loop was also mixing up the sent sync data which caused the wrong files to load if they did manage to get over the timeout. This is a new area for me in figuring out what could go wrong with loading so quickly using the BlzSendSyncData function.

I reworked the timeout to a minimum of 0.03 sec. Attached a version with an -all command that I tested in multiplayer which rapidly loads file 0 for each player successfully without desyncing (at least with 2 players present, might be issues with more).
 

Attachments

  • lua_save_load_v1.0f.w3m
    56.1 KB · Views: 11

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,535
We just can't seem to get this to work consistently. With 3 people testing, we played a few games and the loading seemed to work going from the 1st to the 2nd game, but the 3rd game rolled around and only Player 1 loaded. Maybe I need to increase the Wait duration even more. Here are the triggers I'm using to save/load.

This runs about 15 seconds into the game:
  • Load
    • Events
    • Conditions
    • Actions
      • -------- LOAD: --------
      • For each (Integer int) from 1 to 4, do (Actions)
        • Loop - Actions
          • Wait ((Real(int)) x 0.06) seconds
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • ((Player(int)) controller) Equal to User
              • ((Player(int)) slot status) Equal to Is playing
            • Then - Actions
              • Custom script: udg_save_file_slot[udg_int] = 0
              • Custom script: file:load( Player(udg_int-1), 0, 0.03 )
            • Else - Actions
      • Wait 2.00 seconds
      • -------- SAVE: --------
      • For each (Integer int) from 1 to 4, do (Actions)
        • Loop - Actions
          • Wait ((Real(int)) x 0.06) seconds
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • ((Player(int)) controller) Equal to User
              • ((Player(int)) slot status) Equal to Is playing
            • Then - Actions
              • Custom script: udg_save_file_slot[udg_int] = 0
              • Custom script: file:save( Player(udg_int-1), 0 )
            • Else - Actions
This runs at the end of the game after a team has won:
  • Save Winners
    • Events
    • Conditions
    • Actions
      • -------- SAVE: --------
      • For each (Integer int) from 1 to 4, do (Actions)
        • Loop - Actions
          • Wait ((Real(int)) x 0.06) seconds
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • ((Player(int)) controller) Equal to User
              • ((Player(int)) slot status) Equal to Is playing
            • Then - Actions
              • Custom script: udg_save_file_slot[udg_int] = 0
              • Custom script: file:save( Player(udg_int-1), 0 )
            • Else - Actions
I cut out most of the Actions but it was all just Arithmetic determining how much MMR should increase/decrease. I'm pretty confident that stuff isn't related to the problem.

It seems like swapping teams and thus changing player numbers between games causes problems but it could just be a coincidence. Just tested again and saving/loading worked going from game 1 to game 2, but on game 3 (after swapping teams) it mixed up the loading again.

Anyway, thanks for the constant updates.
 
Last edited:
Level 14
Joined
Feb 7, 2020
Messages
387
We just can't seem to get this to work consistently. With 3 people testing, we played a few games and the loading seemed to work going from the 1st to the 2nd game, but the 3rd game rolled around and only Player 1 loaded. Maybe I need to increase the Wait duration even more. Here are the triggers I'm using to save/load.

This runs about 15 seconds into the game:
  • Load
    • Events
    • Conditions
    • Actions
      • -------- LOAD: --------
      • For each (Integer int) from 1 to 4, do (Actions)
        • Loop - Actions
          • Wait ((Real(int)) x 0.06) seconds
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • ((Player(int)) controller) Equal to User
              • ((Player(int)) slot status) Equal to Is playing
            • Then - Actions
              • Custom script: udg_save_file_slot[udg_int] = 0
              • Custom script: file:load( Player(udg_int-1), 0, 0.03 )
            • Else - Actions
      • Wait 2.00 seconds
      • -------- SAVE: --------
      • For each (Integer int) from 1 to 4, do (Actions)
        • Loop - Actions
          • Wait ((Real(int)) x 0.06) seconds
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • ((Player(int)) controller) Equal to User
              • ((Player(int)) slot status) Equal to Is playing
            • Then - Actions
              • Custom script: udg_save_file_slot[udg_int] = 0
              • Custom script: file:save( Player(udg_int-1), 0 )
            • Else - Actions
This runs at the end of the game after a team has won:
  • Save Winners
    • Events
    • Conditions
    • Actions
      • -------- SAVE: --------
      • For each (Integer int) from 1 to 4, do (Actions)
        • Loop - Actions
          • Wait ((Real(int)) x 0.06) seconds
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • ((Player(int)) controller) Equal to User
              • ((Player(int)) slot status) Equal to Is playing
            • Then - Actions
              • Custom script: udg_save_file_slot[udg_int] = 0
              • Custom script: file:save( Player(udg_int-1), 0 )
            • Else - Actions
I cut out most of the Actions but it was all just Arithmetic determining how much MMR should increase/decrease. I'm pretty confident that stuff isn't related to the problem.

It seems like swapping teams and thus changing player numbers between games causes problems but it could just be a coincidence. Just tested again and saving/loading worked going from game 1 to game 2, but on game 3 (after swapping teams) it mixed up the loading again.

I'm wondering if increasing it to 0.12 sec and making it a timer instead of a wait step will help. It's odd that it stops working after a specific game count, I wonder if that was a coincidence and that it will rather sometimes fail because of variable connection shenanigans.
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,535
I can try to revert back to the Timer approach again. This is what I was doing before (I deleted anything I didn't change from the code just for this post):
Lua:
file = {}
nextSaveLoadPlayer = -1 -- used to queue up the players when saving/loading

function file:load(player, _file_slot, _timeout)
    local file_slot  = _file_slot or 0
    if file_slot <= file.capacity then
        if not file.in_progress then
            file.player      = player
            local timeout    = _timeout or 1.5
            local pid        = pnum(player)
            file.in_progress = true
            file:reset_abils(player)
            debug(function()
                timed(timeout, function()
                    if not udg_save_load_hero[pid] and file.in_progress then
                        file.in_progress = false
                        if is_local(player) then
                            print("Load Timeout: Failed to load character from file slot "..tostring(file_slot)..". File is corrupt.")
                        end
                    end
                end)
                if is_local(player) then
                    Preloader(file.folder.."\\"..file.name..tostring(file_slot)..".pld")
                    if file:read(player) then
                        file:sync()
                    else
                        file.data = ""
                    end
                end
                file.in_progress = false
                GetNextSaveLoadPlayer(false) -- true = save / false = load
            end)
        elseif is_local(player) then
            print(msg.on_conflict)
        end
    elseif is_local(player) then
        print(msg.over_capacity)
    end
end

function file:save(player, _file_slot)
    local file_slot = _file_slot or 0
    if file_slot <= file.capacity then
        if not file.in_progress then
            file.in_progress = true
            local seed = ""
            for _ = 1,12 do
                seed = seed..tostring(math.random(1,5)) -- running this locally causes a desync.
            end
            if is_local(player) then
                save_char = char:save(player, udg_save_load_hero[pnum(player)])
                file:reset_abils(player)
                -- because of the 259 character limit in a preload line, we must break each string into groups.
                save_char:save_data()
                local data, _, sum = encrypt:basic(save_char.data, seed, true)
                save_char.metadata = save_char.metadata..seed..","..tostring(sum)..file.meta_delimiter
                local chunk_size, pos, posend  = 200, 0, 0
                local chunk_count = math.ceil(string.len(data)/chunk_size)
                PreloadGenClear()
                PreloadGenStart()
                Preload("\")\ncall BlzSetAbilityTooltip('"..file.abil[1].."',\""..save_char.metadata.."\",".."0"..")\n//")
                if chunk_count > 1 then
                    for chunk = 2,chunk_count do
                        pos = 1 + ((chunk-1)*chunk_size)
                        posend = chunk*chunk_size
                        if chunk < chunk_count then
                            Preload("\")\ncall BlzSetAbilityTooltip('"..file.abil[chunk].."',\""..string.sub(data, pos, posend).."\",".."0"..")\n//")
                        elseif chunk == chunk_count then
                            Preload("\")\ncall BlzSetAbilityTooltip('"..file.abil[chunk].."',\""..string.sub(data, pos, posend).."\",".."0"..")\n//")
                        end
                    end
                else
                    Preload("\")\ncall BlzSetAbilityTooltip('"..file.abil[2].."',\""..data.."\",".."0"..")\n//")
                end
                Preload("\" )\nendfunction\nfunction a takes nothing returns nothing\n //")
                PreloadGenEnd(file.folder.."\\"..file.name..tostring(file_slot)..".pld")
                --print(msg.on_save)
                save_char = nil
            end
            seed = nil
            file.in_progress = false
            GetNextSaveLoadPlayer(true) -- true = save / false = load
        elseif is_local(player) then
            print(msg.on_conflict)
        end
    elseif is_local(player) then
        print(msg.over_capacity)
    end
end

-- this will run inside the file:save / file:load functions
function GetNextSaveLoadPlayer(saveGame)
    nextSaveLoadPlayer = nextSaveLoadPlayer + 1
    local p = Player(nextSaveLoadPlayer)

    if GetPlayerSlotState(p) == PLAYER_SLOT_STATE_PLAYING and GetPlayerController(p) == MAP_CONTROL_USER then
        local t1 = CreateTimer()
        TimerStart(t1, 1.0, false, function()
            PauseTimer(t1)
            DestroyTimer(t1)

            if saveGame then
                file:save(p, 0)
            else
                file:load(p, 0, 1.5)
            end
        end)
    elseif nextSaveLoadPlayer < 3 then
        -- try again if that player didn't exist
        GetNextSaveLoadPlayer(saveGame)
    end
end

-- call these from your GUI triggers to start the save/load process
function SaveTheGame()
    nextSaveLoadPlayer = -1
    GetNextSaveLoadPlayer(true) -- save
end

function LoadTheGame()
    nextSaveLoadPlayer = -1
    GetNextSaveLoadPlayer(false) -- load
end
I was then running SaveTheGame() and LoadTheGame() in my GUI triggers.
 
Last edited:
Level 14
Joined
Feb 7, 2020
Messages
387
I can try to revert back to the Timer approach again. This is what I was doing before (I deleted anything I didn't change from the code just for this post):
Lua:
file = {}
nextSaveLoadPlayer = -1 -- used to queue up the players when saving/loading

function file:load(player, _file_slot, _timeout)
    local file_slot  = _file_slot or 0
    if file_slot <= file.capacity then
        if not file.in_progress then
            file.player      = player
            local timeout    = _timeout or 1.5
            local pid        = pnum(player)
            file.in_progress = true
            file:reset_abils(player)
            debug(function()
                timed(timeout, function()
                    if not udg_save_load_hero[pid] and file.in_progress then
                        file.in_progress = false
                        if is_local(player) then
                            print("Load Timeout: Failed to load character from file slot "..tostring(file_slot)..". File is corrupt.")
                        end
                    end
                end)
                if is_local(player) then
                    Preloader(file.folder.."\\"..file.name..tostring(file_slot)..".pld")
                    if file:read(player) then
                        file:sync()
                    else
                        file.data = ""
                    end
                end
                file.in_progress = false
                GetNextSaveLoadPlayer(false) -- true = save / false = load
            end)
        elseif is_local(player) then
            print(msg.on_conflict)
        end
    elseif is_local(player) then
        print(msg.over_capacity)
    end
end

function file:save(player, _file_slot)
    local file_slot = _file_slot or 0
    if file_slot <= file.capacity then
        if not file.in_progress then
            file.in_progress = true
            local seed = ""
            for _ = 1,12 do
                seed = seed..tostring(math.random(1,5)) -- running this locally causes a desync.
            end
            if is_local(player) then
                save_char = char:save(player, udg_save_load_hero[pnum(player)])
                file:reset_abils(player)
                -- because of the 259 character limit in a preload line, we must break each string into groups.
                save_char:save_data()
                local data, _, sum = encrypt:basic(save_char.data, seed, true)
                save_char.metadata = save_char.metadata..seed..","..tostring(sum)..file.meta_delimiter
                local chunk_size, pos, posend  = 200, 0, 0
                local chunk_count = math.ceil(string.len(data)/chunk_size)
                PreloadGenClear()
                PreloadGenStart()
                Preload("\")\ncall BlzSetAbilityTooltip('"..file.abil[1].."',\""..save_char.metadata.."\",".."0"..")\n//")
                if chunk_count > 1 then
                    for chunk = 2,chunk_count do
                        pos = 1 + ((chunk-1)*chunk_size)
                        posend = chunk*chunk_size
                        if chunk < chunk_count then
                            Preload("\")\ncall BlzSetAbilityTooltip('"..file.abil[chunk].."',\""..string.sub(data, pos, posend).."\",".."0"..")\n//")
                        elseif chunk == chunk_count then
                            Preload("\")\ncall BlzSetAbilityTooltip('"..file.abil[chunk].."',\""..string.sub(data, pos, posend).."\",".."0"..")\n//")
                        end
                    end
                else
                    Preload("\")\ncall BlzSetAbilityTooltip('"..file.abil[2].."',\""..data.."\",".."0"..")\n//")
                end
                Preload("\" )\nendfunction\nfunction a takes nothing returns nothing\n //")
                PreloadGenEnd(file.folder.."\\"..file.name..tostring(file_slot)..".pld")
                --print(msg.on_save)
                save_char = nil
            end
            seed = nil
            file.in_progress = false
            GetNextSaveLoadPlayer(true) -- true = save / false = load
        elseif is_local(player) then
            print(msg.on_conflict)
        end
    elseif is_local(player) then
        print(msg.over_capacity)
    end
end

-- this will run inside the file:save / file:load functions
function GetNextSaveLoadPlayer(saveGame)
    nextSaveLoadPlayer = nextSaveLoadPlayer + 1
    local p = Player(nextSaveLoadPlayer)

    if GetPlayerSlotState(p) == PLAYER_SLOT_STATE_PLAYING and GetPlayerController(p) == MAP_CONTROL_USER then
        local t1 = CreateTimer()
        TimerStart(t1, 1.0, false, function()
            PauseTimer(t1)
            DestroyTimer(t1)

            if saveGame then
                file:save(p, 0)
            else
                file:load(p, 0, 1.5)
            end
        end)
    elseif nextSaveLoadPlayer < 3 then
        -- try again if that player didn't exist
        GetNextSaveLoadPlayer(saveGame)
    end
end

-- call these from your GUI triggers to start the save/load process
function SaveTheGame()
    nextSaveLoadPlayer = -1
    GetNextSaveLoadPlayer(true) -- save
end

function LoadTheGame()
    nextSaveLoadPlayer = -1
    GetNextSaveLoadPlayer(false) -- load
end
I was then running SaveTheGame() and LoadTheGame() in my GUI triggers.
Any luck getting it to work or same symptoms?
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,535
It's weird, in 2 player games it seems to work fine. It's when we introduce a 3rd player or more that it fails.

But we're in the process of testing the system in a blank map I made to rule out any weirdness. So far the new map works fine with 2 players but we've yet to try 3+.

I'll reply as soon as we get results with 3+ players.
 
Level 14
Joined
Feb 7, 2020
Messages
387
I was able to load 4 players in a LAN game with some tweaked settings after I did finally get it to bug out and not load player 3 at random intervals. Increasing the delay seemed to solve it. So I do think it's something with the sync function but unclear how to fix.

  • DemoLoadAll
    • Events
      • Player - Player 1 (Red) types a chat message containing -all as An exact match
    • Conditions
    • Actions
      • For each (Integer A) from 1 to 10, do (Actions)
        • Loop - Actions
          • Set VariableSet int = (Integer A)
          • Wait ((Real(int)) x 0.10) seconds
          • Custom script: udg_save_file_slot[udg_int] = 0
          • Custom script: file:load( Player(udg_int-1), udg_save_file_slot[udg_int], 0.08 )
 

Attachments

  • 4_loaded.jpg
    4_loaded.jpg
    173.6 KB · Views: 14
  • lua_save_load_v1.0g.w3m
    56.1 KB · Views: 19
Level 24
Joined
Jun 26, 2020
Messages
1,852
If someone is still interested I translated the TriggerHappy's Codeless SaveLoad System to Lua, I don't publish it because I don't know how safe is, and I didn't tested it in multiplayer, can you test it? (I don't think this have compatibility with the Jass version, so don't try to use it in a previously Jass map)
 

Attachments

  • Lua Codeless Save Load v1.0.w3x
    176.6 KB · Views: 12
Level 4
Joined
Jul 31, 2019
Messages
19
Herly inspired me to make one based on FileIO by @Trokkin and this post by @ScrewTheTrees [Lua/Typescript] "Codeless" Saving/Synchronized loading of huge amounts of data.

It hasn't had any load testing on it but has been trivially verified to work in multiplayer.

Added some notes to the FileIO script mentioning it was built primarily by the aforementioned folks and had been butchered into a combination of both of their works.
Not done yet but if anyone wants something that kind of works this might get them where they need.
Based on the ability to stringify/load lua tables. load() portion not implemented in this example yet.


Edit:
Since you seem to only be able to load a file once, I'll be updating the save function to also update local state for whatever loading does too, hopefully automagically, but may need to be implemented manually.
Edit 2:
Just grabbed the entire bee movie script from ScrewTheTrees's github and it saves and loads it just fine in 401 chunks, load time was less than a second.
Edit 3:
96kb of data loads unreliably in multiplayer, probably want to keep files at a reasonable size!
 

Attachments

  • LuaCodelessSaveLoad.w3m
    32 KB · Views: 4
Last edited:
Level 8
Joined
Jan 23, 2015
Messages
121
jc, @Rhobox you've beat me by a couple of days. I've had full lua package already, just wasnt't polished yet to publish it. But ok well it's nice to see ppl's interest.

FYI Blizzard fixed the bug that blocked consequitive file loading, if that's what you're talking about. Otherwise I didn't met problems with loading the file several times, although it is never needed in the actual production.

I'll work on a proper syncing library too, and combine it into a FileIOAsync, when it's ready. So that Bee Movie and other big data can be reliably used for maps.
 
Level 4
Joined
Jul 31, 2019
Messages
19
Hmm i cant get it to load the same resource more than once per game.

I also experimented with a system to split the BlzSendSync calls across several files as it seems like more than 75kb ish greatly increases the chance of desync (ping spike records about 200ms at 75kb abd about 3000 to 13000 at 95kb with two players)

Going to clean that post up and add a couple use case examples, interested to see how your system compares when you're ready!

Note:
No difference was observed for split files, overhead would likely just increase the data you need to transfer to no benefit

Note 2: When loading multiple times I mean load, save new data, load, file is cached from first load and doesnt register disk changes.

Might be missing a necessary call, but resources on these Blizzard functions are hard to find
 
Last edited:
Level 8
Joined
Jan 23, 2015
Messages
121
I also experimented with a system to split the BlzSendSync calls across several files as it seems like more than 75kb ish greatly increases the chance of desync (ping spike records about 200ms at 75kb abd about 3000 to 13000 at 95kb with two players)

Note:
No difference was observed for split files, overhead would likely just increase the data you need to transfer to no benefit
Seems obvious you shouldn't send loads of data at once. Set up a timer, spread it even across some seconds -- but that's stuff for an entire separate system. But it's worth it though. I'm still going to make my own, even if I find a sufficient one.

Splitting files shouldn't affect the data transfer at all, since you effectively join the data when sending it in one burst. Try sending one file at a time, should increase the performance.

Going to clean that post up and add a couple use case examples, interested to see how your system compares when you're ready!
No value in it being held hostage on my hard drive. Here you go, attached it. Same filenames though lol. Note, the systems there are a bit older than what I've posted already.

Note 2: When loading multiple times I mean load, save new data, load, file is cached from first load and doesnt register disk changes.

Might be missing a necessary call, but resources on these Blizzard functions are hard to find
You sure you're updated? AFAIK that was adressed in 1.35 patch
 

Attachments

  • LuaCodelessSaveLoad.w3x
    178.7 KB · Views: 11
Level 4
Joined
Jul 31, 2019
Messages
19
You sure you're updated? AFAIK that was adressed in 1.35 patch
I appear to be updated, I'll take a look at yours.

Splitting files shouldn't affect the data transfer at all, since you effectively join the data when sending it in one burst. Try sending one file at a time, should increase the performance.
Yeah I am using the code injection style for lua so it would load a file and immediately invoke the code to send sync messages. Could add a pause between files if I wanted to get it to work but right now with 2 players it reliably and seems to fairly safely load up ~2500 lines of text which is probably more than any stringified lua table you'd save data in would ever really need.
 
Level 8
Joined
Jan 23, 2015
Messages
121
Yeah I am using the code injection style for lua so it would load a file and immediately invoke the code to send sync messages. Could add a pause between files if I wanted to get it to work but right now with 2 players it reliably and seems to fairly safely load up ~2500 lines of text which is probably more than any stringified lua table you'd save data in would ever really need.
Wait, I thought you used my FileIO? It doesn't do code injection.

Also, check out my Bit Buffer, and then there's unfinished Object64 lib in the map, they help pack data even tighter, in case someone ever does save an entire game world or smth to that save file. It is possible, lol, with that kind of compression
 
Level 4
Joined
Jul 31, 2019
Messages
19
Wait, I thought you used my FileIO? It doesn't do code injection.
I used yours as a base and then fairly heavily modified it. Its a mess of useful and non useful pieces at the moment.

My current version i will clean up after a couple tests from what you just posted will skip loading if a player has already loaded and refer to the save object that doesn't yet exist.


Also, check out my Bit Buffer, and then there's unfinished Object64 lib in the map, they help pack data even tighter, in case someone ever does save an entire game world or smth to that save file. It is possible, lol, with that kind of compression
I definitely will. I found a base64 encoding library on github that ive been using and indexing chunks with two 32 bit values for current and max like ScrewTheTrees did. Worked great for file splitting.

I was loathe to add waits between file loads due to corruption or people trying to access it and entering a race condition but could just get around that with state flags i suppose.

Will take a look at these later today.

If we want to get really silly I could write a compression algorithm that compresses variable names or whatever and stores a base64 encoded library at the top but that seems unnecessary.


Little side note:
Im on mobile right now so i cant be bothered to find the thread, but someone was talking about code injection with !begincode being orders of magnitude faster due to how Warcraft handles preload as JASS and converts via some jass2lua function
 
Last edited:
Level 4
Joined
Jul 31, 2019
Messages
19
Been playing around with repeat file loads on the map you sent as well. Saves repeatedly just fine, but when PlayerState.Load() is invoked repeatedly it just loads the first instance of when it was called. Not a big deal since repeatedly loading probably shouldn't happen, but are you sure this isn't happening on your end as well @Trokkin?
 
Level 4
Joined
Jul 31, 2019
Messages
19
Pointing @GodPoro your way as someone who is probably interested in this. Also, I'll see if I can get one of these systems working in my map soon. I haven't worked on it in a while but it sounds like I can at least "finish it" by finally having a working save/load system, cheers!
If you use mine feel free to shoot me a message about it whenever if you need anything.
I think @Trokkin is closer to having it work for GUI than I am, though
 
Level 4
Joined
Jul 31, 2019
Messages
19
I'm beginning to look into it now, can anyone confirm that their system is 100% working or at least seems stable so far? I'm over here wondering which of these systems to use, lol.
Mine has been tested in multiplayer with a couple people, but doesn't have GUI integration.
Works fine in large files, used by just throwing a lua table of data at it for each player.

Trokkin's was designed to make the file as small as possible, but is done so using a specified bit-width, which could be ignored perhaps but is likely to be a bit annoying in my opinion, but is much closer to GUI support.
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,535
Mine has been tested in multiplayer with a couple people, but doesn't have GUI integration.
Works fine in large files, used by just throwing a lua table of data at it for each player.

Trokkin's was designed to make the file as small as possible, but is done so using a specified bit-width, which could be ignored perhaps but is likely to be a bit annoying in my opinion, but is much closer to GUI support.
I'm currently testing Trokkin's design since his map was already setup for GUI usage and had examples to go off of. I tried using your system but I couldn't figure out how to Load a save slot. I had saving working, just not loading.

So far his system seems to work fine in my 2 player LAN test, but I still need to test it with 4 people in the same game online.

Anyway, I don't actually need GUI examples/integration as long as the Lua isn't too far over my head :p

If you wish to provide an example of your system that saves/loads a few values for Players 1-4 that'd be awesome.

I have 4 GUI variables I want to save, they're all Integers:
MMR[]
MMR_Wins[]
MMR_Losses[]
MMR_GamesPlayed[]

The [index] is the Player Number, indexed starting at 1.

I have a GUI player group called ActiveUsers that always contains the active players in the game if that helps. Thanks!
 
Level 4
Joined
Jul 31, 2019
Messages
19
I'm currently testing Trokkin's design since his map was already setup for GUI usage and had examples to go off of. I tried using your system but I couldn't figure out how to Load a save slot. I had saving working, just not loading.

So far his system seems to work fine in my 2 player LAN test, but I still need to test it with 4 people in the same game online.

Anyway, I don't actually need GUI examples/integration as long as the Lua isn't too far over my head :p

If you wish to provide an example of your system that saves/loads a few values for Players 1-4 that'd be awesome.

I have 4 GUI variables I want to save, they're all Integers:
MMR
MMR_Wins
MMR_Losses
MMR_GamesPlayed

I have a GUI player group called ActiveUsers that always contains the active players in the game if that helps. Thanks!
I'll take a look at that sometime today and see if I can get an example working for you.
It's designed to just take a table and then reconstruct it as a table so I'll make sure the input and output are documented as well.
 
Level 4
Joined
Jul 31, 2019
Messages
19
I'm currently testing Trokkin's design since his map was already setup for GUI usage and had examples to go off of. I tried using your system but I couldn't figure out how to Load a save slot. I had saving working, just not loading.

So far his system seems to work fine in my 2 player LAN test, but I still need to test it with 4 people in the same game online.

Anyway, I don't actually need GUI examples/integration as long as the Lua isn't too far over my head :p

If you wish to provide an example of your system that saves/loads a few values for Players 1-4 that'd be awesome.

I have 4 GUI variables I want to save, they're all Integers:
MMR[]
MMR_Wins[]
MMR_Losses[]
MMR_GamesPlayed[]

The [index] is the Player Number, indexed starting at 1.

I have a GUI player group called ActiveUsers that always contains the active players in the game if that helps. Thanks!
Ok so turns out the file I sent you was AWFUL. Fixed some stuff in my other map and forgot to upload it.
Attached is a new one that actually has data, syncing and loading. All through lua tables.
Give it a go, haven't had anyone to test multiplayer with for the table state yet unfortunately (data in Initialization with some comments)

Ask any questions you need, dm me if you don't want to post here or however that works.
 

Attachments

  • LuaCodelessSaveLoad.w3m
    113.7 KB · Views: 13

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,535
Awesome, I'll check it out now. One thing I did notice is that you're using Player objects as the [index] in your tables. Isn't this prone to desyncs since the handle of the Player will be different for each client? I know this is an issue with Unit objects and as such requires you to stick with something like Unit Indexing. I could be wrong but I know my map still has some random desyncs and I'm pretty sure the issue lies in what I just described.

Edit: I've talked to some others and they said it was fine. It appears to only have to do with using these tables inside of a Pairs function, in which case there's a safe synced version available to use.
 
Last edited:
Top