Savegame UI / Gadget
Moderator: Moderators
Savegame UI / Gadget
I am working on a universal savegame system. Thus far, it writes all Units and Features to the savegame state, and records most of their Order state at the time of the save (subject to certain limitations). It uses a standard location, and the savegame loads after GameStart(), so it can erase stuff like World Builder or the Features on a map at the start of play and rebuild the game-state.
The big practical problem is that I need to build a UI for browsing / viewing / deleting savegames, so that it can record a savegame of BA vs. 3 Universal AIs, say, and 3 games of P.U.R.E. Pylon Wars, and the end-user can open the Widget and access the files.
So, each savegame needs a unique number, based on the game and the count of saves for that game. I need to rebuild the filenames if people perform deletions, so that if you have 5 saves and delete number 3, then it won't bork, or use a unique timestamp or other indicator.
So... would anybody be willing to help me out with this problem? I need:
1. Code for the browsing system- a box that displays a menu of saves, shows all the saves in the savegame folder, and if an end-user clicks on a given file, highlights that save and displays the relevant information in a textbox.
2. More generally, I need to know more about how io can be used to get the counts / filenames of the objects in the savegame folder.
I can build a strict, static, you-can-only-have X number of savegames system without all this, but to build a really good system, I need a much better UI, and I don't have a clue about how to go about that, so I'd like some help, if there are people who would be willing.
The big practical problem is that I need to build a UI for browsing / viewing / deleting savegames, so that it can record a savegame of BA vs. 3 Universal AIs, say, and 3 games of P.U.R.E. Pylon Wars, and the end-user can open the Widget and access the files.
So, each savegame needs a unique number, based on the game and the count of saves for that game. I need to rebuild the filenames if people perform deletions, so that if you have 5 saves and delete number 3, then it won't bork, or use a unique timestamp or other indicator.
So... would anybody be willing to help me out with this problem? I need:
1. Code for the browsing system- a box that displays a menu of saves, shows all the saves in the savegame folder, and if an end-user clicks on a given file, highlights that save and displays the relevant information in a textbox.
2. More generally, I need to know more about how io can be used to get the counts / filenames of the objects in the savegame folder.
I can build a strict, static, you-can-only-have X number of savegames system without all this, but to build a really good system, I need a much better UI, and I don't have a clue about how to go about that, so I'd like some help, if there are people who would be willing.
Last edited by Argh on 12 Jan 2010, 11:26, edited 1 time in total.
Re: Counting Files / Savegame UI
sure that sounds easy you already did the hard part. i could do this esp since ur game is commercial and i am out of a job you can pay me. starting salary just 20k euros per year very cheap labor for such skill as me!
Re: Counting Files / Savegame UI
Eh, if I could afford to offer people salaries for stuff, I'd be doing that, tbh. This particular project is meant as a fairly universal project, though, and I was kinda hoping that people would understand that.
Anyhow, I will get the save / load stuff working to the point that the UI is the last bit, post the code, and see whether people will bite at that point, if nobody's interested otherwise.
Anyhow, I will get the save / load stuff working to the point that the UI is the last bit, post the code, and see whether people will bite at that point, if nobody's interested otherwise.
Re: Counting Files / Savegame UI
Argh wrote:1. Code for the browsing system- a box that displays a menu of saves, shows all the saves in the savegame folder
Err, didn't you notice my savegame/mission menu already does that?Argh wrote:2. More generally, I need to know more about how io can be used to get the counts / filenames of the objects in the savegame folder.
Re: Counting Files / Savegame UI
I'll take a look at the UI side, I didn't test that part.
Re: Counting Files / Savegame UI
OK, took a look.
1. It uses a button system buried in the rest of your UI. I want a Widget that is a standalone program, completely modular (i.e., if you wanted to modify its position to fit into a larger UI schema, you'd just make minor changes to position). And I want a genuine slider menu, so that if you have 100 savegames, you can select the one you want, delete them if you no longer want them, etc.
2. It crashed Spring the first time I tested it.
1. It uses a button system buried in the rest of your UI. I want a Widget that is a standalone program, completely modular (i.e., if you wanted to modify its position to fit into a larger UI schema, you'd just make minor changes to position). And I want a genuine slider menu, so that if you have 100 savegames, you can select the one you want, delete them if you no longer want them, etc.
2. It crashed Spring the first time I tested it.
Re: Counting Files / Savegame UI
If by crash you mean closed down and not reload, that's because Spring.Restart doesn't handle spaces in Spring path.
Re: Counting Files / Savegame UI
No, I installed to j:/GUNDAM, it just plain crashed.
[ 5] The error was: [string "LuaRules/Gadgets/libs/dump_units_lib.lua"]:894: attempt to concatenate field '?' (a nil value)
Was the last error.
[ 5] The error was: [string "LuaRules/Gadgets/libs/dump_units_lib.lua"]:894: attempt to concatenate field '?' (a nil value)
Was the last error.
Re: Counting Files / Savegame UI
Sometimes it has problem when there are two many units with too many orders, can you give me that save? (/Missions/Gundam/SaveGame.txt or whichever save you loaded)
Last edited by zwzsg on 10 Jan 2010, 05:32, edited 1 time in total.
Re: Counting Files / Savegame UI
Sure. Best way to avoid that is to set ModOptions up so maxunits = some very large number.
- Attachments
-
- SaveGame.txt
- (13.02 KiB) Downloaded 113 times
Re: Counting Files / Savegame UI
It loaded, but it crashed a bit later: See, the timeframe:
Anyway, the issue was my system doesn't handle order given on units, because I haven't taken the time to debug my code that translate unitID. I manually removed that one "guard" command, and now it works.
Code: Select all
[ 0] GameID: e758494b736a853434615f1297174d8b
[ 0] Player added point: Start 0
[ 5] AssignOrder failed to decode:
[ 5] The error was: [string "LuaRules/Gadgets/libs/dump_units_lib.lua"]:894: attempt to concatenate field '?' (a nil value)
[ 12] Skirmish AI "Zeon: landmap (Enemy #1)" took over control of team 1
[ 19] Skirmish AI "Federation: landmap (Friend #1)" took over control of team 2
[ 71] Player paused the game
[ 71] Saved: screenshots/screen001.png
[ 71] Player unpaused the game
- Attachments
-
- SaveGame.txt
- Just removed that one GUARD command
- (13 KiB) Downloaded 110 times
Re: Counting Files / Savegame UI
Aha. Yeah, Guard is a problem. I forgot, it won't take a position parameter, kinda makes things tricky. You'd have to stack those commands into a queue and issue them a few gameframes later. I'm not bothering, it's not incredibly vital that Guard orders are preserved, imo.
Re: Counting Files / Savegame UI
OK, here we go. This is pretty much World Builder to write the savegames (except I fixed guard, and ofc it needs to erase everything, including old World Builder states, so it runs on gameframe 1 instead of gamestart).
Not done yet, but this writes the raw savegame.
Code: Select all
function widget:GetInfo()
return {
name = "Write Save Game",
desc = "Writes Save Game State.",
author = "Argh",
date = "January 8, 2010",
license = "(C) Wolfe Games, 2010, released under GPL v.2",
layer = 0,
enabled = true -- loaded by default?
}
end
local Editor = {
[UnitDefNames.world_builder_editor_daemon.id] = "world_builder_editor_daemon",
}
local Blocker = {
[UnitDefNames.blockernull.id] = "blockernull",
}
local CMD_SAVE_GAME = 32730
local f
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
local function copyCommandQueue(u,x,y,z)
local states = Spring.GetUnitStates(u)
local state
if (states ~= nil) then
state = states.firestate
f:write('GiveOrderToUnit(u, CMD.FIRE_STATE,{'..state..'}, 0)\n')
state = states.firestate
f:write('GiveOrderToUnit(u, CMD.MOVE_STATE,{'..state..'}, 0)\n')
state = states["repeat"] and 1 or 0
f:write('GiveOrderToUnit(u, CMD.REPEAT,{'..state..'}, 0)\n')
state = states.active and 1 or 0
f:write('GiveOrderToUnit(u, CMD.ONOFF,{'..state..'}, 0)\n')
state = states.cloak and 1 or 0
f:write('GiveOrderToUnit(u, CMD.CLOAK,{'..state..'}, 0)\n')
state = states.trajectory and 1 or 0
f:write('GiveOrderToUnit(u, CMD.TRAJECTORY,{'..state..'}, 0)\n')
end
local queue = Spring.GetCommandQueue(u);
local GUARD = CMD.GUARD
if (queue ~= nil) then
local length = # queue
for k,v in ipairs(queue) do -- in order
local opts = v.options
if (not opts.internal) and v.id ~= GUARD then -- don't record GUARD orders
if k == 1 and v.id == 5 then
-- don't write a Wait that's the first order
else
f:write('GiveOrderToUnit(u,'..v.id..',{')
for i,k in ipairs(v.params) do
f:write(k..',')
end
f:write('},'..opts.coded..')\n')
end
end
end
end
local buildqueue = Spring.GetRealBuildQueue(u)
if (buildqueue ~= nil) then
for udid,buildPair in ipairs(buildqueue) do
local udid, count = next(buildPair, nil)
f:write('Spring.AddBuildOrders(u,'..udid..','..count..')\n')
end
end
end
function widget:CommandNotify(name)
if (name == CMD_SAVE_GAME) then
f = io.open('mods/savegames.sdd/savegame.lua', 'w+')
if (f) then
f:write('function gadget:GetInfo()\n')
f:write(' return {\n')
f:write(' name = "SaveGame",\n')
f:write(' desc = "Save Game State.",\n')
f:write(' author = "Argh",\n')
f:write(' date = "January 8, 2010",\n')
f:write(' license = "GPL, v.2, 2010",\n')
f:write(' layer = 1,\n')
f:write(' enabled = true,\n')
f:write(' }\n')
f:write('end\n')
f:write('\n\n')
f:write('local u\n')
f:write('local validTable = {}\n')
f:write('local CreateUnit = Spring.CreateUnit\n')
f:write('local GetGaiaTeamID = Spring.GetGaiaTeamID\n')
f:write('local GiveOrderToUnit = Spring.GiveOrderToUnit\n')
f:write('local AddBuildOrders = Spring.AddBuildOrders\n')
f:write('local CreateFeature = Spring.CreateFeature\n')
f:write('\n\n')
f:write('function gadget:GameFrame(f)\n')
f:write('\n')
f:write('if (gadgetHandler:IsSyncedCode()) then\n')
f:write('if f == 1 then\n')
-----------------------------------------------------------------------------------------------------------------Start Feature Writing
local myFeatures = Spring.GetAllFeatures()
--Spring.Echo(myFeatures[1])
if myFeatures[1] ~= nil then
for _,u in ipairs(myFeatures) do
local ud = Spring.GetFeatureDefID(u)
Spring.Echo("Found Feature",u,FeatureDefs[ud].name)
local x,y,z = Spring.GetFeaturePosition(u)
local heading = Spring.GetFeatureHeading(u)
if heading == 16384 then heading = 1 end
if heading == 32767 then heading = 2 end
if heading == -16384 then heading = 3 end
local Featurename = FeatureDefs[ud].name
f:write('Spring.CreateFeature("'..Featurename..'",'..math.floor(x)..','..math.floor(y)..','..math.floor(z)..','..heading..')\n')
end
end
-----------------------------------------------------------------------------------------------------------------End Feature Writing
-----------------------------------------------------------------------------------------------------------------Start Unit Writing
local GetUnitTeam = Spring.GetUnitTeam
for _,u in ipairs(Spring.GetAllUnits()) do
if Editor[Spring.GetUnitDefID(u)] or Blocker[Spring.GetUnitDefID(u)] then
--Do nothing, this is our editor or a Blocker Null, duh
else
local ud = Spring.GetUnitDefID(u)
local x,y,z = Spring.GetUnitBasePosition(u)
local heading = Spring.GetUnitHeading(u)
local saveheading = heading
if heading == 16384 then heading = 1 end
if heading == 32767 then heading = 2 end
if heading == -16384 then heading = 3 end
if heading ~= 1 and heading ~= 2 and heading ~= 3 then
heading = 0
end
local Unitname = UnitDefs[ud].name
f:write('u = CreateUnit("'..Unitname..'",'..x..','..y..','..z..','..heading..','..GetUnitTeam(u)..')\n')
f:write('table.insert(validTable,u,1)\n')
if UnitDefs[ud].canMove or UnitDefs[ud].canFly then
if saveheading ~= 0 and saveheading ~= 16384 and saveheading ~= 32767 and saveheading ~= -16384 then
f:write('Spring.MoveCtrl.Enable(u)\nSpring.MoveCtrl.SetRotation(u,0,'..saveheading..',0)\nSpring.MoveCtrl.Disable(u)\n')
end
end
local myqueue = Spring.GetCommandQueue(u);
if myqueue ~= nil then
copyCommandQueue(u,x,y,z)
end
end
end
-----------------------------------------------------------------------------------------------------------------Destroy all old Units
f:write('for a,b in ipairs(Spring.GetAllUnits()) do\n')
f:write('if validTable[b] == nil then\n')
f:write('Spring.DestroyUnit(b,false,true)\n')
f:write('end\n')
f:write('end\n')
-----------------------------------------------------------------------------------------------------------------End Unit Writing
f:write('gadgetHandler.RemoveGadget("SaveGame")\n')
f:write('end\n')
f:write('end\n')
f:write('end\n')
f:close()
Spring.Echo("Save game has been written to SAVEGAMES.sdd")
end
end
end
function widget:AllowCommand(cmd)
if cmd == CMD_SAVE_GAME then
return true, true;
end
return false
endRe: Counting Files / Savegame UI
That's already what I do, I spawn all units at frame 3, give all orders at frame 5.Argh wrote:You'd have to stack those commands into a queue and issue them a few gameframes later.
The problem is that unit aren't recreated with the same UnitID, and all the nice code I had written to translate Unit IDs failed on the first test (6 monthes ago), and I never bothered to look and fix.
It is a problem that guard, attack, repair, load, and all others CMDTYPE.ICON_UNIT* order fails, (especially considering it crashes, I hoped incorrect commands would just be ignored) so I'll fix eventually. But since it took six month of constant posting about my save system to get one person to try it, I'm not convinced there's any hurry.
Last edited by zwzsg on 10 Jan 2010, 06:44, edited 1 time in total.
Re: Counting Files / Savegame UI
I hear you on all that. I have taken forever to get around to reading your code and testing things, it's just how things go sometimes, and as for everybody else... if it's not drag-drop-done easy, they won't use it, you should know better by now.
Meh, if your savegame system wasn't tied into the rest of your UI and didn't have that (minor, easy to fix) problem about trying to find unitIDs that aren't valid, I'd probably just say, "use it". Problem here is that I don't think people will be willing to put it into their projects if it comes with that baggage or if it's unstable. World Builder's been incredibly stable, but I'll be the first to say that I don't use GUARD, etc. when building maps, so that issue never came up.
I'll filter out the rest of that type of orders from this code, but I am certainly open to looking at your system if you can build a UI that's stand-alone, so that BA / CA / NOTA / etc. can just drag-drop a file or two, and have instant universal savegame support.
Meh, if your savegame system wasn't tied into the rest of your UI and didn't have that (minor, easy to fix) problem about trying to find unitIDs that aren't valid, I'd probably just say, "use it". Problem here is that I don't think people will be willing to put it into their projects if it comes with that baggage or if it's unstable. World Builder's been incredibly stable, but I'll be the first to say that I don't use GUARD, etc. when building maps, so that issue never came up.
I'll filter out the rest of that type of orders from this code, but I am certainly open to looking at your system if you can build a UI that's stand-alone, so that BA / CA / NOTA / etc. can just drag-drop a file or two, and have instant universal savegame support.
Last edited by Argh on 10 Jan 2010, 06:47, edited 1 time in total.
Re: Counting Files / Savegame UI
It's not.Argh wrote:Meh, if your savegame system wasn't tied into the rest of your UI
In fact you can even save a game with my UI disabled, by typing:
/dump MySaveName
Re: Counting Files / Savegame UI
Well, OK, but we still need a way to browse, view the contents of, and delete files then, so that people can manage their saves.
Hence this post in the first place- I can write a quickie UI for this, that's not a problem, but I want something a little more polished than some buttons marked "save1" --> "load10"
Hence this post in the first place- I can write a quickie UI for this, that's not a problem, but I want something a little more polished than some buttons marked "save1" --> "load10"
Re: Counting Files / Savegame UI
I'm not sure how I could do a browser without an UI. And it seem a bit overkill to have a whole interfacing library gadget just for wrapping VFS.DirList("Savegame")
The thing I lack maybe is an interface that has a button to save game, but still ask the player to type a name. But making a name out of keypresses would be part of the UI anyway.
Right now, my system support both:
- can choose save name but must use commanline
or
- can use nice "save" button, but always use the same save.
The thing I lack maybe is an interface that has a button to save game, but still ask the player to type a name. But making a name out of keypresses would be part of the UI anyway.
Right now, my system support both:
- can choose save name but must use commanline
or
- can use nice "save" button, but always use the same save.
Re: Counting Files / Savegame UI
Well, what I want is something like this mockup, where end-users can browse their current saves and manage them, regardless of game.
- Attachments
-
- mockup.jpg (16.99 KiB) Viewed 2701 times
Re: Counting Files / Savegame UI
As a widget?
Just extract my Single_Player widget from the mod, and cut out the skirmish generator and credits menu.
To read info from my savegame-as-startscript, use something like:
local filename = "./Missions/Gundam/SaveGame.txt"
local script = VFS.LoadFile(filename)
local game = string.match(script,"GameType=([^;]*);")
local map = string.match(script,"Mapname=([^;]*);")
local NumTeams = string.match(script,"NumTeams=([^;]*);")
Save current game will never be universal as long as it rely on a gadget. As for Universal AI, haha.
Just extract my Single_Player widget from the mod, and cut out the skirmish generator and credits menu.
To read info from my savegame-as-startscript, use something like:
local filename = "./Missions/Gundam/SaveGame.txt"
local script = VFS.LoadFile(filename)
local game = string.match(script,"GameType=([^;]*);")
local map = string.match(script,"Mapname=([^;]*);")
local NumTeams = string.match(script,"NumTeams=([^;]*);")
Save current game will never be universal as long as it rely on a gadget. As for Universal AI, haha.
