Improvement Owner not updating with Plot Owner (LUA)

TC_

Chieftain
Joined
Jul 8, 2017
Messages
77
Hi guys,

I'm working on a fort mod similiar to Geekob's Enhanced Forts but with some different functionality. Upon pillaging an opposing player's fort, I am trying to give control of that fort and the appropriate surrounding tiles to the player who pillaged it. I am doing this by giving ownership of the fort PLOT to the player, not the improvement. (plot:SetOwner(player))

Using this method, the player who pillaged it and took control of the plot is still able to repair the fort. My problem arises when an event such as ImprovementRemovedFromMap() or ImprovementChanged() are called. Both of these functions use the parameter improvementOwner, and that is always set as the player who originally placed the fort.

Does anyone know how to change ownership of the improvement itself? Is it a problem with how I am setting the plot ownership? Does this miss an event hook that should be changing the improvement owner?

Thanks in advance
 
Or even if there is a way to remove an improvement via LUA that would be a great help
 
(plot:SetOwner(player)) is buggy. In many cases even if one of your cities can later make use of a plot so "grabbed" the plot will be unusable as a result of claiming it for the player via the Plot:SetOwner() method. A more reliable method is to use
Code:
WorldBuilder.CityManager():SetPlotOwner(pPlot, pCity);
But in order to use this method you need a city object to which to assign the plot. If the plot is outside the 5-tile range of the nearest city I am not sure this method will work either.

To cure the issue with the Improvement ownership try as this:
Code:
local iFortImprovementType = GameInfo.Improvements["IMPROVEMENT_FORT"].Index
ImprovementBuilder.SetImprovementType(pPlot, -1)
WorldBuilder.CityManager():SetPlotOwner(pPlot, pCity);
ImprovementBuilder.SetImprovementType(pPlot, iFortImprovementType)
 
  • Like
Reactions: TC_
Thanks for the response. I will use worldbuilding for setting plot ownership from now on.

I'll try out the improvementbuilder stuff out for my main issue. Where did you find that? Ive not seen that anywhere before
 
It's in the worldbuilder lua file as I recall. But I'm not sure anymore. Back when the game was first released I did a lot of trolling through lua base files looking at how Firaxis was doing stuff. And also there's Gedemon's lua objects reference in References and Tutorials.
 
  • Like
Reactions: TC_
Tyvm LeeS, I'll look around at the improvementbuilder stuff. I didn't see it in worldbuilder.lua, but I'll find it somewhere.

I have another question if you don't mind. Do you know if it's possible to give a player ownership of a plot without it being owned by a specific city it to a city?

I tried:

WorldBuilder.CityManager():SetPlotOwner(plot, owner, -1);

and

WorldBuilder.CityManager():SetPlotOwner(plot, owner, false);

but as expected, it doesn't work. (-1) causes it to not claim the tile at all, and false sets the tile as owned by the capital.

The reason I'm hoping to do this is so that the forts that and all of their plots aren't given to a player upon capturing of the city that owns the plots around the fort. Have any ideas?

Thanks again
 
I've never tried to assign a plot to a civ without also assigning it to a city. Too many potential issues otherwise.

ImprovementBuilder.SetImprovementType(PlotObject, iImprovementIndex) is used in nearly all the map generation files to place tribal villages (goody huts) because these are improvements.

But your other issues may depend entirely on what sort of data you are passing as variable "owner" for the WorldBuilder.CityManager():SetPlotOwner
 
  • Like
Reactions: TC_
The reason I'm hoping to do this is so that the forts that and all of their plots aren't given to a player upon capturing of the city that owns the plots around the fort. Have any ideas?

OK after spending more time trying to solve this by giving plot ownership without attaching it to a city, I've decided it can't be done and to try another method to accomplish that goal. However....

NEXT PROBLEMS:

1) Is there really no way to reference a city that was just conquered? GameEvents.CityConquered uses parameters (UnitOwner, unitID). I can't even use that info to figure out the plot where the unit is! Unless unitID is a unique identifier and not just a reference to something like swordsman or archer?

2) I can't find a way to reference which city owns a plot, or what plots are owned by a city. The most straight forward way would be something like City:GetPlots() or Plot:GetCity(), but I'm not seeing anything like either of those. Not even City:GetImprovements()

Any more ideas?
 
Might have solved it. I had to keep a local table of all forts in the game. In addition to that, I had to keep another table of forts that were captured. In order to do that, I had to funnel my code through 4 events: CityConquered, ImprovementRemovedFromMap, CityInitialized, and ImprovementAddedToMap. The call order was spaced out in a way where I can catch all of the captured forts as such:

CityConquered:
set cityConquered = true;
store player who conquered;
initialize table of conquered forts

ImprovementRemovedFromMap:
store the cities original owner through the eOwner parameter here;
store the improvement(fort) in the conquered forts table for each of these events while cityConquered = true;

CityInitialized:
set cityConquered = false;


Apparently when a city is conquered it removes all improvements and replaces them with new ones. So I can then use:

ImprovementAddedToMap:
for each of these events, I call my functions to change owner of the forts back to the original owner if the forts are outside of city range

Needs more testing, but initially it is working.

One more question: Is modding this damn game worth losing the majority of my sleep hours?
 
From my testing file to print the types and values of arguments passed by game hooks:
Code:
function GetArgumentDatas(sOrigin, tTable)
	print("============================================================================================")
	print("[" .. sOrigin .. "]: Dumping Event Hook Argument Data")
	print("............................................................................................")
	for k,v in pairs(tTable) do
		local sKeyType = type(k)
		local sValueType = type(v)
		print("[" .. sOrigin .. "]: Key is of type " .. sKeyType .. " = " .. tostring(k) .. ", Value is of type " .. sValueType .. " = " .. tostring(v))
	end
	print("............................................................................................")
	print("[" .. sOrigin .. "]: dump completed for this firing of the event")
	print("============================================================================================")
end

--############################################### GameEvents.CityConquered ########################################################

function OnCityConquered(...)
	--OnCityConquered(iVictoriousPlayer, iDefeatedPlayer, iNewCityID, iCityPlotX, iCityPlotY)
	--iNewCityID gives the ID # the city is using for the new owner (ie, iVictoriousPlayer)
	--iNewCityID will NOT as a general rule match to the ID # used for the same city for the previous owner (ie, iDefeatedPlayer)
	--CityConquered fires before CityRemovedFromMap, CityAddedToMap, and CityInitialized
	--Firing Order: (all functions subscribed to these events fire in this order as part of a city conquest)
	--	GameEvents.CityConquered
	--	Events.CityRemovedFromMap
	--	Events.CityAddedToMap
	--	Events.CityPopulationChanged
	--	Events.CityTileOwnershipChanged (fires in succession for each tile whose ownership was altered)
	--	Events.CityInitialized
	--	Events.CityProductionCompleted
	--	Events.CityPopulationChanged	(you seem to get two firings of this event as part of a city-capture)
	--	Events.CityProductionCompleted	(you seem to get two firings of this event as part of a city-capture)
	print("GameEvents.CityConquered fired for function OnCityConquered")
	GetArgumentDatas("OnCityConquered", {...})
end
GameEvents.CityConquered.Add(OnCityConquered)
See the notes for the city conquered event. Gedemon's spreadsheet hasn't been updated in quite a while for the arguments that various game hooks pass to subscribed listener events.
 
  • Like
Reactions: TC_
Code:
--%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
--	GRAB TABLE OF ALL PLOTS OWNED AND WORKABLE BY A SPECIFIED CITY
--%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
--GlobalParameters.CITY_MAX_BUY_PLOT_RANGE is not used because the game ignores the setting as per civ5
function GetCityPlots(pCity)
	local iCityRadius = 3
	local tTempTable = {}
	if pCity ~= nil then
		local iCityOwner = pCity:GetOwner()
		local iCityX, iCityY = pCity:GetX(), pCity:GetY()
		for dx = (iCityRadius * -1), iCityRadius do
			for dy = (iCityRadius * -1), iCityRadius do
				local pPlotNearCity = Map.GetPlotXYWithRangeCheck(iCityX, iCityY, dx, dy, iCityRadius);
				if pPlotNearCity and (pPlotNearCity:GetOwner() == iCityOwner) and (pCity == Cities.GetPlotPurchaseCity(pPlotNearCity:GetIndex())) then
					table.insert(tTempTable, pPlotNearCity)
				end
			end
		end
	end
	return tTempTable
end
You can then iterate through the returned table of plots like as
Code:
for i,pPlot in pairs(GetCityPlots(pCity)) do
	if (pPlot:GetImprovementType() == GameInfo.Improvements["IMPROVEMENT_PASTURE"].Index) then
		print("Moo")
	end
end
To get a city object from a City ID, see where I am creating the local variable for the city object from the two passed arguments here:
Code:
function CityFoundedGrabSomePlots(iPlayer, iCityID)
	if (PlayerConfigurations[iPlayer]:GetCivilizationTypeName() == "CIVILIZATION_ROME") then
		print("CityFoundedGrabSomePlots for CIVILIZATION_ROME")
		local pCity = Players[iPlayer]:GetCities():FindID(iCityID)
		GrabSomeAdjacentPlots(pCity, 2, 2)
		GrabSomeAdjacentPlots(pCity, 2, 3)
	end
end
Function "GrabSomeAdjacentPlots" is one that randomly grabs unowned plots adjacent to those already owned by the city, and assigns those plots to the city.
 
  • Like
Reactions: TC_
You've really gone above and beyond here LeeS, thank you. I can streamline my code a great deal using the arguments you reported for GameEvents.CityConquered. Also it would appear the hook I need to be using for finding forts in this city would be Events.CityTileOwnershipChanged. I won't be able to use your function GetCityPlots(pCity), as the tiles I am interested in are outside of the workable 3 plot range. A fort might be 10, 20, or more plots away from the city, but still belonging to that city. However, if I can use plot:GetImprovementType() in each firing of Events.CityTileOwnershipChanged, that should handle my needs completely.

Could you explain to me how to run GetArgumentDatas(sOrigin, tTable) for the events I am interested in? I now need to arguments for CityTileOwnershipChanged.

Thanks again
 
in this line
Code:
GetArgumentDatas("OnCityConquered", {...})
I am sending the text sting "OnCityConquered" and the entire table of arguments passed from the game engine on to function GetArgumentDatas(). All this GetArgumentDatas() function does is prints the data that is sent for each argument from a firing of "OnCityConquered" and tells you whether that data is of form Boolean, Integer, String, or Table.

To make the same GetArgumentDatas() function print the datas for some other event type, you simply do as like here:
Code:
function OnCityTileOwnershipChanged(...)
	--OnCityTileOwnershipChanged(iPlayer, iCityID)
	print("Events.CityTileOwnershipChanged fired for function OnCityTileOwnershipChanged")
	GetArgumentDatas("OnCityTileOwnershipChanged", {...})
end
Events.CityTileOwnershipChanged.Add(OnCityTileOwnershipChanged)
When I ran that function I only saw two arguments being passed, the Player ID# and the City ID#. Hence this commented line on what the event appears to pass to any functions subscribed to it
Code:
--OnCityTileOwnershipChanged(iPlayer, iCityID)

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

What we have to do is look at the results that get printed from one of the game's hook events firing, and try to guess-work what the argument data represent.

Knowing whether the 1st argument is of type Integer rather than of type Boolean is useful because we can then start to compare that data against what occured to make the game engine fire off the event.

If I know player # 0 (the human player in single-player games) just did something, and I see an argument containing an Integer value of "0" I know there is a fair chance this argument might be giving the Player Id#. Then it is merely necessary to record some of the info we get from the event firing and try to force the thing to fire from some other player doing the same sort of action.

Firaxis doesn't provide a manual of what everything is so we as mod-makers have to figure it out.
 
Last edited:
  • Like
Reactions: TC_
Well sadly those arguments make that hook useless to this case, but thank you for the great explanation. I will use the info you provided in determining arguments for other hooks that I'll need in the future.
 
Back
Top Bottom