Change values in game tables using lua

Danmacsch

Geheimekabinetsminister
Joined
Jan 14, 2014
Messages
1,316
Location
Copenhagen, Denmark
Hi,

I've been searching the forum for an answer, but unfortunately didn't find any.

Is it possible to make changes to the game tables using lua while in game? For example, if I want to change the cost of a unit or a building when a set of certain conditions are met. Is that even possible?
 
You can make changes, but the database is cached at game start, so it won't make a difference. You can do some effective changes to the translation game text database, however.

EDIT: That being said, you could accomplish what you're trying to do using Lua by, e.g., two or more buildings or units that are swapped.
 
About all you can try to do is calculate the "standard" cost of the unit or building (which is not always easy to do because you have to take so many modifiers into account), and then add production directly to a city to compensate for the difference between the "standard" cost and how much you want to change the cost.

It is easier to just create dummy buildings with one of:
Code:
<MilitaryProductionModifier>1</MilitaryProductionModifier>
<BuildingProductionModifier>1</BuildingProductionModifier>
<SpaceProductionModifier>1</SpaceProductionModifier>
<WonderProductionModifier>1</WonderProductionModifier>
depending on whether you are wanting to adjust "cost" of units, buildings, or wonders, etc., and then add as many dummy buildings for unit production as needed to create a 30% change in "costs" for example. It's a modifier on current city production instead of a direct change in the # of hammers required, but it's about the closest 'easy' thing to program in lua that does not also allow of too-easy exploit in the hands of a human player (the effects are not actually applied except as part of turn processing, and are extra production only towards the correct type of item).

[edit]big gorilla in the room is that gold purchasing costs would not be affected one way or the other
 
Thanks for the answers.

And I'm sorry, I should have specified: the changing of cost of a unit/building was just an example, and isn't what I'm looking to do. What I what to do is - for a Decision (support for Sukritact's E&D) for a custom civ - to change a specific setting of a building (the UB for the custom civ).

But maybe there's another way around it, someone can think of.

The UB is a Courthouse replacement, which can also only be built in occupied cities. As far as I know, the only way to 'tell' the game that for a specific building, is to use the 'NoOccupiedUnhappiness' tag in the buildings table, and thus, is what I have currently done. Now I thought (and hoped) it would be possible to change this setting to false for the building by using lua (when a Decision is enacted). Further, I also wanted to change the maintenance cost of the building with the same Decision.
Can you guys see a possible workaround for this issue?
 
Uh, let's see if I understand what you want:
  1. Player_X has not enacted Decision_X
    • Courthouse UB has
      Code:
      <NoOccupiedUnhappiness>true</NoOccupiedUnhappiness>
      <GoldMaintenance>4</GoldMaintenance>
    • Courthouse UB can be constructed only in conquered cities
  2. Player_X has enacted Decision_X
    • Courthouse UB has
      Code:
      <NoOccupiedUnhappiness>false</NoOccupiedUnhappiness>
      <GoldMaintenance>3</GoldMaintenance>
    • Courthouse UB can be constructed anywhere, but there is no longer any way to quell occupied unhappiness in conquered cities via a building.
Note that I just used 3 and 4 as discussion values for the GoldMaintenance

If I have understood what you are looking for, then you need two copies of the building (Courthouse_UB_1 and Courthouse_UB_2), and in addition to the Decisions code you will probably need a CityCanConstruct lua event. It would probably also be a good idea when the decision is enacted to give the player a dummy policy (POLICY_DUMMY_X), and control the CityCanConstruct 'result' for Courthouse_UB_1 and Courthouse_UB_2 primarily upon whether the player has POLICY_DUMMY_X.
 
Almost correctly, but actually I still wanted the building to quell occupied unhappiness after the decision is enacted.
I'm not that familiar with Lua Events though (I assume they are of a different nature than regular GameEvents and possibly also Events). I checked the (somewhat outdated) modiki page on LuaEvents, but there wasn't anything called CityCanConstruct..
Could you possibly provide me with some more info or point me in the direction of some.

Thanks in advance, LeeS.
 
CityCanConstruct is the 5th one down in the GameEvents.

By "lua event" I did not necessarily mean LuaEvents. I did not realize you would literaly interprete that as meaning CityCanConstruct was an LuaEvent. I meant it as you would have to add an event function in your lua.

But by setting NoOccupiedUnhappiness to false is by definition disabling the ability to quell the occupied unhappiness, so I am :confused:
 
You'll need three buildings

1) Standard courthouse replacement, NoOccupiedUnhappiness=true, maintenance=5
2) Cheap courthouse replacement, NoOccupiedUnhappiness=true, maintenance=3
3) Pseudo-courthouse, NoOccupiedUnhappiness=false, maintenance=3

The game will only permit 1 and 2 to be built in annexed cities and 3 to only be built in core cities, so you'll need some additional Lua (the PlayerCanConstruct handler) to further limit 1 to only be built if the decision is NOT enacted and 2 and 3 to only be built if it HAS been enacted

You may also need Lua to delete any of the expensive courthouses and replace them with cheap versions when the decision is enacted.
 
You'll need three buildings

1) Standard courthouse replacement, NoOccupiedUnhappiness=true, maintenance=5
2) Cheap courthouse replacement, NoOccupiedUnhappiness=true, maintenance=3
3) Pseudo-courthouse, NoOccupiedUnhappiness=false, maintenance=3

The game will only permit 1 and 2 to be built in annexed cities and 3 to only be built in core cities, so you'll need some additional Lua (the PlayerCanConstruct handler) to further limit 1 to only be built if the decision is NOT enacted and 2 and 3 to only be built if it HAS been enacted

You may also need Lua to delete any of the expensive courthouses and replace them with cheap versions when the decision is enacted.
Whoward for the win in understanding what it was DanMacsch appears to have been after and what it would require.

I could not seem to wrap my head around the courthouse that could be built everywhere and yet still having a 'courthouse' that quelled the occupied unhappiness.

----------------------------------------------------------------------

DanMacsch,

  • My notion of adding a dummy policy mentioned earlier when the decision is enacted was to provide an easy way after game save and reload to determine if the decision had been taken without needing to go to data persistence methods.
  • I've been trending away from sticking marker dummy buildings in the capital (for example) because while those work just fine, the player's capital can be captured, and this possibility requires a whole boatload of additional 'tracking' methods to ensure you do not 'lose' the info on whether or not 'x' has occured before.
  • Dummy Policies are much easier to make use of if you do not wish to set-up some form of SaveUtils or TableSaverLoader data-persistence method.
 
Okay, so I've tried doing what was suggested here, and it partially works, but there is something I can't get working and I don't understand why.

As whoward69 suggested I've created three buildings
Code:
INSERT INTO Buildings 	
		(Type, 										BuildingClass,	Cost,	HurryCostModifier,	GoldMaintenance,	PrereqTech,	NoOccupiedUnhappiness,	Description, 						Civilopedia, 						Help, 									Strategy,								 	ArtDefineTag, 	MinAreaSize,	ConquestProb,	PortraitIndex, 	IconAtlas)
SELECT	('BUILDING_DMS_KOYANG'),					BuildingClass,	Cost,	HurryCostModifier,	3,					PrereqTech,	NoOccupiedUnhappiness,	('TXT_KEY_BUILDING_DMS_KOYANG'),	('TXT_KEY_CIV5_DMS_KOYANG_TEXT'),	('TXT_KEY_BUILDING_DMS_KOYANG_HELP'),	('TXT_KEY_BUILDING_DMS_KOYANG_STRATEGY'),	ArtDefineTag,	MinAreaSize,	ConquestProb,	3, 				('DMS_ARAUCANIAPATAGONIA_ORELIE_ATLAS')
FROM Buildings WHERE (Type = 'BUILDING_COURTHOUSE');

[COLOR="Green"]-- Koyang for annexed cities after Constitution Decision is enacted [/COLOR]
INSERT INTO Buildings 	
		(Type, 										BuildingClass,	Cost,	HurryCostModifier,	GoldMaintenance,	PrereqTech,	NoOccupiedUnhappiness,	Description, 						Civilopedia, 						Help, 									Strategy,								 	ArtDefineTag, 	MinAreaSize,	ConquestProb,	PortraitIndex, 	IconAtlas)
SELECT	('BUILDING_DMS_KOYANG_CONSTITUTION'),		BuildingClass,	Cost,	HurryCostModifier,	1,					PrereqTech,	NoOccupiedUnhappiness,	('TXT_KEY_BUILDING_DMS_KOYANG'),	('TXT_KEY_CIV5_DMS_KOYANG_TEXT'),	('TXT_KEY_BUILDING_DMS_KOYANG_HELP'),	('TXT_KEY_BUILDING_DMS_KOYANG_STRATEGY'),	ArtDefineTag,	MinAreaSize,	ConquestProb,	3, 				('DMS_ARAUCANIAPATAGONIA_ORELIE_ATLAS')
FROM Buildings WHERE (Type = 'BUILDING_COURTHOUSE');

[COLOR="Green"]-- Koyang for regular cities after Constitution Decision is enacted [/COLOR]
INSERT INTO Buildings 	
		(Type, 										BuildingClass,	Cost,	HurryCostModifier,	GoldMaintenance,	PrereqTech,	NoOccupiedUnhappiness,	Description, 						Civilopedia, 						Help, 									Strategy,								 	ArtDefineTag, 	MinAreaSize,	ConquestProb,	PortraitIndex, 	IconAtlas)
SELECT	('BUILDING_DMS_KOYANG_CONSTITUTION_PSUDO'),	BuildingClass,	Cost,	HurryCostModifier,	1,					PrereqTech,	0,						('TXT_KEY_BUILDING_DMS_KOYANG'),	('TXT_KEY_CIV5_DMS_KOYANG_TEXT'),	('TXT_KEY_BUILDING_DMS_KOYANG_HELP'),	('TXT_KEY_BUILDING_DMS_KOYANG_STRATEGY'),	ArtDefineTag,	MinAreaSize,	ConquestProb,	3, 				('DMS_ARAUCANIAPATAGONIA_ORELIE_ATLAS')
FROM Buildings WHERE (Type = 'BUILDING_COURTHOUSE');
Then I've made sure that when the decision in question is enacted, the player is given a policy (this was already the case) and all Koyang buildings (first one in the above sql) is changed to the cheaper one (second one in the above sql):
Code:
local buildingKoyangID										= GameInfoTypes["BUILDING_DMS_KOYANG"]
local buildingKoyangConstitutionAnnexedID					= GameInfoTypes["BUILDING_DMS_KOYANG_CONSTITUTION"]
local buildingKoyangConstitutionRegularID					= GameInfoTypes["BUILDING_DMS_KOYANG_CONSTITUTION_PSUDO"]
local policyKoyangDecisionConstitutionID					= GameInfoTypes["POLICY_DMS_ARAUCANIAPATAGONIA_CONSTITUTION"]

Decisions_AraucaniaPatagoniaOrelieAntoineWriteTheConstitution.DoFunc = (
	function(pPlayer)
		local iCulture = math.ceil(400 * iMod)
		pPlayer:ChangeJONSCulture(-iCulture)
		pPlayer:ChangeNumResourceTotal(iMagistrate, -2)
		-- culture yield from Koyangs
		pPlayer:SetNumFreePolicies(1)
		pPlayer:SetNumFreePolicies(0)
		pPlayer:SetHasPolicy(policyKoyangDecisionConstitutionID, true)
		-- replace standard Koyangs with cheaper ones
		for pCity in pPlayer:Cities() do
			if pCity:IsHasBuilding(buildingKoyangID) then
				pCity:SetNumRealBuilding(buildingKoyangID, 0)
				pCity:SetNumRealBuilding(buildingKoyangConstitutionAnnexedID, 1)
			end
		end

		save(pPlayer, "Decisions_AraucaniaPatagoniaOrelieAntoineWriteTheConstitution", true)
	end
	)
This works correctly. But this is where I run into problems. I've created three PlayerCanConstruct events; one function to ensure only a specific civtype can build the two non-standard Koyang buildings (the 2nd and 3rd in the sql above), another function to ensure the same two Koyang buildings only a buildable after the decision is enacted, and a third one to ensure that after the decision is enacted the standard Koyang building isn't buildable anymore.
Code:
local tBuildToCivilization = {
  buildingKoyangConstitutionRegularID,
  buildingKoyangConstitutionAnnexedID
}

function OnPlayerCanConstructKoyangCivTypeSpecific(playerID, BuildingType)
	local pPlayer = Players[playerID]
	if (tBuildToCivilization[BuildingType]) then
		-- not produce-able by any other civs than Araucanía and Patagonia
		if not pPlayer:GetCivilizationType() == civilizationAraucaniaPatagoniaOrelieAntoineIID then
		print("OnPlayerCanConstructKoyangCivTypeSpecific: Koyang decision buildings not produce-able by any other than Araucanía and Patagonia..")
		return false
		end
	end
	return true	
end
GameEvents.PlayerCanConstruct.Add(OnPlayerCanConstructKoyangCivTypeSpecific)

function OnPlayerCanConstructKoyangConstitution(playerID, BuildingType)
	local pPlayer = Players[playerID]
	if (tBuildToCivilization[BuildingType]) then
		-- if decisions is not enacted then the 'decision Koyang' for annexed cities should not be produce-able.
		if not pPlayer:HasPolicy(policyKoyangDecisionConstitutionID) then
		print("OnPlayerCanConstructKoyangConstitution: decisions is not enacted: 'decision Koyang buildings' are not produce-able.")
		return true
		end
	end
	return true	
end
GameEvents.PlayerCanConstruct.Add(OnPlayerCanConstructKoyangConstitution)

function OnPlayerCanConstructKoyang(playerID, BuildingType)
	local pPlayer = Players[playerID]
	if (BuildingType == buildingKoyangID) then
		-- if decisions is enacted then the regular 'expensive Koyang' should not be produce-able.
		if pPlayer:HasPolicy(policyKoyangDecisionConstitutionID) then
			print("OnPlayerCanConstructKoyang: decisions is enacted: regular 'expensive Koyang' is not produce-able.")
		return false
		end
	end
	return true	
end
GameEvents.PlayerCanConstruct.Add(OnPlayerCanConstructKoyang)
But for some reason, only the OnPlayerCanConstructKoyang function fires - or at least, that's the only one where the print statement shows.

Help is - as always - much appreciated :).
 
PlayerCanConstruct is an "All" event (ie a logical and), so as soon as one handler returns false, none of the others will be called (and it looks like Add uses a LIFO list to call the handlers)

You'll need to merge the logic from all three of your handlers into one.
 
Okay, thanks for your answer - that's valuable info for this as well as for future projects.

I will try merging them somehow. I wonder though, is this also the case with CityCanConstruct?
 
Yep, all core Firaxis game events are "All", as are most of the modded DLL ones (there are probably less than 10 "Any" events in the modded DLL)

Edit: I lie! Since Firaxis borrowed my events, there are four "Any" hooks in the core DLL
 
Also it would seem this part would not actually do anything, because all I can see is "return true":
Code:
function OnPlayerCanConstructKoyangConstitution(playerID, BuildingType)
	local pPlayer = Players[playerID]
	if (tBuildToCivilization[BuildingType]) then
		-- if decisions is not enacted then the 'decision Koyang' for annexed cities should not be produce-able.
		if not pPlayer:HasPolicy(policyKoyangDecisionConstitutionID) then
		print("OnPlayerCanConstructKoyangConstitution: decisions is not enacted: 'decision Koyang buildings' are not produce-able.")
		return [COLOR="Blue"]true[/COLOR]
		end
	end
	return [COLOR="blue"]true[/COLOR]	
end
GameEvents.PlayerCanConstruct.Add(OnPlayerCanConstructKoyangConstitution)
 
Also it would seem this part would not actually do anything, because all I can see is "return true"
Yeah, corrected that afterwards though..

But I'm still having trouble. I tried merging my statements, but something is clearly wrong:
Code:
local tBuildToCivilization = {
  buildingKoyangConstitutionRegularID,
  buildingKoyangConstitutionAnnexedID
}

function OnCityCanConstructKoyang(playerID, BuildingType)
	local pPlayer = Players[playerID]
	if (BuildingType == buildingKoyangID) then
		-- if decisions is enacted then the regular 'expensive Koyang' should not be produce-able.
		if pPlayer:HasPolicy(policyKoyangDecisionConstitutionID) then
			print("OnPlayerCanConstructKoyang: decisions is enacted: regular 'expensive Koyang' is not produce-able.")
		return false
		end
	end
	return true	
end
GameEvents.CityCanConstruct.Add(OnCityCanConstructKoyang)
Okay, so OnCityCanConstructKoyang is meant to remove the option of building the Koyang if the player has the required policy, but maybe I'm misunderstanding the logic.
Then secondly I have:
Code:
function OnPlayerCanConstructKoyang(playerID, buildingType)
	print("OnPlayerCanConstructKoyang initializing..")
	-- only care if building is a Koyang variant
	if (not tBuildToCivilization[buildingType]) then
		print("OnPlayerCanConstructKoyang: return true if building type is not Koyang variant..")
		return true;
	end
	-- only care if player is AraucaniaPatagonia
	
	if Players[playerID]:GetCivilizationType() ~= civilizationAraucaniaPatagoniaOrelieAntoineIID then
		print("OnPlayerCanConstructKoyang: return true function if player is not AraucaniaPatagonia..")
		return true;
	end
	-- allow player to construct Koyang variants
	if Players[playerID]:HasPolicy(policyKoyangDecisionConstitutionID) then
		print("OnPlayerCanConstructKoyang: allow AraucaniaPatagonia to build Koyang Variant if the have policy..")
		return true;
	end
	return false
end
GameEvents.PlayerCanConstruct.Add(OnPlayerCanConstructKoyang)
Here, OnPlayerCanConstructKoyang is meant to allow the two variant Koyang buildings to be built if the player has the required policy. But right now, the only statement I receive, is:
Code:
OnPlayerCanConstructKoyang: return true if building type is not Koyang variant.."
And if selecting a foreign city via the In-Game Editor tool, I get:
Code:
OnPlayerCanConstructKoyang: return true function if player is not AraucaniaPatagonia.."
 
Sure. Here's the lua file and here's the entire mod just in case that's easier.. Note that there's a lot of clutter-code and stuff that I need to delete, so the code isn't all that pretty.

The main lua functions file together with the Decisions file (in which the policy is granted to the player), can be found in the lua folder. The regular Koyang building (build-able before the decision is enacted) is defined in the DMS_AraucaníaPatagoniaOrélieAntoineI_GameDefines.sql folder, and the other two Koyang buildings plus the Policy is defined in the DMS_AraucaníaPatagoniaOrélieAntoineI_GameDefines_ModSupport.sql folder.

I really hope you'll be able to identify what I've done wrong LeeS, but in any case, thanks for your help.
 
I would have coded as follows, but I do not believe there is any real functional difference between the way I would code and the way you did.
Code:
function OnPlayerCanConstructKoyang(playerID, buildingType)
	print("OnPlayerCanConstructKoyang initializing..")
	if (tBuildToCivilization[buildingType]) then
		if Players[playerID]:GetCivilizationType() == civilizationAraucaniaPatagoniaOrelieAntoineIID then
			if Players[playerID]:HasPolicy(policyKoyangDecisionConstitutionID) then
				print("OnPlayerCanConstructKoyang: allow AraucaniaPatagonia to build Koyang Variant if they have policy..")
				return true
			else
				print("OnPlayerCanConstructKoyang: dis-allow AraucaniaPatagonia to build Koyang Variant if they do not have policy..")
				return false
			end
		else	print("OnPlayerCanConstructKoyang: return false from function if player is not AraucaniaPatagonia..")
			return false
		end
	else	print("OnPlayerCanConstructKoyang: return true if building type is not Koyang variant..")
		return true
	end
end
GameEvents.PlayerCanConstruct.Add(OnPlayerCanConstructKoyang)
I believe, however, your problem is not in the lua code for the function. It is in the construction of table:
Code:
local tBuildToCivilization = {
  buildingKoyangConstitutionRegularID,
  buildingKoyangConstitutionAnnexedID
}
This will result in table structure such as:
Code:
tBuildToCivilization[1] = buildingKoyangConstitutionRegularID
tBuildToCivilization[2] = buildingKoyangConstitutionAnnexedID
So that when you do this (taken directly from your original code):
Code:
if (not tBuildToCivilization[buildingType]) then
It will be the same as saying either
Code:
if (not tBuildToCivilization[1]) then
or
Code:
if (not tBuildToCivilization[2]) then
which would make the code want to reference back to BUILDING_MUGHAL_FORT and BUILDING_KREPOST, I think, since I think they are ID#s "1" and "2" in the Buildings table.
Alter your table as:
Code:
local tBuildToCivilization = { [buildingKoyangConstitutionRegularID] = "true",
  [buildingKoyangConstitutionAnnexedID] = "true"
}
This will place the correct building ID# into the "key" part of the table pairs, and will throw "true" into the "value" sides of the table's pairs just to have something there besides "nil". This will make the table structure as if you had stated
Code:
local tBuildToCivilization = { }
tBuildToCivilization[buildingKoyangConstitutionRegularID] = "true"
tBuildToCivilization[buildingKoyangConstitutionAnnexedID] = "true"
and matches up to your code here:
Code:
if (not tBuildToCivilization[buildingType]) then

------------------------------------------------------------------------------------------------

You also have problems here (and perhaps elsewhere as well in your decisions-support SQL:
Code:
------------------------------
-- Policy_BuildingClassCultureChanges
------------------------------
INSERT INTO Policy_BuildingClassCultureChanges
		(PolicyType,					BuildingClassType,				CultureChange)
VALUES		('POLICY_DMS_ARAUCANIAPATAGONIA_CONSTITUTION',	'[COLOR="Red"]BUILDING[/COLOR]_DMS_KOYANG',				2),
		('POLICY_DMS_ARAUCANIAPATAGONIA_CONSTITUTION',	'[COLOR="red"]BUILDING[/COLOR]_DMS_KOYANG_CONSTITUTION',		2),
		('POLICY_DMS_ARAUCANIAPATAGONIA_CONSTITUTION',	'[COLOR="red"]BUILDING[/COLOR]_DMS_KOYANG_CONSTITUTION_PSUDO',	2);
The name of the table is Policy_BuildingClassCultureChanges. I suspect this is actually making the SQL file fail in whole or in part. Unfortunately Database.log is not near as friendly with SQL failures as it is with XML failures.

Additionally, Policy_BuildingClassCultureChanges will not impliment a culture-change to a Building within a BuildingClass which is neither the default Building within the class (and then only for Civilizations which have no unique version within the class), nor a unique replacement within that class (and then only for the Civilization(s) which have a <Civilization_BuildingClassOverrides> entry to make that Building their unique within the class). See Issues With Effects "Refusals": in Post #1 of This Reference I Created.
  • None of these buildings are the default within their class
  • Only one of them could have been the unique within the class for your civ. I did not look to see if you have designated any so.
  • In any case you must reference the BUILDINGCLASS_SOMETHING to which these buildings belong, and would only end up with one entry in the table, which would be BUILDINGCLASS_COURTHOUSE. Even fixing this will not fix the 'refusal' issues with adding the culture.
 
You can make changes, but the database is cached at game start, so it won't make a difference. You can do some effective changes to the translation game text database, however.

EDIT: That being said, you could accomplish what you're trying to do using Lua by, e.g., two or more buildings or units that are swapped.
Are you saying that english text aren't cached and could therefore be changed while in the game?
 
They are cached, but you can refresh by changing the display language.

You can use DB.Query() to change entries in the language database, then refresh the cache with Locale.SetCurrentLanguage(Locale.GetCurrentLanguage().Type).

In most cases, it's easier to do a Controls.controlname:SetText(), but if you want to, e.g., change the civ or leader name halfway through the game, then DB.Query(sqlstatement) would be a good way to go.
 
Top Bottom