• Civilization 7 has been announced. For more info please check the forum here .

Store Variable values between saves LUA

KennyTG

Chieftain
Joined
May 26, 2014
Messages
45
Hi,

I wrote a function in Lua which uses a boolean variable. This variable is initially defined as false and is defined outside of the function. When the function is called by a game event, if the conditions are met, it will update this value to true. This works fine when used in a single play through, but when loading a game where that value had been set to true, I find that this is no longer the case and that the value of the variable is now false.

To the point, is there a way to store a Lua variable's value between saves?

Thanks.
 
Yes, there are two widely used utility scripts for exactly what you describe, which is known as Persistent Data. Look through the forums for something called SaveUtils and/or something called TableSaverLoader, and see which one would work better.

I have no idea where they are (probably buried underneath piles of lua threads) so I don't have the links. Sorry.

Edit: Nevermind I found them:
SaveUtils
TableSaverLoader
 
For a (few) simple values, check out how the various DLC scenarios do this in the TurnsRemaining.lua files (search for Modding)
 
Thanks whoward69. I'll also look into those files. Would a two dimensional boolean array (a table of boolean values within a table i.e. bTable[50][12]) constitute a few simple values?
 
Assuming every cell is populated that's 600 Booleans, not my definition of "a few"
 
Ok. I decided to try TableSaverLoader, but I'm having trouble getting it to work. I added the TableSaverLoader file to my mod and imported it into VFS. I'm using the Lua hook. After trying to implement it, I've been unable to save my game. The Save and Quicksave buttons behave as if they are being blocked; I click on them, but nothing happens. Here's what I'm using so far:
Code:
function OnModLoaded() --called from end of last mod file to load

	local bNewGame = not TableLoad(gT, "pc")

	if bNewGame then
		print("Initializiing for new game...")
	else
		print("Initializing for loaded game...")
		TableLoad(gT, "pc")
	end

	TableSave(gT, "pc")  --before the initial autosave that runs for both new and loaded games
end
function OnEnterGame()   --Runs when Begin or Continue Your Journey pressed
	print("Player entering game ...")
	ContextPtr:LookUpControl("/InGame/GameMenu/SaveGameButton"):RegisterCallback(Mouse.eLClick, SaveGameIntercept)
	ContextPtr:LookUpControl("/InGame/GameMenu/QuickSaveButton"):RegisterCallback(Mouse.eLClick, QuickSaveIntercept)
end
Events.LoadScreenClose.Add(OnEnterGame)

function SaveGameIntercept()	--overrides Civ5 code when player presses Save Game from Game Menu or Cntr-s
	TableSave(gT, "pc")
	UIManager:QueuePopup(ContextPtr:LookUpControl("/InGame/GameMenu/SaveMenu"), PopupPriority.SaveMenu)
end

function QuickSaveIntercept()	--overrides Civ5 code when player presses Quick Save from Game Menu or F11
	TableSave(gT, "pc")
	UI.QuickSave()
end

local autoSaveFreq = OptionsManager.GetTurnsBetweenAutosave_Cached()
function OnGameOptionsChanged()
	autoSaveFreq = OptionsManager.GetTurnsBetweenAutosave_Cached()
end
Events.GameOptionsChanged.Add(OnGameOptionsChanged)

function OnAIProcessingEndedForPlayer(iPlayer)
	if iPlayer == 63 then					--runs on barb turn AFTER barb unit moves (very close to the regular autosave)
		if Game.GetGameTurn() % autoSaveFreq == 0 then	--only need to do on autosave turns
			TableSave(gT, "pc")
		end
	end
end
Events.AIProcessingEndedForPlayer.Add(OnAIProcessingEndedForPlayer)

function InputHandler(uiMsg, wParam, lParam)
	if uiMsg == KeyEvents.KeyDown then
		if wParam == Keys.VK_F11 then
			QuickSaveIntercept()		--F11 Quicksave
        		return true
		elseif wParam == Keys.S and UIManager:GetControl() then
			SaveGameIntercept()			--ctrl-s
			return true
		end
	end
end
ContextPtr:SetInputHandler(InputHandler)

OnModLoaded()

Here is how I defined the array that I'm trying to store.
Code:
 gT = {} -- defining the master table to be saved by Pazyryk's code
 bAS = {} --defines an array
 gT = {bAS = bAS}--placing my array within the master Table
 	for i=1, 21 do
		bAS[i] = {} 
		for j=1, 12 do 
			bAS[i][j]=false
		end 
	end
 
Do you have another ContextPtr / InputHandler being defined anywhere else when TableSaverLoader is called?

I recall there being issues with that, since only one of those can be active at a time, or something.
 
Not completely clear how you structured your TableSaver hookup, but I think you may be making structural errors. Here's an experimental lua that I know works to implement TableSaverLoader:
Spoiler :
Code:
-- Queens_Estate
-- Author: LeeS
-- DateCreated: 8/14/2014 6:00:43 AM
--------------------------------------------------------------
include("TableSaverLoader.lua")

gNumberCSAllies = 0
gNumberCSFriends = 0
gNumberOfBuildings = 0
gRequiredCivName = "CIVILIZATION_AMERICA"
gBuildingForCode = GameInfoTypes.BUILDING_QUEENS_ESTATE
gAllyHappyDummy = GameInfoTypes.BUILDING_CS_ALLY_HAPPINESS
gFriendHappyDummy = GameInfoTypes.BUILDING_CS_FRIEND_HAPPINESS
--print("The Required Civ Name is " .. tostring(gRequiredCivName))

gT = {}
gT.gNumberCSAllies = 0
gT.gNumberCSFriends = 0
TableName = "CSAllies"
-------------------------------------------------------------------------------------------------
--TableSaverLoader Hookups
-------------------------------------------------------------------------------------------------

function OnModLoaded() --called from end of last mod file to load

	local bNewGame = not TableLoad(gT, TableName)

	if bNewGame then
		print("New Game")
		else print("Loaded from Saved Game")
			gNumberCSAllies = gT.gNumberCSAllies
			gNumberCSFriends = gT.gNumberCSFriends
			--print("While game loading: " .. tostring(gRequiredCivName) .. " has the following number of City-State Allies: " .. tostring(gNumberCSAllies))
			--print("While game loading: " .. tostring(gRequiredCivName) .. " has the following number of City-State Friends: " .. tostring(gNumberCSFriends))
	end

	TableSave(gT, TableName)
end

function OnEnterGame()   --Runs when Begin or Continue Your Journey pressed
	print("Player entering game ...")
	ContextPtr:LookUpControl("/InGame/GameMenu/SaveGameButton"):RegisterCallback(Mouse.eLClick, SaveGameIntercept)
	ContextPtr:LookUpControl("/InGame/GameMenu/QuickSaveButton"):RegisterCallback(Mouse.eLClick, QuickSaveIntercept)
end
Events.LoadScreenClose.Add(OnEnterGame)

function SaveGameIntercept()	--overrides Civ5 code when player presses Save Game from Game Menu or Cntr-s
	TableSave(gT, TableName)
	UIManager:QueuePopup(ContextPtr:LookUpControl("/InGame/GameMenu/SaveMenu"), PopupPriority.SaveMenu)
end

function QuickSaveIntercept()	--overrides Civ5 code when player presses Quick Save from Game Menu or F11
	TableSave(gT, TableName)
	UI.QuickSave()
end

local autoSaveFreq = OptionsManager.GetTurnsBetweenAutosave_Cached()
function OnGameOptionsChanged()
	autoSaveFreq = OptionsManager.GetTurnsBetweenAutosave_Cached()
end
Events.GameOptionsChanged.Add(OnGameOptionsChanged)

function OnAIProcessingEndedForPlayer(iPlayer)
	if iPlayer == 63 then					--runs on barb turn AFTER barb unit moves (very close to the regular autosave)
		if Game.GetGameTurn() % autoSaveFreq == 0 then	--only need to do on autosave turns
			TableSave(gT, TableName)
		end
	end
end
Events.AIProcessingEndedForPlayer.Add(OnAIProcessingEndedForPlayer)

function InputHandler(uiMsg, wParam, lParam)
	if uiMsg == KeyEvents.KeyDown then
		if wParam == Keys.VK_F11 then
			QuickSaveIntercept()		--F11 Quicksave
        		return true
		elseif wParam == Keys.S and UIManager:GetControl() then
			SaveGameIntercept()			--ctrl-s
			return true
		end
	end
end
ContextPtr:SetInputHandler(InputHandler)



-------------------------------------------------------------------------------------------------
--Calculate during turn processing the number of City State Allies and Friends
--this updates the number of city state allies during game play
-------------------------------------------------------------------------------------------------

function QueensEstateData(iPlayer)
	local pPlayer = Players[iPlayer]
	if pPlayer:GetCivilizationType() == GameInfoTypes[gRequiredCivName] then
		local pCity = pPlayer:GetCapitalCity();

		--print(tostring(gRequiredCivName) .. " is as iPlayer number " .. tostring(iPlayer))

		gNumberCSAllies = 0
		gNumberCSFriends = 0
		iCivPlayerNumber = iPlayer
		for i = 0, GameDefines.MAX_PLAYERS - 1 do
			if i ~= iCivPlayerNumber then
				if Players[i]:IsMinorCiv() then
					local iFriend = Players[i]:GetMinorCivFriendshipLevelWithMajor(iCivPlayerNumber)
					--print("results from iFriend = Players[i]:GetMinorCivFriendshipLevelWithMajor(iCivPlayerNumber) is " .. tostring(iFriend))

					if iFriend == 1 then
						--print("Is a Friend")
						gNumberCSFriends = gNumberCSFriends + 1
					end

					if iFriend == 2 then
						--print("Is an Ally")
						gNumberCSAllies = gNumberCSAllies + 1
					end
				end
			end
		end

		--print(tostring(gRequiredCivName) .. " has the following number of City-State Friends: " .. tostring(gNumberCSFriends))
		--print(tostring(gRequiredCivName) .. " has the following number of City-State Allies: " .. tostring(gNumberCSAllies))

		-------------------------------------------------------------------------
		--Ally and Friend happiness dummy in capital commands here
		-------------------------------------------------------------------------

		iNumCapitalAllyHappyBuildings = pCity:GetNumRealBuilding(gAllyHappyDummy)
		iNumCapitalFriendHappyBuildings = pCity:GetNumRealBuilding(gFriendHappyDummy)
		if iNumCapitalAllyHappyBuildings ~= gNumberCSAllies then
			pCity:SetNumRealBuilding(gAllyHappyDummy, gNumberCSAllies);
		end
		if iNumCapitalFriendHappyBuildings ~= gNumberCSFriends then
			pCity:SetNumRealBuilding(gFriendHappyDummy, gNumberCSFriends);
		end

		gT.gNumberCSAllies = gNumberCSAllies
		gT.gNumberCSFriends = gNumberCSFriends
	end
end
GameEvents.PlayerDoTurn.Add(QueensEstateData)


-------------------------------------------------------------------------------------------------
--Calculate the number of Queens Estate built or building
--this updates the number of buildings during game play
--disables building more than the number allowed by City-State Allies
-------------------------------------------------------------------------------------------------

function LeesBuildingConstructionOverride(iPlayer, iCity, iBuilding)
	local pPlayer = Players[iPlayer]
	local result = true
	local pCity = pPlayer:GetCityByID(iCity)
	if iBuilding == gBuildingForCode then
		if pPlayer:GetCivilizationType() ~= GameInfoTypes[gRequiredCivName] then
			result = false
		end
		if pPlayer:GetCivilizationType() == GameInfoTypes[gRequiredCivName] then
			local sCityName = pCity:GetName()
			gNumberOfBuildings = 0
			for pCity in pPlayer:Cities() do
				if pCity:IsHasBuilding(iBuilding) then
					gNumberOfBuildings = gNumberOfBuildings + 1
				else if pCity:GetProductionBuilding() == iBuilding then
					gNumberOfBuildings = gNumberOfBuildings + 1
					end
				end
			end
			print("Number of Buildings made or making is " .. tostring(gNumberOfBuildings))
			if gNumberCSAllies <= gNumberOfBuildings then
				print("LeesBuildingConstructionOverride: disabling Queen's Estate construction in the city of " .. sCityName)
				result = false
			else
				print("LeesBuildingConstructionOverride: enabling Queen's Estate construction in the city of " .. sCityName)
			end
		end
	end
	return result
end

GameEvents.CityCanConstruct.Add(LeesBuildingConstructionOverride)

OnModLoaded()
Although I think the current filename for TableSaverLoader is "TableSaverLoader016.lua" as opposed to the "TableSaverLoader.lua" I have at the top in the "include" statement.
 
Here I'll just explain what I'm using and see if that raises any problems. I have one function using GameEvents.TeamSetHasTech as the hook and Player:CanBuild within it. This file doesn't use TableSaverLoader at all. In a separate file I have:
Code:
 gT = {} -- defining the master table to be saved by Pazyryk's code
 bAS = {} --defines an array
 gT = {bAS = bAS}--placing my array within the master Table
 	for i=1, 21 do
		bAS[i] = {} 
		for j=1, 12 do 
			bAS[i][j]=false
		end 
	end

Then a function using Events.SerialEventImprovementCreated as the hook and Plot:SetImprovementType, Plot:GetImprovementType, Plot:SetResourceType, Plot:GetResourceType, and Map.GetPlot within it. The two dimensional array is used to test if a player had completed an action.

Then I have the TableSaverLoader hook:
Code:
function OnModLoaded() --called from end of last mod file to load

	local bNewGame = not TableLoad(gT, "pc")

	if bNewGame then
		print("Initializiing for new game...")
	else
		print("Initializing for loaded game...")
		TableLoad(gT, "pc")
	end

	TableSave(gT, "pc")  --before the initial autosave that runs for both new and loaded games
end
function OnEnterGame()   --Runs when Begin or Continue Your Journey pressed
	print("Player entering game ...")
	ContextPtr:LookUpControl("/InGame/GameMenu/SaveGameButton"):RegisterCallback(Mouse.eLClick, SaveGameIntercept)
	ContextPtr:LookUpControl("/InGame/GameMenu/QuickSaveButton"):RegisterCallback(Mouse.eLClick, QuickSaveIntercept)
end
Events.LoadScreenClose.Add(OnEnterGame)

function SaveGameIntercept()	--overrides Civ5 code when player presses Save Game from Game Menu or Cntr-s
	TableSave(gT, "pc")
	UIManager:QueuePopup(ContextPtr:LookUpControl("/InGame/GameMenu/SaveMenu"), PopupPriority.SaveMenu)
end

function QuickSaveIntercept()	--overrides Civ5 code when player presses Quick Save from Game Menu or F11
	TableSave(gT, "pc")
	UI.QuickSave()
end

local autoSaveFreq = OptionsManager.GetTurnsBetweenAutosave_Cached()
function OnGameOptionsChanged()
	autoSaveFreq = OptionsManager.GetTurnsBetweenAutosave_Cached()
end
Events.GameOptionsChanged.Add(OnGameOptionsChanged)

function OnAIProcessingEndedForPlayer(iPlayer)
	if iPlayer == 63 then					--runs on barb turn AFTER barb unit moves (very close to the regular autosave)
		if Game.GetGameTurn() % autoSaveFreq == 0 then	--only need to do on autosave turns
			TableSave(gT, "pc")
		end
	end
end
Events.AIProcessingEndedForPlayer.Add(OnAIProcessingEndedForPlayer)

function InputHandler(uiMsg, wParam, lParam)
	if uiMsg == KeyEvents.KeyDown then
		if wParam == Keys.VK_F11 then
			QuickSaveIntercept()		--F11 Quicksave
        		return true
		elseif wParam == Keys.S and UIManager:GetControl() then
			SaveGameIntercept()			--ctrl-s
			return true
		end
	end
end
ContextPtr:SetInputHandler(InputHandler)

OnModLoaded()

Thanks guys. It turns out to have been a small mistake.

Although I think the current filename for TableSaverLoader is "TableSaverLoader016.lua" as opposed to the "TableSaverLoader.lua" I have at the top in the "include" statement.

And that seems to have been the problem because now it's working.
 
TableLoader() I don't think is an actual function name unless you renamed it.
It should be just TableLoad() and that really only needs to run once.

Your description of having your table set up in a separate file leads me to wonder if you may be running into a scoping issue with the separated pieces of the code in different Lua contexts, and unable to communicate with each other.

In the file in which you define gT, do you also have the TableSaverLoader code running there?

Edit:
I would also suggest taking a look at the TableSaverLoad thread by Pazyryk, since this deals with confusion about how to hook it up properly.
 
The two files have unrelated functions so that's not a concern. My issue turned out to be with how I did include("TableSaverLoader016.lua"). Thanks though.
 
Ah, I see. Glad that was worked out.

I think I had asked Pazyryk to change it to include the version number in the filename after experiencing some conflict with other Civs which used it, and wound up having their older versions load over my newer one and causing Lua errors.

This, I suppose, has the side effect that a large amount of existing code which calls "TableSaverLoader.lua" will technically no longer work if they use the new files.
 
Top Bottom