SaveUtils.lua -- Itemized Data Storage Across Mods

Might be better for you to actually see the script; It's not a complex one, nor does it need to be. Just a counter that is used to trigger a period of anarchy; Golden Ages are simple, Dark Ages should be too. ;)

Spoiler :
Code:
include( "Math" );

include( "SaveUtils" ); MY_MOD_NAME = "DarkAges--vmdy.02.11.25.2010";

DebugMode = true


-- Implement Dark Ages from extended periods of unhappiness
function DarkAges()
	local pPlayer = Players[ Game.GetActivePlayer() ];

	local availableHappiness = pPlayer:GetExcessHappiness(); -- Get the player's happiness
	local goldenAgeMeter = pPlayer:GetGoldenAgeProgressMeter(); -- Get Golden Age progress


	
	-- Load script data, initialize Dark Age variables
--[[	local scriptData = pPlayer:GetScriptData(); 
	local darkAgeProgress; 
	local darkAgeThreshold;
	local darkAgeTurns; 

	if (tonumber(scriptData) ~= nil) then 
		scriptData = tonumber(scriptData);
		darkAgeProgress = scriptData % 10000;
		darkAgeThreshold = math.floor((scriptData % 100000000) / 10000);
		darkAgeTurns = math.floor(scriptData / 100000000)
	else
		scriptData = 0;
		darkAgeProgress = 0;
		darkAgeThreshold = 500;
		darkAgeTurns = 0;
	end --]]

	local darkAgeProgress = load( pPlayer, "DarkAgeProgress" ) or {0};
	local darkAgeThreshold = load( pPlayer, "DarkAgeThreshold" ) or {500};
	local darkAgeTurns = load( pPlayer, "DarkAgeTurns" ) or {0};

	print( darkAgeProgress, darkAgeThreshold, darkAgeTurns );
	
	-- Check happiness and golden age; If golden age meter < 0 AND happiness < 0, increment Dark Age Meter
	if (darkAgeTurns == 0) then
		if (availableHappiness < 0) then
			if (goldenAgeMeter + availableHappiness < 0) then
				darkAgeProgress = darkAgeProgress + math.abs(goldenAgeMeter + availableHappiness);
				print( "Dark Age Progress: "..darkAgeProgress );
			end
		elseif (darkAgeProgress > 0) then			
			darkAgeProgress = darkAgeProgress - availableHappiness;
			if (darkAgeProgress < 0) then
				pPlayer:SetGoldenAgeProgressMeter(0 - darkAgeProgress);
				darkAgeProgress = 0;
			else
				pPlayer:SetGoldenAgeProgressMeter(0);
			end
		end
	end

	-- If in Dark Age, decrement the number of turns. Do this BEFORE starting a Dark Age!
	if (darkAgeTurns > 0) then		
		darkAgeTurns = darkAgeTurns - 1;
	end
	
	-- If Progress >= Threshold, start a Dark Age, and reset/modify Dark Age variables.
	if (darkAgeProgress >= darkAgeThreshold) then
		darkAgeProgress = darkAgeProgress - darkAgeThreshold;
		darkAgeTurns = darkAgeTurns + 10;
		if (darkAgeThreshold > 50) then
			if (darkAgeThreshold - 50 >= 50) then
				darkAgeThreshold = darkAgeThreshold - 50;
			else 
				darkAgeThreshold = 50;
			end
		end
		print( "Dark Age Began! Dark Age Turns: "..darkAgeTurns..", Dark Age Threshold: "..darkAgeThreshold );

		doDarkAge(pPlayer, darkAgeTurns);
	end

	-- Store all Dark Age variables from turn to turn. AFAIK, only allowed one scriptdata... So values must be combined into a single int for storage
	save( pPlayer, "DarkAgeProgress", darkAgeProgress );
	save( pPlayer, "DarkAgeThreshold", darkAgeThreshold );
	save( pPlayer, "DarkAgeTurns", darkAgeTurns );

	--[[scriptData = (darkAgeTurns * 100000000) + (darkAgeThreshold * 10000) + darkAgeProgress;
	pPlayer:SetScriptData(scriptData);]]

end

-- Start the Dark Age (Anarchy)
function doDarkAge(pPlayer, darkAgeTurns)
	if (not pPlayer) then
		return;
	end

	pPlayer:SetAnarchyNumTurns(darkAgeTurns);
end



Events.ActivePlayerTurnStart.Add(DarkAges);

My original save/load mechanism is still there, but commented out. Worked perfectly well, but not compatible with any other mod saving data. :lol:
 
I've created a test script using a pasted copy of the posted code to ensure it functions properly. The following functions as expected.

Code:
include( "SaveUtils" ); MY_MOD_NAME = "Fix It Please";
include( "WhysUtils" );

local pPlayer = Players[ Game.GetActivePlayer() ];
print(out( pPlayer ));

function doIt()
  local data = load( pPlayer, "data" );
 [COLOR="Blue"] if type( data ) ~= "number" then data = 0; end[/COLOR]
  print(out( data ));
  data = data +1;
  save( pPlayer, "data", data );
end
Events.ActivePlayerTurnStart.Add( doIt );

If you load data that hasn't yet been saved and thus doesn't exist, it will return an empty table.
 
Honestly, when I first saw the string output I assumed that was the issue. And then began sorting around for a way to convert it (the blue line :p).

I really shouldn't be messing with things while this exhausted. Or while drunk. And definitely not the two together. :lol:

Thanks for pointing out the problem. :goodjob:
 
Ah, turns out I have another question. Sorry to be a bother. :lol:

What would be the best way to set up a "neutral" file? In this case, I need to load my data in TopPanel.lua, but would like that file to remain open to other mods (IE, no "My_Mod_Name" set). Looking at it, I assumed the following would work: local darkAgeProgress = load( pPlayer, "DarkAgeProgress", "DarkAges--vmdy.02.11.25.2010" );

And it does seem to work... Almost. The bar breaks after loading. If I save/load, the data will display correctly, but otherwise it's dead. FireTuner outputs the following error: Runtime Error: [string "C:\Users\Valkrionn\Documents\My Games\Sid M..."]:413: attempt to index global 'cacheState' (a nil value)


Edit: Nevermind. Re-read the comments in the SaveUtils script, and passed 0 for the cache argument. Functions as expected now.
 
build: 2010.11.26.0000

The following will now work as expected. Wasn't a bug with the cache after all, but it was none-the-less counter intuitive, so now it's intuitive. :)

Code:
include( "SaveUtils" ); MY_MOD_NAME = "MyMod";
include( "WhysUtils" );

local pPlayer = Players[ Game.GetActivePlayer() ];
print(out( pPlayer ));

function doIt()
  local data = load( pPlayer, "data" ) or 0;
  print(out( data ));
  data = data +1;
  save( pPlayer, "data", data );
end
Events.ActivePlayerTurnStart.Add( doIt );

I also updated some of the documentation regarding sharing recommendations.
 
Ah, turns out I have another question. Sorry to be a bother. :lol:

What would be the best way to set up a "neutral" file? In this case, I need to load my data in TopPanel.lua, but would like that file to remain open to other mods (IE, no "My_Mod_Name" set). Looking at it, I assumed the following would work: local darkAgeProgress = load( pPlayer, "DarkAgeProgress", "DarkAges--vmdy.02.11.25.2010" );

And it does seem to work... Almost. The bar breaks after loading. If I save/load, the data will display correctly, but otherwise it's dead. FireTuner outputs the following error: Runtime Error: [string "C:\Users\Valkrionn\Documents\My Games\Sid M..."]:413: attempt to index global 'cacheState' (a nil value)


Edit: Nevermind. Re-read the comments in the SaveUtils script, and passed 0 for the cache argument. Functions as expected now.

I'm looking into this. You don't really want to use cache state 0. It will reduce performance. What you're trying to do should be entirely doable without turning off the cache.

What file is the 413 error line for?

Oh wait, I think I know what the problem is. SaveUtils shares its cacheState and pCache globals on Events.LoadScreenClose. TopPanel must be attempting to do something prior to LoadScreenClose so can't find the globals.

Would it be possible for you to move the operation to a later point in the process? If not, then do the following.

include( "SaveUtils" ); MY_MOD_NAME = "MyMod";
cacheState = share( "SaveUtils.cacheState", cacheState or {} );
pCache = share( "SaveUtils.pCache", pCache or Cache:new() );

Then make sure that ShareData.lua is loaded as a LuaContext near the top of the InGame.xml. This will allow the data to be shared before anything else executes in the InGame.xml. The down side is this approach can create merging requirements if you wish to use it with another mod. It's possible that may be unavoidable, depending on what TopPanel is trying to do.
 
I'd like to cut in at this juncture for a bit of lua modder solidarity:

Code:
C:\Users\Valkrionn\Documents\My Games\Sid M..."]:413: attempt to index global 'cacheState' (a nil value)"

I mean, COME ON! Give us a chance. :D Especially if you get a 'tried to index '?'' error...

Amount of time I've spent clicking on about 30 files in my project and going to the line to see if it in any way looks like that might be the file the error is in....
 
Yeah, I mean if they're gonna cut it short, I'd rather have the tail, not the head. :p

Valkrionn, the share statement for the cache was moved to the LoadScreenClosed so mods could be loaded as InGameUIAddins without concern for execution order or an InGame.xml. The down side is that anything that occures in global scope is going to occur prior to LoadScreenClosed.

I'll consider making it easier to use it either way. While it may be possible to rewrite TopPanel to perform the operation at a more opportune time, doing so could prove an overwhelming task.

edit: dark ages sound cool, btw.
 
For what it's worth, I've pointed this mod out to both the testers and the devs, and will be using it to create several test mods. Excellent work on this. :goodjob:

Yes, I believe I'll be using this from now on. Was going to hack my own, but now I don't have to. Thanks for saving me a week. :goodjob:
 
Is there any disadvantage to directly adding things to Player? For example, I wrote a city maintenance script and added a city maintenance variable to the Player class with a setter and getter. It seems to save and load automatically so there's no need to do serialisation and deserialisation yourself. This also allows to share data for example between TopPanel and InGame. Am I missing something or did you just not try it?

Code:
--[[
	CityMaintenance.lua
	
	Creator: alpaca
	Last Change: 26.11.2010
	
	Description: Adds a non-linear maintenance mechanic to cities.
]]--

--[[
	Set up SaveUtils
]]--
--include("SaveUtils")
--MY_MOD_NAME = "PlayWithMe_v1_26.11.2010"


------------------------------------------------------------------
--	Utility functions
------------------------------------------------------------------

--[[
	Recalculate the city maintenance
	Arguments:
		n: num. Number of cities
	Returns:
		num. City maintenance
]]--
function calcCityMaintenance(n)
	return n*(n - 1)/2
end

--[[
	Get the city maintenance for a player
]]--
function getCityMaintenance(self)
	return self.CityMaintenance
end
Player.GetCityMaintenance = getCityMaintenance

--[[
	Set the city maintenance for a player
	Arguments:
		maint: num. City maintenance
]]--
function setCityMaintenance(self, maint)
	self.CityMaintenance = maint
end
Player.SetCityMaintenance = setCityMaintenance

-- add the city maintenance as a player variable


------------------------------------------------------------------
--	Implementation
------------------------------------------------------------------

-- these store the data for each player
tCityMaintenance = {}
local tnCities = {}

-- initialise
Player.CityMaintenance = Player.CityMaintenance or 0
Player.NCitiesOld = Player.CityMaintenance or -1


--[[
	Perform the city maintenance update for all players
]]--
function doCityMaintenance()


	for kPlayer, pPlayer in ipairs(Players) do
		if pPlayer:IsAlive() then
			local nCitiesCurrent = pPlayer:GetNumCities()
			-- was a city gained or lost?
			if pPlayer.NCitiesOld ~= nCitiesCurrent then
				-- recalculate city maintenance
				local buildingMaintenanceWithoutCities = pPlayer:GetBuildingGoldMaintenance() - pPlayer:GetCityMaintenance()
				pPlayer:SetCityMaintenance(calcCityMaintenance(nCitiesCurrent))
				pPlayer:SetBaseBuildingGoldMaintenance(pPlayer:GetCityMaintenance() + buildingMaintenanceWithoutCities)
				pPlayer.NCitiesOld = nCitiesCurrent
			end
			
		end
	end

end

--[[
	Hook into both the active player's turn start (for AI maintenance) and cities being built (for immediate player maintenance update)
]]--
Events.ActivePlayerTurnStart.Add(doCityMaintenance)

Events.SerialEventCityCreated.Add(doCityMaintenance)
 
Okay, did a little bit of testing on that.

It DOES work... Right up until you exit civ and then reload. The values aren't saved, so SaveUtils is still necessary. This is still very useful for sharing data in 'neutral' files such as TopPanel; VERY nice find. :goodjob:

Ah ok, thanks for testing. I kind of assumed the UI lua contexts are reloaded when you reload a game and didn't test it. Would probably have stumbled upon it sooner or later, but this definitely saves time. Back to using SaveUtils for my little mod :lol:
 
Top Bottom