Building Resources

Out of curiosity, was it not possible to make a new table and save the data into the table? From what I've seen of the sql side, it seems possible but not sure if it's as easy to access from lua. That should take care of the save game issue without requiring serializing to a specific string, or is this more because you need flexible non-typed data storage?

I don't believe the database persists between saved games. If it does persist then I suspect that would be possible, but it would be terrible to look at and would almost certainly have slower performance. BuildlingResources.lua for example is using nested tables several levels deep. Buildling a database driven design capable of dealing with unlimited table nesting sounds nightmarish, but I don't doubt someone knows how to do it, I just strongly suspect serialization is much faster.
 
I don't believe the database persists between saved games. If it does persist then I suspect that would be possible, but it would be terrible to look at and would almost certainly have slower performance. BuildlingResources.lua for example is using nested tables several levels deep. Buildling a database driven design capable of dealing with unlimited table nesting sounds nightmarish, but I don't doubt someone knows how to do it, I just strongly suspect serialization is much faster.

Hmm.. yeah, they're more using the db for storing setting values than dynamic data aren't they. I just now grasped how honestly strange that concept is to me. :lol: I do a lot of web app development and there the db is all about the dynamic (stored) data and almost nothing with settings.
 
Tho I've done work in both C++ and Java, PHP is my native tongue at this point, and what I use to develop web apps. PHP has a super global by the name of $_SESSION. All data stored in session is serialized at the end of a script and deserialized at the start of the next iteration. That's why you can't have circular references in PHP session. It's the same process.
 
Thank you for the MOD. If it is possible I want to use it in my MOD (off course with all credits).

Just to make sure, the only thing I have to do is change the "MY_MOD_NAME" in both the
BuildingResources.lua and the SaveUtils.lua?
 
No, do NOT touch anything in SaveUtils. :)

Allow me to attempt a brief tutorial. There are only a couple lines of code you need to understand to make this work with your own mod and mod compilations. For example, lets say you want to combine the following three lua files: BuildingResources.lua, OtherMod.lua, MyMod.lua.

BuildingResources.lua: (deprecated version)
Spoiler :
Code:
-- vymdt.03.2010.11.02.0100
-- Created by: Whys Alives -Open source
--===========================================================================
-- BuildingResources
--===========================================================================
--[[
Allows buildings to produce resources.

Template includes Alchemist building which gives 1 gold resource and 1 iron.
Additional resources for buildings can be easily added to the XML.

Known Issues:

When a luxury resource is first gained from a building, empire happiness in
TopPanel does not update properly until unit moved or next turn.

There is no new resource notification.
]]

include( "SaveUtils" ); MY_MOD_NAME = "[COLOR="DarkOrange"]b218b309-9df5-4af5-baf9-c297e593398a[/COLOR]";
--===========================================================================
--[[
Adds and removes resources provided by buildings.
]]
function applyBuildingResources()
	
	--build resource change table.
	local changeTable = {};
	for row in GameInfo.Building_ResourceChange() do
		changeTable[row.BuildingType] = changeTable[row.BuildingType] or {};
		changeTable[row.BuildingType][row.ResourceType] = row.ResourceChange;
	end

	--loop thru all live players (human and AI).
	for index,pPlayer in pairs( Players ) do
		if pPlayer ~= nil and pPlayer:IsAlive() then
			
			--load list of previously applied building resources.
			local applied = load( pPlayer, "Building Resources" );
			local listing = {}; --new list of currently applied building resources.
			
			--loop thru player cities.
			for pCity in pPlayer:Cities() do
				local cityID = pCity:GetID();
				listing[cityID] = {};
				applied[cityID] = applied[cityID] or {};
				
				--loop thru relevant building types.
				for buildingType,resourceTable in pairs( changeTable ) do
					local buildingID = GameInfo["Buildings"][buildingType]["ID"];

					--look for matching buildings.
					if pCity:IsHasBuilding( buildingID ) then
						listing[cityID][buildingID] = buildingType;

						--apply resources for new buildings.
						if applied[cityID][buildingID] == nil then
							--loop thru resource changes.
							for resourceType,resourceChange in pairs( resourceTable ) do
								local resourceID = GameInfo["Resources"][resourceType]["ID"];
								pPlayer:ChangeNumResourceTotal( resourceID, resourceChange ); --cumulative.
							end --END resource loop.
						end
						--remove handled building case.
						applied[cityID][buildingID] = nil;

					end
				end --END building loop.

				--remove empty city case.
				if len( applied[cityID] ) == 0 then applied[cityID] = nil; end
			end --END city loop.

			--save list of currently applied building resources.
			save( pPlayer, "Building Resources", listing );

			--unapply resources for lost buildings.
			for cityID,buildingTable in pairs( applied ) do
				for buildingID,buildingType in pairs( buildingTable ) do
					--loop thru resource changes.
					for resourceType,resourceChange in pairs( changeTable[buildingType] ) do
						local resourceID = GameInfo["Resources"][resourceType]["ID"];
						pPlayer:ChangeNumResourceTotal( resourceID, -resourceChange ); --cumulative.
					end --END resource loop.
				end --END building loop.
			end --END city loop.

		end
	end --END player loop.
end
Events.ActivePlayerTurnStart.Add( applyBuildingResources );
--===========================================================================
--END BuildingResources
--===========================================================================
-- Created by: Whys Alives -Open source

OtherMod.lua:
Spoiler :
Code:
include( "SaveUtils" ); MY_MOD_NAME = "[COLOR="DarkOrange"]Player Save Inspector[/COLOR]";

function inspectPlayerSaves()
	setCacheState( 0 ); --no cache.
	for index,pPlayer in pairs( Players ) do
		if pPlayer ~= nil and pPlayer:IsAlive() then
			local data = load( pPlayer, nil, false );
			print(out( data ));
		end
	end
end
Events.ActivePlayerTurnStart.Add( inspectPlayerSaves );

MyMod.lua
Code:
function doMyOwnThing()
	for index,pPlayer in pairs( Players ) do
		if pPlayer ~= nil and pPlayer:IsAlive() then
			[COLOR="Red"]pPlayer:SetScriptData( "I break things." );[/COLOR]
		end
	end
end
Events.ActivePlayerTurnStart.Add( doMyOwnThing );

All 3 files access ScriptData, but only BuildingResources and OtherMod are using SaveUtils to do it. The third file is accessing ScriptData on its own and will break BuildingResources because it will save the string "I break things" over all the player saves, wiping out BuildingResources save data. OtherMod will allow you to see what is going on in the save slots and what you'll notice (depending on execution order of these 3 files) is that SaveUtils stores a complex string containing individual areas for individual mods data, so each mod using SaveUtils can share the same save slot. But the third file is overwriting that complex string with a very simple string that overwrites all the other mods data. It's being very rude. To fix it, this is all you have to do...

Code:
[COLOR="Blue"]include( "SaveUtils" ); MY_MOD_NAME = "[COLOR="Green"]MyMod[/COLOR]";[/COLOR]
function doMyOwnThing()
	for index,pPlayer in pairs( Players ) do
		if pPlayer ~= nil and pPlayer:IsAlive() then
			[COLOR="Blue"]save( pPlayer, "myOwnThing", [COLOR="Green"]"I'm a team player."[/COLOR] );[/COLOR]
		end
	end
end
Events.ActivePlayerTurnStart.Add( doMyOwnThing );

The include statement essentially makes the SaveUtils file a part of the file calling include. It's saying, give me everything in SaveUtils so that I can use it in this file. It should appear at the top before any other code. The MY_MOD_NAME right after that tells SaveUtils, "hey, any data this file saves or loads should go under this name." If modders don't change that name to their own mod's unique name, then they will overwrite each other's data because they'll be saving it to the same mod name.

In the middle of the code is the save statement. Originally it said, "hey save this string. I don't care who else is using this save slot." Now it says, "hey SaveUtils, please carefully place this string along side the data being used by other mods so we can all share the save slot."

Each lua file that needs to save or load data needs to have SaveUtils included at the top of the file and the mod name set to the name of the mod. If you notice, BuildingResources and OtherMod have a different MY_MOD_NAME. This is because we are combining them and they were originally separate mods. We want to make them all a part of our new mod, so we would change them all to, "MyMod". So the final product would look like this...


Spoiler :
BuildingResources.lua:
Code:
-- vymdt.03.2010.11.02.0100
-- Created by: Whys Alives -Open source
--===========================================================================
-- BuildingResources
--===========================================================================
--[[
Allows buildings to produce resources.

Template includes Alchemist building which gives 1 gold resource and 1 iron.
Additional resources for buildings can be easily added to the XML.

Known Issues:

When a luxury resource is first gained from a building, empire happiness in
TopPanel does not update properly until unit moved or next turn.

There is no new resource notification.
]]

include( "SaveUtils" ); MY_MOD_NAME = "[COLOR="Green"]MyMod[/COLOR]";[/COLOR]
--===========================================================================
--[[
Adds and removes resources provided by buildings.
]]
function applyBuildingResources()
	
	--build resource change table.
	local changeTable = {};
	for row in GameInfo.Building_ResourceChange() do
		changeTable[row.BuildingType] = changeTable[row.BuildingType] or {};
		changeTable[row.BuildingType][row.ResourceType] = row.ResourceChange;
	end

	--loop thru all live players (human and AI).
	for index,pPlayer in pairs( Players ) do
		if pPlayer ~= nil and pPlayer:IsAlive() then
			
			--load list of previously applied building resources.
			local applied = load( pPlayer, "Building Resources" );
			local listing = {}; --new list of currently applied building resources.
			
			--loop thru player cities.
			for pCity in pPlayer:Cities() do
				local cityID = pCity:GetID();
				listing[cityID] = {};
				applied[cityID] = applied[cityID] or {};
				
				--loop thru relevant building types.
				for buildingType,resourceTable in pairs( changeTable ) do
					local buildingID = GameInfo["Buildings"][buildingType]["ID"];

					--look for matching buildings.
					if pCity:IsHasBuilding( buildingID ) then
						listing[cityID][buildingID] = buildingType;

						--apply resources for new buildings.
						if applied[cityID][buildingID] == nil then
							--loop thru resource changes.
							for resourceType,resourceChange in pairs( resourceTable ) do
								local resourceID = GameInfo["Resources"][resourceType]["ID"];
								pPlayer:ChangeNumResourceTotal( resourceID, resourceChange ); --cumulative.
							end --END resource loop.
						end
						--remove handled building case.
						applied[cityID][buildingID] = nil;

					end
				end --END building loop.

				--remove empty city case.
				if len( applied[cityID] ) == 0 then applied[cityID] = nil; end
			end --END city loop.

			--save list of currently applied building resources.
			save( pPlayer, "Building Resources", listing );

			--unapply resources for lost buildings.
			for cityID,buildingTable in pairs( applied ) do
				for buildingID,buildingType in pairs( buildingTable ) do
					--loop thru resource changes.
					for resourceType,resourceChange in pairs( changeTable[buildingType] ) do
						local resourceID = GameInfo["Resources"][resourceType]["ID"];
						pPlayer:ChangeNumResourceTotal( resourceID, -resourceChange ); --cumulative.
					end --END resource loop.
				end --END building loop.
			end --END city loop.

		end
	end --END player loop.
end
Events.ActivePlayerTurnStart.Add( applyBuildingResources );
--===========================================================================
--END BuildingResources
--===========================================================================
-- Created by: Whys Alives -Open source

OtherMod.lua:
Code:
include( "SaveUtils" ); MY_MOD_NAME = "[COLOR="Green"]MyMod[/COLOR]";[/COLOR]

function inspectPlayerSaves()
	setCacheState( 0 ); --no cache.
	for index,pPlayer in pairs( Players ) do
		if pPlayer ~= nil and pPlayer:IsAlive() then
			local data = load( pPlayer, nil, false );
			print(out( data ));
		end
	end
end
Events.ActivePlayerTurnStart.Add( inspectPlayerSaves );

MyMod.lua
Code:
[COLOR="Blue"]include( "SaveUtils" ); MY_MOD_NAME = "[COLOR="Green"]MyMod[/COLOR]";[/COLOR]
function doMyOwnThing()
	for index,pPlayer in pairs( Players ) do
		if pPlayer ~= nil and pPlayer:IsAlive() then
			[COLOR="Blue"]save( pPlayer, "myOwnThing", [COLOR="Green"]"I'm a team player."[/COLOR] );[/COLOR]
		end
	end
end
Events.ActivePlayerTurnStart.Add( doMyOwnThing );

If the following 2 commands appear anywhere outside of the SaveUtils file, then they need to be converted in the following manner, where "target" is a player, plot, or unit.

target:SetScriptData( value ); ...becomes...
save( target, key, value );

target:GetScriptData(); ...becomes...
load( target, key );

The "key" can be any string or integer and it is simply a name that you call the save data by. This allows for more than one piece of data per mod for each save slot by using different keys for identification. By putting a certain key in load, it knows you want the data you saved with that key. So you need to use different keys for different data then call it by name when you want it back.

Is this clear at all, or just confusing? Please let me know.
 
I think I get it now (well sort of...lol)

At the moment I have only no lua-files yet. So I copy the context of the SaveUtils (without any modification) to a new LUA-file in ModBuddy. Then next I copy the BuildingResources.lua To an other Lua file.

Then I change the MY_MOD_NAME to a unique MOD-name. I will add my initials and creatingdate+time to it to make sure no one else is going to use the same name.

If in future I will add other lua-files to the MOD, then I start the lua-script with:

include( "SaveUtils" ); MY_MOD_NAME = "My Mod Name"

And for "My Mod Name" I use the same unique name as I did before in the BuildingResources.lua file.

Did I get it this time?

Edit: one more question - do I have to change the action-tab in Modbuddy?
 
That is very correct. :) Just don't change anything in SaveUtils.

When putting Lua files together, there are a couple ways to do it, but the cleanest IMO is to add each file to the content tab as an InGameUIAddin. Then you don't have to mess around with the InGame.xml file.
 
Oh, it might also be easier to just download "Building Resources -- Template" in the online mods screen, then add files from there. Then you won't have to copy anything.
 
hi,

saw some older threads about how you didnt like having to do your updates with the turn start event...

you can use gamedatadirty event instead of activeplayerturnstart like this

Code:
function a ()
	for k,v in pairs(player) do
		if ( Players[ k ]:IsTurnActive() ) then
			--update buildings
		end
	end
end
Events.SerialEventGameDataDirty.Add( a )

you'll just need to add a flag for human player because gamedatadirty can be called for the human player more than once per turn. so either skip humans in gamedatadirty and use activeplayer, or set the flag on once done in gamedatadirty and back to 0 on turn end

gamedatadirty seems to trigger on turn start, but also when human gets popups, changes game info screen, and more than likely another instance i just havent found yet. plenty of other examples in my civ5 files btw if you're interested
 
Version 4. :)

Now supports custom notifications.

Still have the one issue of the TopPanel not instantly updating the happiness info. Anyone know how to fix this?

smellymummy, I appreciate the info. I'll have to give it some thought to see if it can fix my issue.
 
there is also the 'SpecificCityInfoDirty' event which seems to trigger on city creation, production change, player turns, and a few others. it passes 3 values (player, cityid, int). not sure what the third is

with the right handling it could do the resource updates in real time
 
I'd just like to say, I'm now using this mod component in my Alpha Centauri-inspired content mod and it's been working beautifully. It's been working fine for pre-existing resources, new strategics, and new luxuries that aren't found on the map. So kudos, Whys, for quickly figuring out how to do something that Firaxis should have been using from the start.
 
:) Version 5.

Upgraded to the newest version of SaveUtils with ShareData. ShareData allows SaveUtils' deserialized cache to be used by all concurrent mods and lua states as a super-global. The functionality of BuildingResources.lua itself has not changed, but the overall mod architecture has been improved for use as a compliance template.
 
:) Version 6.

Removed the InGame.xml, as it is no longer necessary for ShareData.lua.
 
Known Issue: when a luxury resource is first gained from a building, empire happiness in TopPanel does not update properly until unit moved or next turn.

Can't you just call OnTopPanelDirty() from TopPanel?
 
I can get it to execute at the proper time, but it doesn't resolve the issue. <shrug> I've been hoping someone else might figure it out for me. :)
 
I am aware of the issue, but currently there is no known solution.
 
Back
Top Bottom