A couple more "quick" inquiries

See up-page @ #352. The CityCanConstruct lua is irrelevant because it will only fire if something is possible by the settings of all code within the SQL database. Two mutually-exclusive buildings are also mutually exclusive to themselves and once one copy of any of these mutually-exclusive buildings is present within a city, no more may be constructed within that same city regardless of the Building-Class "NoLimit" settings.

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

You would have to remove the <MutuallyExclusiveGroup> settings from the definitions of the buildings in order for the lua script to be used, but you still won't be able to construct multiple copies of the same building within the same city. Placing them there directly by lua is another animal entirely.

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

As far as the lua itself goes, you missed an ending ")" here:
Code:
GameEvents.CityCanConstruct.Add(
function(iPlayer, iCity, iBuilding)
	local pPlayer = Players[iPlayer]
	local pCity = pPlayer:GetCityByID(iCity)
	if ((iBuilding == 1) or (iBuilding = 2)) then
	-- obviously we would change those values :P
		if ((iBuilding == 1) and (pCity:IsHasBuilding(2)) 
		or ((iBuilding == 2) and (pCity:IsHasBuilding(1))
		then return false end
	end
	return true
end[color="red"])[/color]
Or you chopped it off when you copy/pastad to the forum.

I would probably have written the code a little differently in order to make it easier to follow what I am trying to do, as in:
Code:
GameEvents.CityCanConstruct.Add(
function(iPlayer, iCity, iBuilding)
	local pPlayer = Players[iPlayer]
	local pCity = pPlayer:GetCityByID(iCity)
	if (iBuilding == 1) then
		return (not pCity:IsHasBuilding(2))
	elseif (iBuilding = 2)) then
		return (not pCity:IsHasBuilding(1))
	end
	return true
end)
Or I would have set-up a table of correspondances if everything is direct 1-to-1 relationships between two types of buildings and thrown these correspondances into an lua-table:
Code:
local [COLOR="green"]tMutuallyExclusiveBuildings[/COLOR] = { [COLOR="Red"][1][/COLOR] = [COLOR="Blue"]2[/COLOR], [COLOR="red"][2][/COLOR] = [COLOR="blue"]1[/COLOR] }

GameEvents.CityCanConstruct.Add(
function(iPlayer, iCity, iBuilding)
	if [COLOR="red"]tMutuallyExclusiveBuildings[iBuilding][/COLOR] then
		local pPlayer = Players[iPlayer]
		local pCity = pPlayer:GetCityByID(iCity)
		return (not pCity:IsHasBuilding([COLOR="blue"]tMutuallyExclusiveBuildings[iBuilding][/COLOR]))
	end
	return true
end)
I color-coded to show which part of the code is pulling data from which side of the 'Key,Value' pairs from the table I called tMutuallyExclusiveBuildings. With the code constructed in such a way you can place 1 or 10000 pairs of such corresondances within the lua-table, and the rest of the code does not need to change.
 
Ah, here's the problem I'm finding in retesting: NoLimit doesn't work :p
Even without MutuallyExclusiveGroup.
And without Lua.

After I build one "river" building, it doesn't continue to appear in the build menu, plus:
Code:
> print(Players[0]:GetCapitalCity():CanConstruct(GameInfoTypes.BUILDING_AW_TEXTILE_MILL_RIVER))
 [I]UA_Functions_Belgium: false[/I]
So... any other ideas?
 
.... but you still won't be able to construct multiple copies of the same building within the same city.
did try to tell you

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

A refresh on what it is you are trying to accomplish would help.
 
Didn't see :/
That's just so... well, frankly, stupid. A bad coding decision on the part of Firaxis - unless I'm not understanding what <NoLimit> is intended to do?

A refresh on what it is you are trying to accomplish would help.
It's for my civ's UB. It's not actually very useful in and of itself, but it allows you to build "textile mills" in your city. These can be "powered" by either water or steam - that is, they require either a river or coal to be built. The key points to note are 1) cities' mills are either powered by rivers XOR steam; your city has to pick a side and stick with it, and 2) there's no limit to how many mills can be built in a city. (Although penalties start stacking up almost exponentially to counterbalance this and discourage you from building more than MAYBE 2 max per city)

At any rate, I think I've come up with a possible solution, although it's a bit convoluted. Basically, we make a dummy version of each mill and swap it out for the real thing every time it's built. So, the dummy gets built, is immediately destroyed once finished, and an actual mill is surreptitiously added in via Lua.

As far as pseudocode:
Code:
GameEvents.CityConstructed.Add(
function(iPlayer, iCity, iBuilding)
	local pPlayer = Players[iPlayer]
	local pCity = pPlayer:GetCityByID(iCity)

	if (iBuilding == 1_dummy) then
		pCity:SetNumRealBuilding(1_dummy, 0)
		pCity:SetNumRealBuilding(1, pCity:GetNumBuilding(1)+1)
	end

	if (iBuilding == 2_dummy) then
		pCity:SetNumRealBuilding(2_dummy, 0)
		pCity:SetNumRealBuilding(2, pCity:GetNumBuilding(2)+1)
	end

end)
And then we use CityCanConstruct to pit steam dummy vs. river actual, and river dummy vs. steam actual, if that makes sense.
We might need some way to prevent the mills from each seemingly showing up in the pedia twice.

Sound functional, if perhaps not elegant?
 
  1. Make the Steam Dummy need the Steam Real, and the River Dummy need the River Real, in a city via <Building_ClassesNeededInCity>
  2. Handle your exclusivity requirements by settings of Steam Real and River Real and try the table <Building_LockedBuildingClasses> instead of the <MutuallyExclusiveGroup>
  3. The first copy of either Steam Real or Rvier Real is constructed in the normal way, which then unlocks for that city the construction of the appropriate Dummy.
  4. Then handle the swap-out of the dummy for the second, third, fourth 'real' via the CityConstructed lua-event
  5. Just keep in mind stacking limitations on building effects within the same city for whatever it is you want Steam Real and River Real to do.
 
So good news! The thing is working, with the dummy buildings and textile mills and stuff.
...I didn't just get it working, but I forgot to mention it for... 3 weeks, evidently.
The only thing keeping me from release now is <shudders> E&D.

There is the one quirk, which is that all the textile mills I build show up as only one icon in the city screen. So, it always looks as if there's only one textile mill in the city, though there might be, say, 6, as confirmed by the effects of 6 textile mills as well as FireTuner. Then, if I want to sell them, they all get sold at once. This might just be the doing of <NoLimit> being true, I don't know.

But anyway, yaaaaaaaaaay LeeS

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

That said, the next problem is simply an algorithm-devising one.

I'm thinking a cool UA might be:
Monopolize any luxury resource which you are the first to obtain.
Essentially this means that if, say, I get silver before anyone else, then no one else can benefit from silver unless they get it from me.

There's a couple ways to do this...

1) Make a dummy version which gives no happiness of every luxury resource and, when a resource is monopolized, iterate through all the plots in the world which contain that resource (not owned by the monopoly owner) and switch it to the dummy resource.

2) When a resource is monopolized, iterate through all the plots in the world which contain that resource (not owned by the monopoly owner) and switch it to a non-luxury resource. Crabs might turn into Fish, for example, or Silver turning into Fish.

3) When someone takes control of a monopolized resource not via import (partially checkable by int Player.GetTotalResourceAvailable(int ResourceType, bool IncludeImports), remove it directly via Lua.

1 and 2 have a similar problem... namely, iterating through plots. Somehow we'd need to create tables of luxury-specific plots to iterate through. As I discovered a little while, we can't just iterate through all the plots in the world. :p We might be able to switch it to make monopolized resources go poof only once a player's borders come to include the plot (with GameEvents.CityBoughtPlot, I think), but that will cause the behavior of the AI - and possibly the human - to become very screwy and confused, as they would continually settle near silver only to have it turn into stone every time.

We could edit ASP.lua to create some global luxury-specific plot tables ahead of time, but that would make it incompatible with other very popular mods, such as More Luxuries and Optimized Resource Distribution.

So I need to figure which method would work the most smoothly.

If it helps, I made a utility one time to track player-owned plots (intended to work with TSL Serializer), but it's untested and I have no idea whether it's at all useful, but here it is anyway:
Spoiler :
Code:
include("PlotIterators.lua")

tPlayerPlots = {}
for iPlayer, _ in pairs(Players) do
	if (tPlayerPlots[iPlayer] == nil) then
		tPlayerPlots[iPlayer] = {}
	end
end

-->-----------> DIRECT EDITS <----------<--
-- They may never be need to be used, but it never hurts to allow the possibility

function AddPlot(iPlayer, iX, iY, pPlot)
	-- pPlot exists for the possibility if the x and y is not known
	if iX and iY then
		table.insert(tPlayerPlots[iPlayer], Map.GetPlot(iX, iY))
	else
		table.insert(tPlayerPlots[iPlayer], pPlot)
	end
end

function RemovePlot(iPlayer, iX, iY, pPlot)
	if iX and iY then
		table.remove(tPlayerPlots[iPlayer], Map.GetPlot(iX, iY))
	else
		table.remove(tPlayerPlots[iPlayer], pPlot)
	end
end

-->----------> REFRESH TABLE <----------<--
-- For re-calculating the table, not editing it directly

function CleanPlots(iPlayer) -- remove plots in a player's plot table that don't belong there
	for iPlayer, pPlot in pairs(tPlayerPlots) do
		if not (pPlot:GetOwner() == iPlayer) then
			table.remove(tPlayerPlots[iPlayer], pPlot)
		end
	end
end

function ErasePlots(iPlayer) -- Wipe out a specific player's entire plot table
	-- if no iPlayer or iPlayer == -1, reset the entire table
	if (iPlayer == nil) or (iPlayer < 0) then
		tPlayerPlots = nil;
		tPlayerPlots = {}
	else
		tPlayerPlots[iPlayer] = nil;
		tPlayerPlots[iPlayer] = {}
	end
end

-->----------> AUTO-ADD PLOTS <---------<--
-- The gem of the whole table, which automatically adds plots as the occasion requires

function AddPlots_CityCaptured(iOldPlayer, bCapital, iX, iY, iNewOwner, bConquest)
	local pPlot = Map.GetPlot(iX, iY)
	for pCityPlot in PlotAreaSweepIterator(pPlot, 5, SECTOR_NORTH, DIRECTION_CLOCKWISE, DIRECTION_OUTWARDS, CENTRE_EXCLUDE) do
		if (pCityPlot:GetOwner() == iNewPlayer) then
			table.insert(tPlayerPlots[iNewPlayer], pCityPlot)
		end
	end
end
GameEvents.CityCaptureComplete.Add(RemovePlots_CityLost)

function AddPlots_CityFounded(iPlayer, iX, iY)
	local pPlot = Map.GetPlot(iX, iY)
	table.insert(tPlayerPlots[iPlayer], pPlot)
	for pCityPlot in PlotAreaSweepIterator(pPlot, 1, SECTOR_NORTH, DIRECTION_CLOCKWISE, DIRECTION_OUTWARDS, CENTRE_EXCLUDE) do
		table.insert(tPlayerPlots[iPlayer], pCityPlot)
	end
end
GameEvents.PlayerCityFounded.Add(AddPlots_CityCaptured)

function AddPlots_Culture(iPlayer, iCity, iX, iY, bGold, bFaithOrCulture)
	local pPlot = Map.GetPlot(iX, iY)
	if bFaithOrCulture then
		table.insert(tPlayerPlots[iPlayer], pPlot)
	end
end
GameEvents.CityBoughtPlot.Add(AddPlots_Culture)

-->--------> AUTO-REMOVE PLOTS <--------<--
-- Necessary cleanup required that make use of CleanPlots

function RemovePlots_CityDestroyed(_, iPlayer, iCity)
	CleanPlots(iPlayer)
end
Events.SerialEventCityDestroyed.Add(RemovePlots_CityDestroyed)

function RemovePlots_CityLost(iOldPlayer, bCapital, iX, iY, iNewOwner, bConquest)
	CleanPlots(iOldPlayer)
end
GameEvents.CityCaptureComplete.Add(RemovePlots_CityLost)

TIA,
AW
 
I haven't looked through the rest of the posted code, but this leapt out at me:
Code:
function [color="green"]CleanPlots[/color]([color="blue"]iPlayer[/color]) -- remove plots in a player's plot table that don't belong there
	for [color="blue"]iPlayer[/color], [color="red"]pPlot[/color] in pairs([color="magenta"]tPlayerPlots[/color]) do
		if not (pPlot:GetOwner() == iPlayer) then
			table.remove([color="magenta"]tPlayerPlots[/color][[color="blue"]iPlayer[/color]], [color="red"]pPlot[/color])
		end
	end
end
  1. You don't need to send iPlayer down to function CleanPlots because you are defining iPlayer in your iteration through table tPlayerPlots.
  2. pPlot is being used incorrectly in one or another of the places shown:
    • it is being used as the "value" in the primary "key,value" pairs of table tPlayerPlots
    • It is later being as a "value" within a sub-table array [iPlayer] of the primary table tPlayerPlots
    • Can't be used both ways. Has to be one or the other. And in the way you are attempting to 'index' it with "GetOwner()", then the way you are using it here
      Code:
      for [color="blue"]iPlayer[/color], [color="red"]pPlot[/color] in pairs([color="magenta"]tPlayerPlots[/color]) do
      is the incorrect way to use it.
  3. You are attempting to remove a table-item while in the process of iterating through a list of the items within the same table. This will break the iteration-sequence and generally cause the code to not process all items within the original table.
    • I may have made this mistake when I wrote the Urartu code, but William has since taught me more better.
    This is the offending line I am talking about:
    Code:
    table.remove([color="magenta"]tPlayerPlots[/color][[color="blue"]iPlayer[/color]], [color="red"]pPlot[/color])
  1. Assuming that what you want is a primary table called tPlayerPlots where the primary "key,value" pairs are
    • "key"=[PlayerID #], and
    • "value"=sub-table of valid plots arranged as:
      1. "key"=item number of the item in the list as integers '1' to 'n', and
      2. "value"=plot reference as a pointer such as 'pPlot'
  2. What you need is something more like this (assuming I haven't made any whoopsies to further confuse the issue):
    Code:
    function CleanPlots() -- remove plots in a player's plot table that don't belong there
    	local tTempItemsTable = {}
    	--scan through all plots of all players registered in tPlayerPlots and gather a list of plots assigned to the wrong players
    	for iPlayer,PlayerPlotTable in pairs(tPlayerPlots) do
    		tTempItemsTable[iPlayer] = {}
    		for ItemNumber,pPlot in pairs(PlayerPlotTable) do
    			if pPlot:GetOwner() ~= iPlayer) then
    				table.insert(tTempItemsTable[iPlayer], ItemNumber)
    			end
    		end
    	end
    	--run through the list of all incorrectly assigned plots and remove them from the player plot subtables within the larger tPlayerPlots
    	for iPlayer,RemovalDatas in pairs(tTempItemsTable) do
    		local iMaxTempTable = #RemovalDatas
    		if iMaxTempTable == nil then
    			iMaxTempTable = 0
    		end
    		while iMaxTempTable > 0 do
    			table.remove(tPlayerPlots[iPlayer], RemovalDatas[iMaxTempTable])
    			iMaxTempTable = iMaxTempTable - 1
    		end
    	end
    end
    • You should only make your code execute function CleanPlots() once per turn since it is running through a list of plots assigned to all players. Having it run through all players every turn (as part of a PlayerDoTurn, for example) would seem like a lot of unecessary processing
  3. Just keep in mind that TableSaverLoader won't save pointer data such as 'pPlot' objects. It needs every "value" such as this to be in terms of an integer or a string or a boolean.
 
Just keep in mind that TableSaverLoader won't save pointer data such as 'pPlot' objects. It needs every "value" such as this to be in terms of an integer or a string or a boolean.
...didn't you write the Lua for Urartu which uses TSLS to track Berd plots?

(Guess I also have to update Asturias then)

Does TSLS allow saving tables? We may then need it to store {x,y} as the value. Then, rather than ever interacting with the actual persistent table - which would store coordinates - we could use GameEvents.GameSequenceInitComplete or whatever it's called (I'm too lazy to look it up) to create a non-persistent table:
Code:
tPlayerPlots = {}
for iPlayer, _ in pairs(Players) do
	if (tPlayerPlots[iPlayer] == nil) then
		tPlayerPlots[iPlayer] = {}
	end
end

GameEvents.GameSequenceInitComplete.Add(
function() -- I don't remember if it has any parameters either

	for iPlayer, table in pairs(tPlayerPlotCoords) do
		for k,v in pairs(table) do
			tPlayerPlots[iPlayer][k] = Map.GetPlot(v[1],v[2])
		end
	end

end)
And refresh the persistent table every so often:
Code:
for iPlayer, table in pairs(tPlayerPlotCoords) do
	table = {}
	for k, pPlot in pairs(tPlayerPlots[iPlayer]) do
		table[k] = {pPlot:GetX(), pPlot:GetY()}
	end
end

Spoiler :
Code:
--[[
	To be clear, tPlayerPlots is structured like this:
		
	tPlayerPlots = {
		Player #0: {
			0: Plot,
			1: Plot,
			2: Plot
		},
		Player #1: {
			0: Plot,
			1: Plot,
			2: Plot
	}

	Or in other words,

	{{Plot, Plot, Plot},{Plot, Plot, Plot}}

	A table of tables. 1st index = Player ID, 2nd index = ??? doesn't matter really
]]

And if we can't save tables - well, one X-coord table and one Y-coord table shouldn't be too messy.

~~~~~~~~~~~~~~~~~~~~~~~~~~~

Plus, of course, I need to decide on a monopoly algorithm. #3 to me seems like the best route... or, at least, the least bad one.
 
TableSaverLoader saves and reloads data that has been placed within lua-tables, but it does not like pointer data like 'pPlot' or 'pCity' for any of the "values" within any table.

DarkScythe's TableSaverLoader Serializer is a 'helper' function to make all mods running at the same time and which are using Pazyryk's TableSaverLoader not conflict with each other. DarkScythe's system does not in itself change the behavior of what TableSaverLoader will accept within the tables.

For Urartu I always saved all the plot data related to the Berd improvements as X,Y coordinates as like this
Code:
table.insert(TableName, {X=iPlotX,Y=iPlotY})
What I was talking about in the mistake I may have made in the Urartu code was doing this:
Code:
for k,v in pairs(TableName) do
	if v == "jello" then
		table.remove(TableName,k)
	end
end
  • This has the potential of not running through all the "k,v" pairs within "TableName" because the removal of an item from the table midstream causes all "k" values greater than the one being removed to be shifted down and they then become "k's" of values "15-1", "16-1", "17-1" etc instead of "k" values of "15", "16", and "17".
  • Where the "k" values within a table are not sequential "arrays" (ie, integers in order '1' to 'n' without skips between '1' and 'n') you can get the same incomplete processing of all the items within the table. So the issue applies whether you are using a table that is arranged as an "array" (sequential integers) or as what is referred to as a "hash".
  • You don't get runtime errors or crashing as a result of this sort of thing, you just don't actually get all the way through all the "k,v" pairs within the lua-table.
I'm sure William will correct my poor explanation in a couple of places. :lol:

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

I don't know that the functions for the Berd Improvement needs a hot-quick-patch-fix, but it is possible this mistake may at some point have to be addressed. To be honest I'd have to look at that code again and see whether or not I used correct scan-then-remove procedure or not.
 
As far as all copies of a building getting sold when you sell the building in city-view, I had opportunity to test this morning. This appears to be normal behavior to remove all the copies of the same building from the city and is not linked to whether the Building-Class has <NoLimit>1.

And multiple copies of the same building have always only ever used a single icon in the city-view within the list of buildings the city contains.

The basic reality is the game's UI for cityview just isn't written to actually make use of multiple copies of a single building within the same city. Nor is the gamecore code, really, even though it allows us to do this with lua's "SetNumRealBuildings".

At some point Firaxis looks to have been experimenting with multiple copies of the same building in the same city, but it also looks as if they didn't experiment with it for very long before abandoning the idea. Everything in the unmodded version of the game is coded for 1 City -> 1 Building even though there are some remaindered bits of code for 1 City -> 2+ Buildings scattered here and there (and which we mod-maker sneaky buggers make exploit of on a regular basis).

1So far as I can tell, <NoLimit> in <BuildingClasses> appears not to have any actual effect anywhere.
 
I'm sure William will correct my poor explanation in a couple of places. :lol:

Makes sense to me :)

Just to add, that if you do need to remove items from a table you are currently iterating over (technically known as concurrent modification) the only safe way to do it is to iterate the table storing the keys you need to remove into another table, and then iterate the keys stored in the second table and delete them from the first.

There is a special case if you are iterating an array (ie a special type of table where all the keys are sequentially ascending integers starting at 1) AND the order is not important, you can iterate the table backwards (ie from #table to 1) and delete the entries directly.
 
Another question, and I promise it's easy - a simple Yes or No (with optional elaboration):

Without Lua or a DLL mod (which supply workwarounds/solutions of which I'm aware) - that is, strictly with XML/SQL - is it possible to make an improvement unbuildable on all features?

Like Improvement_ValidFeatures, but opposite, essentially. And BuildFeatures is tricky, since it's usually used to stipulate that features like marsh/forest/jungle must first be removed... but the improvement in mind can only be built on desert tiles, and we don't want to remove oases or floodplains.
 
I did in fact make the goof mentioned upthread in the Berd Functions lua file. The attachment has an updated version to fix the potential issue wrt to scanning through an lua-table and removing items at the same time. Just extract the attachment and copy/paste the new version of the Berd_Functions.lua file file into the ModBuddy version of the mod. That way, next time you build, the fix will be in.
 

Attachments

  • NewBerdVersion.zip
    3.9 KB · Views: 123
This was too long for a PM, so here's a bag with a loose cat:

So, for the Berd Improvement, this is functioning and tested code:
Spoiler :
Code:
local DebugPrintMessages = true
local ShowGamePlayMessages = true
local tImprovementsToMonitor = {}	-- do not change this line


tImprovementsToMonitor[GameInfoTypes.IMPROVEMENT_AW_BERD] = {Handling= (
	function(iPlayer, tPlots, iRequiredCiv, iRequiredImprovementType, bWeCanStealAPlot, bMustBeOwned)
		local pPlayer = Players[iPlayer]
		local iNumImmuneGoldYields = 2	--maximum # of GOLD from an individual Berd before 'discounting' will occur,
		local iNumImmuneFoodYields = 2	--maximum # of FOOD from an individual Berd before 'discounting' will occur
		local iNumImmuneProdYields = 2	--maximum # of PRODUCTION from an individual Berd before 'discounting' will occur
		local iBerdYieldModifier = .67
		local sRoundingMethod = "down"
		local MathRoundingMethod = math.floor
		if sRoundingMethod == "up" then
			MathRoundingMethod = math.ceil
		end
		local pCapitalCity = pPlayer:GetCapitalCity()
		local tBerdYieldPlots = {}
		for ItemNumber,pPlot in pairs(tPlots) do
			if (pPlot:GetImprovementType() ~= -1) and (not pPlot:IsImprovementPillaged()) and (pPlot:GetImprovementType() == iRequiredImprovementType) then
				local bGrabAnAdjacentPlot = true
				if (pPlot:GetOwner() ~= iPlayer) and (pPlot:GetOwner() == -1) then
					pPlot:SetOwner(iPlayer)
					bGrabAnAdjacentPlot = false
				end
				local iFood, iGold, iProduction = 0, 0, 0
				local bAdjacentPlotClaimed = false
				local pNearestCity = pCapitalCity
				local iChance = 0

				if bGrabAnAdjacentPlot then
					iChance = LRS_GetRandom(1,10)
					PrintDebug("For adjacent plot land-grabbing iChance resulted in a value of " .. iChance)
				end
				if pPlayer:GetNumCities() > 1 then
					pNearestCity = GetNearestCityToPlot(pPlot, pPlayer)
				end
				local MoutainAdjacentPlots = {}
				local FirstPickAdjacentPlots = {}
				for direction = 0, DirectionTypes.NUM_DIRECTION_TYPES - 1, 1 do
					local pAdjacentPlot = Map.PlotDirection(pPlot:GetX(), pPlot:GetY(), direction)
					if pAdjacentPlot and not pAdjacentPlot:IsCity() then
						if not ItemIsInTable(tBerdYieldPlots,pAdjacentPlot) then
							if (pAdjacentPlot:GetOwner() == iPlayer) or (pAdjacentPlot:GetOwner() == -1) then
								if not pAdjacentPlot:IsBeingWorked() then
									--Get the Yields from the Adjacent plot
									iFood = iFood + pAdjacentPlot:GetYield(GameInfoTypes.YIELD_FOOD)
									iGold = iGold + pAdjacentPlot:GetYield(GameInfoTypes.YIELD_GOLD)
									iProduction = iProduction + pAdjacentPlot:GetYield(GameInfoTypes.YIELD_PRODUCTION)
								end
								if bGrabAnAdjacentPlot then
									if pAdjacentPlot:GetOwner() == -1 and iChance == 2 then
										if pAdjacentPlot:IsMountain() then
											table.insert(MoutainAdjacentPlots, pAdjacentPlot)
											PrintDebug("A Moutain Plot at " .. pAdjacentPlot:GetX() .. "," .. pAdjacentPlot:GetY() .. " was added to the temporary table MoutainAdjacentPlots")
										else
											table.insert(FirstPickAdjacentPlots, pAdjacentPlot)
											PrintDebug("A Non-Moutain Plot at " .. pAdjacentPlot:GetX() .. "," .. pAdjacentPlot:GetY() .. " was added to the temporary table FirstPickAdjacentPlots")
										end
									end
								end
							end
							-------------------------------------------------------------------------
							--marks the adjacent plot as already having been processed this turn
							-------------------------------------------------------------------------
							table.insert(tBerdYieldPlots, pAdjacentPlot)
						end
					end
				end

				if (#FirstPickAdjacentPlots + #MoutainAdjacentPlots) > 0 then
					local pGrabPlot = "NONE"
					if #FirstPickAdjacentPlots > 0 then
						pGrabPlot = FirstPickAdjacentPlots[math.random(#FirstPickAdjacentPlots)]
					else
						pGrabPlot = MoutainAdjacentPlots[math.random(#MoutainAdjacentPlots)]
					end
					PrintDebug("A plot adjacent to the Berd improvement plot at " .. pPlot:GetX() .. "," .. pPlot:GetY() .. " should be flipped to the players ownership because iChance is 2")
					pGrabPlot:SetOwner(iPlayer)
					PrintDebug("A plot at "..pGrabPlot:GetX()..","..pGrabPlot:GetY().." which is adjacent to the Berd improvement plot at " .. pPlot:GetX() .. "," .. pPlot:GetY() .. " was flipped to the players ownership")
					GameplayAlertMessage("Berd at ("..pPlot:GetX()..","..pPlot:GetY()..") grabbed an adjacent plot! Newly acquired plot is at "..pGrabPlot:GetX()..","..pGrabPlot:GetY())
				else
					PrintDebug("No plots adjacent to the Berd improvement plot at " .. pPlot:GetX() .. "," .. pPlot:GetY() .. " should be flipped to the players ownership because either iChance is not 2 or there are no more plots to grab")
				end

				PrintDebug("Before any discounting of yields, the Yields from the Berd Improvement at plot " .. pPlot:GetX() .. "," .. pPlot:GetY() .. " were FOOD = " .. iFood .. " , GOLD = " .. iGold .. " , PRODUCTION = " .. iProduction)

				-------------------------------------------------------------------------------------------------------------
				--Adjust the total yields coming from a single Berd Improvement based on the settings at the top of the file
				-------------------------------------------------------------------------------------------------------------
				if iFood > iNumImmuneFoodYields then
					iFood = iNumImmuneFoodYields + MathRoundingMethod((iFood - iNumImmuneFoodYields) * iBerdYieldModifier)
				end
				if iGold > iNumImmuneGoldYields then
					iGold = iNumImmuneGoldYields + MathRoundingMethod((iGold - iNumImmuneGoldYields) * iBerdYieldModifier)
				end
				if iProduction > iNumImmuneProdYields then
					iProduction = iNumImmuneProdYields + MathRoundingMethod((iProduction - iNumImmuneProdYields) * iBerdYieldModifier)
				end

				-------------------------------------------------------------------------------------------------------------
				--Add the yields to the nearest city and the player treasury (for gold)
				-------------------------------------------------------------------------------------------------------------
				pNearestCity:ChangeFood(iFood)
				PrintDebug(iFood .. " Food was ported from Berd Improvement plot " .. pPlot:GetX() .. "," .. pPlot:GetY() .. " to the city of " ..  pNearestCity:GetName())
				pPlayer:ChangeGold(iGold)
				PrintDebug(iGold .. " Gold was ported from Berd Improvement plot " .. pPlot:GetX() .. "," .. pPlot:GetY() .. " directly to the player treasury")
				pNearestCity:ChangeProduction(iProduction)		
				PrintDebug(iProduction .. " Production was ported from Berd Improvement plot " .. pPlot:GetX() .. "," .. pPlot:GetY() .. " to the city of " ..  pNearestCity:GetName())
				GameplayAlertMessage("Berd at ("..pPlot:GetX()..","..pPlot:GetY().." returns "..iFood.." [ICON_FOOD] Food, "..iProduction.." [ICON_PRODUCTION] Production, and "..iGold.." [ICON_GOLD] Gold to the city of "..pNearestCity:GetName()..".")
			end
		end
	end
	), MustBeOwned=false, CannotBeStolen=true, RequiredCiv=GameInfoTypes.CIVILIZATION_AW_URARTU }

tImprovementsToMonitor[GameInfoTypes.IMPROVEMENT_MINE] = {Handling= (
	function(iPlayer, tPlots, iRequiredCiv, bWeCanStealAPlot, bMustBeOwned)
		local bPlotIsGoodForCurrentPlayer = true
		local pPlayer = Players[iPlayer]
		for Item,pPlot in pairs(tPlots) do
			print("There is a Mine at grid " .. pPlot:GetX() .. "," .. pPlot:GetX())
			print("We ought to do something about that Mine")
		end
	end
	), MustBeOwned=false, CannotBeStolen=true, RequiredCiv="NONE" }

--xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
--xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx< MAKE NO CHANGES BELOW THIS LINE >xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
--xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

local tTableOfWorkerUnits = { [GameInfoTypes.UNIT_WORKER] = "true"}
local tImprovedPlots = {}
local bRequiresSpecifiedCivs = true
local tRequiredSpecifiedCivs = {}
--------------------------------------------------------------
-- GameplayAlertMessage
--------------------------------------------------------------
function GameplayAlertMessage(sMessage)
	if ShowGamePlayMessages then
		local pPlayer = Players[Game.GetActivePlayer()]
		if pPlayer:IsHuman() then
			Events.GameplayAlertMessage(sMessage)
		end
	end
end
--------------------------------------------------------------
-- PrintDebug
--------------------------------------------------------------
function PrintDebug(sMessage)
	if DebugPrintMessages then
		print(sMessage)
	end
end
--------------------------------------------------------------
-- ItemIsInTable
-- Author: LeeS
-- returns true/false as to whether 'DataItem' is a "value" in 'tTable'
-- also returns the Item # (the 'k') for the matching "value"
--------------------------------------------------------------
function ItemIsInTable(tTable,DataItem)
	for iTableItem,TableDataValue in pairs(tTable) do
		if TableDataValue == DataItem then
			return true, iTableItem
		end
	end
	return false
end
--------------------------------------------------------------
-- LRS_GetRandom
-- Author: LeeS
-- modified from JFD's with fix
--------------------------------------------------------------
function LRS_GetRandom(lower, upper)
	local iRandom = Game.Rand((upper + 1) - lower, "") + lower
	return ((iRandom > upper) and upper or iRandom)
end
--------------------------------------------------------------
-- GetNearestCityToPlot
-- Author: LeeS
--------------------------------------------------------------
function GetNearestCityToPlot(pPlot, pPlayer)
	local pNearestCity = nil
	local iNearestDistance = 9999
	local iX, iY = pPlot:GetX(), pPlot:GetY()
	for pCity in pPlayer:Cities() do
		local iDistance = Map.PlotDistance(iX, iY, pCity:GetX(), pCity:GetY())
		if (iDistance < iNearestDistance) then
			iNearestDistance = iDistance
			pNearestCity = pCity
		end
	end
	return pNearestCity, iNearestDistance
end
----------------------------------------------------------------------
-- Pull necessary data for all worker-class units, default and unique
----------------------------------------------------------------------
for iUnit,Boolean in pairs(tTableOfWorkerUnits) do
	local sUnitClass = GameInfo.Units[iUnit].Class
	for UnitRow in GameInfo.Units("Class='" .. sUnitClass .. "'") do
		if not tTableOfWorkerUnits[UnitRow.ID] then
			tTableOfWorkerUnits[UnitRow.ID] = Boolean
		end
	end
end
---------------------------------------------------------------------------------------------------------
-- Build tables tRequiredSpecifiedCivs and tImprovedPlots based on data in table tImprovementsToMonitor
---------------------------------------------------------------------------------------------------------
for ImprovementType,DataTable in pairs(tImprovementsToMonitor) do
	tImprovedPlots[ImprovementType]= {[-1]={}}
	if DataTable.RequiredCiv ~= "NONE" then
		if not tRequiredSpecifiedCivs[DataTable.RequiredCiv] then
			tRequiredSpecifiedCivs[DataTable.RequiredCiv] = GameInfo.Civilizations[DataTable.RequiredCiv].Type
		end
	else
		tRequiredSpecifiedCivs["NONE"] = "NONE"
	end
	for iPlayer = 0, GameDefines.MAX_PLAYERS - 2 do
		local pPlayer = Players[iPlayer]
		if pPlayer:IsEverAlive() then
			tImprovedPlots[ImprovementType][iPlayer]= {}
		end
	end
end
-----------------------------------------------------------------------------------------------------------
-- calculate needed value for bRequiresSpecifiedCivs from data within table tRequiredSpecifiedCivs
-- this is to help determine of the code needs to run at all every turn based on player civs in the game
-----------------------------------------------------------------------------------------------------------
for CivID,CivTypeName in pairs(tRequiredSpecifiedCivs) do
	if CivID == "NONE" then
		bRequiresSpecifiedCivs = false
	end
end
-----------------------------------------------------------------------------------------------------------
-- scan map and record all plots with improvements we are interested in
-----------------------------------------------------------------------------------------------------------
for PlotIndex = 0, (Map.GetNumPlots() - 1), 1 do
	local pPlot = Map.GetPlotByIndex(PlotIndex)
	if (pPlot ~= nil) then
		local iPlotImprovementType, iPlotOwner = pPlot:GetImprovementType(), pPlot:GetOwner()
		if iPlotImprovementType ~= -1 and tImprovementsToMonitor[iPlotImprovementType] then
			PrintDebug("An Improvement from table tImprovementsToMonitor was detected on game reloading or new-game start:")
			PrintDebug("The plot was at " .. pPlot:GetX() .. "," .. pPlot:GetY() .. " and the improvement was " .. iPlotImprovementType .. " (" .. Locale.ConvertTextKey(GameInfo.Improvements[iPlotImprovementType].Description) .. ")")
			if iPlotOwner >= 0 then
 				PrintDebug("The plot belongs to Player # " .. iPlotOwner .. " (" .. Locale.ConvertTextKey(GameInfo.Civilizations[Players[iPlotOwner]:GetCivilizationType()].Description) .. ")")
			else
 				PrintDebug("The plot belongs to Player # " .. iPlotOwner .. " (No One)")
			end
			if not pPlot:IsImprovementPillaged() then
				PrintDebug("The plot is not pillaged and is being added to table tImprovedPlots[" .. iPlotImprovementType .. "][" .. iPlotOwner .. "]")
				table.insert(tImprovedPlots[iPlotImprovementType][iPlotOwner],pPlot)
			end
		end
	end
end
-------------------------------------------------------------------------------------
--scan through player units and find workers on Improvements we are interested in
--this is called from the PlayerDoTurn event
-------------------------------------------------------------------------------------
function AddNewPlotsAfterWorkerFinishes(iPlayer)
	local pPlayer = Players[iPlayer]
	local iPlayerCivID = pPlayer:GetCivilizationType()
	for pUnit in pPlayer:Units() do
		if tTableOfWorkerUnits[pUnit:GetUnitType()] then
			PrintDebug("A Worker Type Unit has been detected")
			local pPlot = pUnit:GetPlot()
			local iImprovementType = pPlot:GetImprovementType()
			if (iImprovementType ~= -1) and not pPlot:IsImprovementPillaged() then
				if tImprovementsToMonitor[iImprovementType] then
					PrintDebug("The Worker Type Unit is standing on an Improvement in the list of table tImprovementsToMonitor")
					local bAddPlotToTable = false
					local iRequiredCivilization = tImprovementsToMonitor[iImprovementType].RequiredCiv
					local bMustBeOwned = tImprovementsToMonitor[iImprovementType].MustBeOwned
					local bCannotBeStolen = tImprovementsToMonitor[iImprovementType].CannotBeStolen
					local iPlotOwner = pPlot:GetOwner()
					if bMustBeOwned and (iPlotOwner ~= -1) then
						PrintDebug("bMustBeOwned was true and (iPlotOwner ~= -1)")
						if (iPlotOwner == iPlayer) then
							PrintDebug("(iPlotOwner == iPlayer)")
							if (iRequiredCivilization ~= "NONE") and (iRequiredCivilization == iPlayerCivID) then
								PrintDebug("(iRequiredCivilization ~= NONE) and (iRequiredCivilization == iPlayerCivID) so the plot will be added to table tImprovedPlots[iImprovementType][iPlayer]")
								bAddPlotToTable = true
							end
						end
					end
					if not bMustBeOwned then
						PrintDebug("bMustBeOwned was false")
						if bCannotBeStolen then
							PrintDebug("bCannotBeStolen was true")
							if (iPlotOwner == iPlayer) or (iPlotOwner == -1) then
								PrintDebug("(iPlotOwner == iPlayer) or (iPlotOwner == -1) so the plot will be added to table tImprovedPlots[iImprovementType][iPlayer]")
								bAddPlotToTable = true
							end
						else
							PrintDebug("bCannotBeStolen was false")
							if (iPlotOwner ~= iPlayer) then
								PrintDebug("(iPlotOwner ~= iPlayer) so the plot will be added to table tImprovedPlots[iImprovementType][iPlayer]")
								bAddPlotToTable = true
							end
						end
					end
					if bAddPlotToTable then
						PrintDebug("The plot ought to be added to table tImprovedPlots[iImprovementType][iPlayer]")
						if not ItemIsInTable(tImprovedPlots[iImprovementType][iPlayer],pPlot) then
							PrintDebug("The plot was determined that it should be added to table tImprovedPlots[iImprovementType][iPlayer]")
							table.insert(tImprovedPlots[iImprovementType][iPlayer],pPlot)
						else
							PrintDebug("The plot was determined to already be contained within table tImprovedPlots[iImprovementType][iPlayer] so it is not necessary to add it to the table")
						end
					else
						PrintDebug("The plot SHOULD NOT to be added to table tImprovedPlots[iImprovementType][iPlayer]")
					end
				end
			end
		end
	end
end
-------------------------------------------------------------------------------------
--player turn processing for Improvements/Plots we are interested in
-------------------------------------------------------------------------------------
function PlayerDoTurnImprovementsProcessing(iPlayer)
	local pPlayer = Players[iPlayer]
	if pPlayer:IsBarbarian() then return end
	AddNewPlotsAfterWorkerFinishes(iPlayer)
	for iImprovementType,DataTable in pairs(tImprovementsToMonitor) do
		if #tImprovedPlots[iImprovementType][iPlayer] > 0 then
			local tPlotsToRemove = {}
			local iLengthOfTableRemoves = 0
			local bWeCanStealAPlot = not DataTable.CannotBeStolen
			local iRequiredCivilization = DataTable.RequiredCiv
			local bMustBeOwned = DataTable.MustBeOwned
			local bProcessImprovementForThisPlayer = true
			if (iRequiredCivilization ~= "NONE") and (pPlayer:GetCivilizationType() ~= iRequiredCivilization) then
				bProcessImprovementForThisPlayer = false
			end
			if bProcessImprovementForThisPlayer then
				DataTable.Handling(iPlayer, tImprovedPlots[iImprovementType][iPlayer], iRequiredCivilization, iImprovementType, bWeCanStealAPlot, bMustBeOwned)
			end
			for Item,pPlot in pairs(tImprovedPlots[iImprovementType][iPlayer]) do
				local bPlotShouldBeRemoved = false
				local iPlotOwner = pPlot:GetOwner()
				if bMustBeOwned and (iPlotOwner ~= iPlayer) then
					if (iPlotOwner ~= nil) and (tImprovedPlots[iImprovementType][iPlotOwner] ~= nil) and (type(tImprovedPlots[iImprovementType][iPlotOwner]) == "table") and not ItemIsInTable(tImprovedPlots[iImprovementType][iPlotOwner],pPlot) then
						table.insert(tImprovedPlots[iImprovementType][iPlotOwner],pPlot)
					end
					bPlotShouldBeRemoved = true
				end
				if (pPlot:GetImprovementType() == -1) or pPlot:IsImprovementPillaged() or (pPlot:GetImprovementType() ~= iImprovementType) then
					bPlotShouldBeRemoved = true
				end
				if bPlotShouldBeRemoved then
					table.insert(tPlotsToRemove, Item)
					iLengthOfTableRemoves = iLengthOfTableRemoves + 1
				end
			end
			if iLengthOfTableRemoves > 0 then
				while iLengthOfTableRemoves > 0 do
					table.remove(tImprovedPlots[iImprovementType][iPlayer], tPlotsToRemove[LengthOfTableRemoves])
					LengthOfTableRemoves = LengthOfTableRemoves - 1
				end
			end
		end
	end
end
------------------------------------------------------------
---- WilliamHoward's IsCivInPlay
------------------------------------------------------------
function IsCivInPlay(iCivType)
  for iSlot = 0, GameDefines.MAX_MAJOR_CIVS-1, 1 do
    local iSlotStatus = PreGame.GetSlotStatus(iSlot)
    if (iSlotStatus == SlotStatus.SS_TAKEN or iSlotStatus == SlotStatus.SS_COMPUTER) then
      if (PreGame.GetCivilization(iSlot) == iCivType) then
        return true
      end
    end
  end
  
  return false
end
------------------------------------------------------------
---- Game Event Hooks & Confirmation Print Statements
------------------------------------------------------------
if bRequiresSpecifiedCivs then
	for CivID,CivTypeName in pairs(tRequiredSpecifiedCivs) do
		if IsCivInPlay(CivID) then
			GameEvents.PlayerDoTurn.Add(PlayerDoTurnImprovementsProcessing)
			print("PlayerDoTurnImprovementsProcessing was added to GameEvents.PlayerDoTurn: bRequiresSpecifiedCivs was evaluated as 'true' and at least one required civ is in play")
		end
	end
else
	GameEvents.PlayerDoTurn.Add(PlayerDoTurnImprovementsProcessing)
	print("PlayerDoTurnImprovementsProcessing was added to GameEvents.PlayerDoTurn: bRequiresSpecifiedCivs was evaluated as 'false'")
end
------------------------------------------------------------
---- Loading Confirmation Print Statements
------------------------------------------------------------

--print("xxxxxxxxxxxxxxxx.lua loaded successfully to the end of the file without code-problems")


print("Berd_Functions.lua loaded to the end")
  1. I made the test file run as its own "InGameUIAddin" as shown in this portion of the modified modinfo file
    Code:
        <File md5="CF537B25DB23B7FF4ABA47803E885219" import="0">Lua/Berd_Functions.lua</File>
        <File md5="01853F722E51D92844894F6B83AC54FB" import="0">Lua/TSLSerializerHookup.lua</File>
        <File md5="6374C30777C686FC681969F62880FE4A" import="1">Lua/UA_Functions.lua</File>
    	............snips................
    	
      </Files>
    	............snips................
      <EntryPoints>
        <EntryPoint type="InGameUIAddin" file="Lua/TSLSerializerHookup.lua">
          <Name>TSLSH</Name>
          <Description>
          </Description>
        </EntryPoint>
        <EntryPoint type="InGameUIAddin" file="UI/AW_Urartu_UA_UI.lua">
          <Name>UI</Name>
          <Description>
          </Description>
        </EntryPoint>
        <EntryPoint type="InGameUIAddin" file="Lua/Berd_Functions.lua">
          <Name>Berd</Name>
          <Description>Berd</Description>
        </EntryPoint>
      </EntryPoints>
    </Mod>
  2. I also changed the end of the "Lua/TSLSerializerHookup.lua" file to read as this:
    Code:
    -->-------> TSL SERIALIZER HOOKUP <-------<--
    -- include all files that need tables saved
    --include('Berd_Functions.lua')
    include('UA_Functions.lua')
    
    
    OnModLoaded()
    This makes the "Lua/Berd_Functions.lua" file completely independant of the TableSaverLoader
  3. This bit is merely placeholder showing where the function for the IMPROVEMENT_AW_URARTU_FORT would need to go to use the same system:
    Code:
    tImprovementsToMonitor[GameInfoTypes.IMPROVEMENT_MINE] = {Handling= (
    	function(iPlayer, tPlots, iRequiredCiv, bWeCanStealAPlot, bMustBeOwned)
    		local bPlotIsGoodForCurrentPlayer = true
    		local pPlayer = Players[iPlayer]
    		for Item,pPlot in pairs(tPlots) do
    			print("There is a Mine at grid " .. pPlot:GetX() .. "," .. pPlot:GetX())
    			print("We ought to do something about that Mine")
    		end
    	end
    	), MustBeOwned=false, CannotBeStolen=true, RequiredCiv="NONE" }
  4. Parameters:
    • Handling
      This is where you define the function that should run every turn to process all plots that have the desired improvement and which belong to player X. There are several parameters passed to these functions:
      • iPlayer
        This will be the Player ID# of the player currently being processed​
      • tPlots
        This will be the table of relevant plots which have the improvement specified.
        The table will be in the form of
        • 'k' = a numerical item integer #
        • 'v' = a plot object in the form of pPlot so that you can directly do methods such as v:GetX()
      • iRequiredCiv
        Will be the same data as was specified for "RequiredCiv". When a specific civ is required, only that civ will ever get execution of what you specify for the "Handling". The code passes along the data for "RequiredCiv" as parameter "iRequiredCiv" to the function written in as the "Handling"​
      • bWeCanStealAPlot
        This translates the data for "CannotBeStolen" into a boolean specifying whether the plot with such an improvement can be stolen away from another player, but currently the code that passes this info doesn't pass the info for plots belonging to other players. You can use this flag as a control for whether a plot that is in the table of plots belonging to Player_X can be taken back away from Player_Y if Player_Y stole the plot through a Citadel Bomb or the like.​
      • bMustBeOwned
        This specifies whether the plot has to belong to the player, and is passed directly from what was specified for "MustBeOwned". How you make use of this data is your own business.​
    • MustBeOwned
      Specified whether the plot must be owned by the player. After any code in the "Handling" runs, any plots that don't belong to the correct player are removed from the table holding the plots with Improvement_X and which are thought to belong to the player. This is to handle issues related to Citadel Bombs and the like.​
    • CannotBeStolen
      Specifies whether plots with this improvement can be stolen from ownership by other players. When false, this will act as a flag that the plot could be 'claimed' from unowned territory.​
    • RequiredCiv
      Specifies the required cvilization that is linked to the improvement. Otherwise state "NONE". When a valid reference to a civilization is given in the form of "GameInfoTypes.CIVILIZATION_SOMETHING", then only the specified civilization uses the code specified for the "Handling" function.​
  5. As a general rule you would want to change the DebugPrintMessages flag at the top of the code to 'false'.
  6. Note to the miscellaneous reader: we aren't using the BuildFinsihed event because it only fires for BNW, and AW has an anti-BNW-only fetish. We are monitoring Workers at the beginning of the player's turn, and if the worker is standing on an improvement we are interested in that has not yet been registered within our lua-tables, then we register the plot into the table(s).
--------------------------------------------------------------------------------------------------------

I used the Berd Improvement and the Urartu Civ as a test of concept. All you have to do is write the 'Handling' function for your new civ/improvement and drop it in where the stuff for the Berd Improvement is in this code. No TableSaverLoader is required. If you only have the one improvement you are interested in for the new civ, then you can eliminate the 'placeholder' example stuff for the Mine that is in the code.
 
:dubious::faint::eek2::wow::wow::wow:

...I was not expecting that you'd completely rewrite the code! :lol:

This is incredible. Very straightforward, easy to plug in, and if it works, it's all good with me! To clarify four things, though:

1) Just because I don't need TSL Serializer, that doesn't mean I can't still use it, right? Specifically, this new UI I'm making is going to borrow Georgia's Eklesia system of tracking an invisible number along with the improvements. (The Eklesiae track turns to determine when to spawn missionaries, for example)

2) The plot-scanning part - including Map.GetPlotByIndex() - is G&K-friendly, right?

3) I'm still wondering if this can be implemented, say, through a LuaEvent, primarily because it seems redundant or otherwise inefficient to copy and paste the code for every mod that needs to scan through specific improvements. Certainly, several scripts all looping over all the plots on the map every turn would rack up overhead lag. I suppose this is a secondary concern, however.

4) And lastly, this integrates the fix for the periodic table-cleaning issue - the problem of removing plots from the table that no longer belong every so often without breaking the iteration prematurely - that plagued the original Berd code?

I know, I know, I'm just like that annoying customer that's always changing user requirements and dashing all sense of fulfillment. (https://xkcd.com/664/) And the next sentence would be a justification except I'm really not sure how to justify it. :p
 
  1. Yes, you can still use TableSaverLoader. But you need the code for that to run entirely on its own and seperately from this code. Do not try to mix-n-match the table-names used. I would make any lua-file using this code its own seperate file and its own seperate "InGameUIAddin" in Modbuddy. It's probably possible to integrate the two sets of code, but thinking about how to do it is making my head want to explode at the moment because you don't want the tables used in this code to be persisted by TableSaverLoader.
  2. Yes, compatible with G&K
  3. You aren't scanning through all the plots on the map every turn. You are doing that only once on game-load or game start, and then after that you are only looking at the plots that have been added to the tables tracking completed improvements for only those types of improvements in which you are interested. And then, only for those players for whom examining the table of plots with Improvement-X is appropriate, and then only those plots owned by that player. The real overhead is in scanning through player units looking for Worker-Class units. And for those worker-class units, all the execution is done for each player with one scan through the list of that player's units. There could be multiple mods all running this code every turn -- but how many of them are going to be looking at the same improvements, and therefore the same list of plots?
  4. Yes, the table-cleaning is done every turn on the table holding the list of plots for Player-X of Improvements-Y. If a plot listed in the table no longer belongs to Player-X, or the plot no longer has the correct improvement, or the improvement has been pillaged, the plot gets removed from the list of plots belonging to Player-X which also have Improvement-Y. The only remaining outstanding issue is that I did not make the code auto-reassign the plot from Player-X to Player-Z when Player-Z grabs away a plot that used to belong to Player-X. That's something that needs a little more thought but only is really relevant when for Improvement-X the setting of RequiredCiv is "NONE". And there probably also needs to be a little more 'think-through' for the cases where a plot with the correct sort of improvement is returned to Player-X or grabbed by Player-X via Citadel-Bomb or the like. I'll have to think that part of the issue through and probably have to add an addiitonal event-hook to handle plots changing hands in the middle of a gameplay session.

The "MustBeOwned" setting is for determining whether to add a plot to the list of plots for player-X when a worker is standing on such a plot at the begining of the turn. The rest of the code requires such a plot to belong to Player-X in order for the plot to remain in the list of plots for Player-X. This is why the 'Handling' function for the improvement executes before the portion of the code that determines whether the plot should remain on the list. This allows the 'Handling' function to grab the plot for Player-X where this is appropriate, or not where not.

Re-reading my own code as I actually wrote it, "MustBeOwned" also determines whether the plot is removed from the player's list of plots for ImprovementType-X. Only when "MustBeOwned" is set to "true" and the plot owner is not the same as Player-X is the plot removed from the table for Player-X/Improvement-X.

I have edited so that if "MustBeOwned" is set to "true" and the plot owner is not the same as Player-X not only is the plot removed from the table for Player-X/Improvement-X, but the plot is shifted to being listed under the table for Player-Y/Improvement-X.

Still looking at a couple of the other issues to decide the easiest method to account for fluctuating plot ownerships.

Next edit edit: I've posted a slightly-altered version of the code over the top of the previous version posted in #375 which should handle during turn processing any of the issues related to shifting plot ownership, though I have as yet not tested this altered version of the code.
 
Unfortunately, I have the new problem that a mod of mine is crashing upon trying to load the mod - that is, before the civ selection screen. My mod is a simple improvement test mod, to test 3D graphics, and as such it's filled to the brim with placeholder 2D art and text, etc., so don't be too confused by that.
Of course, since it's a CTD, I'm not getting much help from the logs; stopwatch.log got this far (with only my mod and IGE enabled):
Spoiler :
Code:
[47984.690] , Discovering Base Game Map Scripts, 0.000905
[47987.108] , CreateCoreDatabase, 2.422686
[47992.443] , 	cvAudioManager::LoadExtraContentScripts, 2.897003
[47992.443] , cvAudioSystem::Startup, 2.897109
[47997.950] , DISCOVERING MODS, 5.339534
[48000.446] , 	Generate BaseGame Loc Database., 2.497310
[48000.462] , 		Get Package Files, 0.013251
[48002.381] , 		Generate Specific DLC Database, 1.921656
[48002.396] , 		Get Package Files, 0.016843
[48006.452] , 		Generate Specific DLC Database, 4.055445
[48006.484] , 		Get Package Files, 0.023130
[48008.995] , 		Generate Specific DLC Database, 2.515359
[48009.026] , 		Get Package Files, 0.021902
[48010.696] , 		Generate Specific DLC Database, 1.666976
[48010.711] , 		Get Package Files, 0.017591
[48012.084] , 		Generate Specific DLC Database, 1.378999
[48012.100] , 		Get Package Files, 0.012083
[48013.535] , 		Generate Specific DLC Database, 1.440495
[48013.566] , 		Get Package Files, 0.023448
[48017.092] , 		Generate Specific DLC Database, 3.534502
[48017.107] , 		Get Package Files, 0.015387
[48018.059] , 		Generate Specific DLC Database, 0.947021
[48018.059] , 		Get Package Files, 0.000005
[48018.199] , 		Get Package Files, 0.135319
[48030.289] , 		Generate Specific DLC Database, 12.087847
[48030.430] , 		Get Package Files, 0.146021
[48042.286] , 		Generate Specific DLC Database, 11.855388
[48042.301] , 		Get Package Files, 0.013152
[48042.910] , 		Generate Specific DLC Database, 0.608106
[48042.910] , 		Get Package Files, 0.000006
[48042.910] , 	Generate DLC Loc Databases., 42.455993
[48049.056] , 	CvLocalizationDatabaseFactory::GenerateMergedLocalizationDatabase(), 6.149125
[48049.056] , New Localization Code, 51.105214
[48051.115] , 	BeforeDeactivateMods - Release Database Cache, 0.000231
[48051.115] , 	BeforeDeactivateMods - Unload DLL, 0.000001
[48051.256] , 	AfterDeactivateMods - Reset filesystem, 0.000001
[48052.519] , 		LoadCoreDatabase - Detach Loc Database, 0.024326
[48052.535] , 		LoadCoreDatabase - Load Database, 0.014260
[48058.604] , 			CvLocalizationDatabaseFactory::GenerateMergedLocalizationDatabase(), 6.061007
[48059.711] , 		LoadCoreDatabase - Attach Loc Database, 7.175794
[48059.711] , 	AfterDeactivateMods - Rollback Database, 8.455005
[48061.412] , 		Load DLC into Game Database, 1.693234
[48061.412] , 	AfterDeactivateMods - Load DLC, 1.693308
[48061.412] , 		Discovering Base Game Map Scripts, 0.001542
[48062.675] , 		Parse Map Scripts, 1.274895
[48062.675] , 	Discover and Parse Base Game Map Scripts, 1.276559
[48062.675] , 		SetActiveDLCandMods - Import Mod Files into filesystem, 0.000534
[48062.675] , 		SetActiveDLCandMods - Perform OnModActivated Actions, 0.000314
[48062.675] , 	SetActiveDLCandMods - Activate Mods, 0.000893
[48063.798] , 	SetActiveDLCandMods - Notify Localization Database Updated, 1.114352
[48063.908] , Ensure All Tables with 'ID' column start at 0, 0.013532
[48066.965] , Validate FK Constraints, 3.064665
[48066.981] , Localization Checks, 0.013748
[48066.981] , Validating UnitGameplay2DScripts, 0.000189
[48067.558] , 	SetActiveDLCandMods - Reload GameCore DLL, 3.764864
[48067.589] , 	SetActiveDLCandMods - Refresh include() file list, 0.020424
[48067.589] , 	SetActiveDLCandMods - Refresh Gameplay DLL Data, 0.000000
[48067.589] , 	SetActiveDLCandMods - Perform Gameplay Database Post Processing, 0.001818
[48067.589] , Ensure All Tables with 'ID' column start at 0, 0.008940
[48070.647] , Validate FK Constraints, 3.055920
[48070.662] , Localization Checks, 0.014230
[48070.662] , Validating UnitGameplay2DScripts, 0.000212
[48071.240] , 	SetActiveDLCandMods - Cache Game Database Data, 3.652148
[48071.240] , 	SetActiveDLCandMods - Rescan Localized Audio, 0.000238
[48072.300] , SetActiveDLCandMods, 21.193396
[48085.233] , LoadUnitLibraries via Database, 12.575058
[48106.917] , Discovering Base Game Maps, 0.000770
[48107.432] , (Lua) Modding.CanEnableMod, 0.002487
[48107.447] , (Lua) Modding.CanEnableMod, 0.002174
[48107.494] , Discovering Base Game Maps, 0.000716
[48107.572] , Discovering Base Game Maps, 0.000763
[48120.302] , (Lua) Modding.CanEnableMod, 0.040874
[48121.004] , (Lua) Modding.CanEnableMod, 0.038326
[48122.486] , (Lua) Modding.CanEnableMod, 0.046218
[48126.932] , (Lua) Modding.CanEnableMod, 0.042776
[48128.976] , (Lua) Modding.CanEnableMod, 0.056309
[48132.501] , Rediscover Mods, 0.716061
[48132.564] , (Lua) Modding.CanEnableMod, 0.041270
[48135.668] , (Lua) Modding.CanEnableMod, 0.039254
[48151.268] , (Lua) Modding.CanEnableMod, 0.041472
[48153.109] , Rediscover Mods, 1.829760
[48153.171] , (Lua) Modding.CanEnableMod, 0.045067
[48154.809] , (Lua) Modding.CanEnableMod, 0.040714
[48158.959] , (Lua) Modding.CanEnableMod, 0.331108
[48160.535] , (Lua) Modding.CanEnableMod, 0.080352
[48162.547] , Rediscover Mods, 2.005105
[48162.641] , (Lua) Modding.CanEnableMod, 0.079880
[48164.372] , (Lua) Modding.CanEnableMod, 0.081507
[48165.059] , (Lua) Modding.CanEnableMod, 0.085653
[48165.995] , Rediscover Mods, 0.929633
[48166.088] , (Lua) Modding.CanEnableMod, 0.079395
[48166.681] , (Lua) Modding.CanEnableMod, 0.078119
[48167.820] , (Lua) Modding.CanEnableMod, 0.081547
[48168.662] , Rediscover Mods, 0.821295
[48168.756] , (Lua) Modding.CanEnableMod, 0.083506
[48169.427] , (Lua) Modding.CanEnableMod, 0.091004
[48170.597] , (Lua) Modding.CanEnableMod, 0.081738
[48171.408] , Rediscover Mods, 0.788731
[48171.501] , (Lua) Modding.CanEnableMod, 0.079680
[48172.001] , (Lua) Modding.CanEnableMod, 0.077446
[48173.327] , (Lua) Modding.CanEnableMod, 0.081817
[48174.075] , Rediscover Mods, 0.720212
[48174.169] , (Lua) Modding.CanEnableMod, 0.088705
[48175.932] , Rediscover Mods, 0.593320
[48176.025] , (Lua) Modding.CanEnableMod, 0.082533
[48177.133] , (Lua) Modding.CanEnableMod, 0.078751
[48180.050] , (Lua) Modding.CanEnableMod, 0.086277
[48181.189] , Rediscover Mods, 1.058567
[48181.298] , (Lua) Modding.CanEnableMod, 0.083441
[48183.326] , (Lua) Modding.CanEnableMod, 0.081131
[48188.818] , (Lua) Modding.CanEnableMod, 0.077439
[48190.393] , Rediscover Mods, 1.575109
[48190.502] , (Lua) Modding.CanEnableMod, 0.086166
[48192.889] , (Lua) Modding.CanEnableMod, 0.081091
[48200.892] , (Lua) Modding.CanEnableMod, 0.121035
[48201.407] , 		ActivateModsAndDLCForEnabledMods - Get Enabled Mods, 0.000102
[48201.703] , 				SetActiveDLCandMods - Import Mod Files into filesystem, 0.026969
[48202.000] , 					Update Database - Localization/IGE_EN_US.xml, 0.305109
[48202.015] , 					Update Database - Localization/IGE_ZH_CN.xml, 0.016619
[48202.405] , 					Update Database - Localization/IGE_FR_FR.xml, 0.384020
[48202.608] , 					Update Database - Localization/IGE_DE_DE.xml, 0.198956
[48202.826] , 					Update Database - Localization/IGE_ES_ES.xml, 0.225479
[48203.092] , 					Update Database - Localization/IGE_IT_IT.xml, 0.257260
[48204.464] , 					Update Database - Localization/IGE_RU_RU.xml, 1.381061
[48204.761] , 					Update Database - Localization/IGE_JA_JP.xml, 0.298584
[48204.776] , 					Update Database - ArtDefines.xml, 0.000625
[48204.776] , 					Update Database - CIV5Improvements_Expansion.xml, 0.001052
[48204.776] , 				SetActiveDLCandMods - Perform OnModActivated Actions, 3.070289
[48204.776] , 			SetActiveDLCandMods - Activate Mods, 3.289242
[48205.868] , 			SetActiveDLCandMods - Notify Localization Database Updated, 1.103822
[48205.868] , 			SetActiveDLCandMods - Reload GameCore DLL, 0.000834
[48205.900] , 			SetActiveDLCandMods - Refresh include() file list, 0.024841
[48205.900] , 			SetActiveDLCandMods - Refresh Gameplay DLL Data, 0.000000
[48205.900] , 			SetActiveDLCandMods - Perform Gameplay Database Post Processing, 0.001818
[48205.915] , Ensure All Tables with 'ID' column start at 0, 0.009868
[48209.035] , Validate FK Constraints, 3.126698
[48209.051] , Localization Checks, 0.013891
[48209.051] , Validating UnitGameplay2DScripts, 0.000195
[48209.628] , 			SetActiveDLCandMods - Cache Game Database Data, 3.719600
[48209.628] , 			SetActiveDLCandMods - Rescan Localized Audio, 0.000249
[48209.628] , 				Discovering Modder Map Scripts, 0.001019
[48209.628] , 				Parse Map Scripts, 0.003589
[48209.628] , 			SetActiveDLCandMods - Parse Map Scripts, 0.004690
And the crash dump was useful as any other - that is to day, it wasn't - but it did seem to reveal that 1) the problem was with DX11, and 2) had something to do with a memory access violation or something.

The mod is to test the graphics of an "Artesian Well" improvement, converted from a civ4 building called "qumran".

So props to whoever has any idea what may be causing the crash
Download here

TIA,
AW
 
Guess who's back...

I basically just have two problems this time around:

1) The more critical problem is one with finding an event hook. I need one that will fire at the end of a player's turn, or at least before GameEvents.PlayerDoTurn. I've tried Events.ActivePlayerTurnEnd, but it's not helpful in my case since it only fires for the human player. Let me put it this way - you know how ships on land or land units on ocean will be magically teleported to the nearest plot where they belong? I need to get a global variable set before that occurs, but unfortunately that takes place before PlayerDoTurn. And ActivePlayerTurnEnd doesn't work because since it doesn't fire for the AI, the global variable doesn't get correctly set for the human's turn.

2) Less critically, my code is really, really, ridiculously slow, mostly due to this one function that, of necessity, has to iterate through two three-level-deep nested tables. It produces about 5 straight seconds of complete lag in which the game essentially freezes. So, I'm open to ideas on how to optimize that function.

Can attach the mod as needed.

TIA,
AW

EDIT: Nevermind, I managed to make the first one work by having each player create the functionality for the previous player using PlayerDoTurn. But optimization is still a goal.
 
Last edited:
New puzzle for anyone who's paying attention:

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

TL;DR version: Can we, in Lua, make a replica of the method Unit.JumpToNearestValidPlot() with the extra string attached of requiring the plot to be owned by a specific player?

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

Non-TL;DR version:

You know the method Unit.JumpToNearestValidPlot() ?

What I need is essentially that method, but with one additional caveat: that during the plot-determining process, it needs to be able to consider the owner of the plot.

What I want to do is this:

Cause a friendly unit to jump to the nearest valid plot that is not within friendly territory

Now what happens when you try to call JumpToNearestValidPlot() on the unit if it's already within friendly territory? - I mean, strictly speaking, it works, but it doesn't accomplish anything; the (X,Y) coordinates of the unit haven't changed, since the nearest valid plot it can jump to is the plot it's already standing on.

What I need is to add another parameter to JumpToNearestValidPlot() that will allow me to account for this. I don't particularly care what form or data type that parameter comes in, whether it's a player ID (int) whose plots the algorithm should specifically seek out, or a table of player objects representing players whose plots should be avoided like the plague, I don't care. I just need it to somehow consider the player at hand.

Since editing a method would require DLL modding, I figure I may as well just invent the wheel. This is the C++ source for Unit.JumpToNearestValidPlot(), from CvUnit.cpp:
Spoiler :
Code:
bool CvUnit::jumpToNearestValidPlot()
{
    VALIDATE_OBJECT
    CvCity* pNearestCity;
    CvPlot* pLoopPlot;
    CvPlot* pBestPlot;
    int iValue;
    int iBestValue;
    int iI;

    CvAssertMsg(!isAttacking(), "isAttacking did not return false as expected");
    CvAssertMsg(!isFighting(), "isFighting did not return false as expected");

    pNearestCity = GC.getMap().findCity(getX(), getY(), getOwner());

    iBestValue = INT_MAX;
    pBestPlot = NULL;

    for(iI = 0; iI < GC.getMap().numPlots(); iI++)
    {
        pLoopPlot = GC.getMap().plotByIndexUnchecked(iI);

        if(pLoopPlot && pLoopPlot->isValidDomainForLocation(*this))
        {
            if(canMoveInto(*pLoopPlot))
            {
                if(pLoopPlot->getNumFriendlyUnitsOfType(this) < GC.getPLOT_UNIT_LIMIT())
                {
                    // Can only jump to a plot if we can enter the territory, and it's NOT enemy territory OR we're a barb
                    if(canEnterTerritory(pLoopPlot->getTeam()) && (isBarbarian() || !isEnemy(pLoopPlot->getTeam(), pLoopPlot)) && !pLoopPlot->isMountain())
                    {
                        CvAssertMsg(!atPlot(*pLoopPlot), "atPlot(pLoopPlot) did not return false as expected");

                        if((getDomainType() != DOMAIN_AIR) || pLoopPlot->isFriendlyCity(*this, true))
                        {
                            if(getDomainType() != DOMAIN_SEA || (pLoopPlot->isFriendlyCity(*this, true) && pLoopPlot->isCoastalLand()) || pLoopPlot->isWater())
                            {
                                if(pLoopPlot->isRevealed(getTeam()))
                                {
                                    iValue = (plotDistance(getX(), getY(), pLoopPlot->getX(), pLoopPlot->getY()) * 2);

                                    if(pNearestCity != NULL)
                                    {
                                        iValue += plotDistance(pLoopPlot->getX(), pLoopPlot->getY(), pNearestCity->getX(), pNearestCity->getY());
                                    }

                                    if(pLoopPlot->area() != area())
                                    {
                                        iValue *= 3;
                                    }

                                    if(iValue < iBestValue)
                                    {
                                        iBestValue = iValue;
                                        pBestPlot = pLoopPlot;
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    if(GC.getLogging() && GC.getAILogging())
    {
        CvString strLogString;
        if(pBestPlot != NULL)
        {
            strLogString.Format("Jump to nearest valid plot by %s , X: %d, Y: %d, From X: %d, From Y: %d", getName().GetCString(),
                               pBestPlot->getX(), pBestPlot->getY(), getX(), getY());
            GET_PLAYER(m_eOwner).GetHomelandAI()->LogHomelandMessage(strLogString);
        }
        else
        {
            strLogString.Format("Can't find a valid plot within range. %s deleted, X: %d, Y: %d", getName().GetCString(), getX(), getY());
            GET_PLAYER(m_eOwner).GetHomelandAI()->LogHomelandMessage(strLogString);
        }
    }

    if(pBestPlot != NULL)
    {
        setXY(pBestPlot->getX(), pBestPlot->getY());
        ClearMissionQueue();
        SetActivityType(ACTIVITY_AWAKE);
    }
    else
    {
        return false;
    }

    return true;
}

Now, there is a check in there for whether the "valid plot" in question is owned by an opposing team, but it doesn't seem to make any player distinction.

My question is: is this something that could replicated (with my one extra string attached) in Lua with the Lua methods available to us, or does it rely on processes reserved for the DLL?

TIA,
AW
 
Top Bottom