Initiative: Common Lua Events

Probably just an oversight, but as I explained before, (given the reason to have separate gained/lost and created/destroyed), gained/destroyed want to get pPlayer rather than pCity.

Not really. I could see passing both, but if you know the city you can always easily get the player. The reason why I think the city needs to be passed is because it's likely you want to have this information for overview screens and such (if your cities consume resources, you'll want a screen telling the player how many each city needs)

I'm just considering when I should start pushing it out with my mods. Feels a bit too alpha at the moment.

What's... include("lib")?

I can't find that file anywhere.

Sorry, forgot to remove that again. lib is my own library file which has a few small functions like logging tables and a class mechanism. I used it for logging one of the tables.

I wouldn't release it right now, really. I haven't had the time to really playtest these events and while I think I captured all cases, you know that usually some stuff comes up during testing that you didn't anticipate.
 
I actually think BuildingGained could just as easily be a subset of BuildingCreated instead. Depends on how you think of it. Does the player gain a building that's applied to a city, or does a city gain a building that's applied to a player? If we need them both, I say leave them separate and let the common script decide.
I see them as conceptually separate; however, some building may need to have effects on both triggers, as appropriate.
 
Not really. I could see passing both, but if you know the city you can always easily get the player. The reason why I think the city needs to be passed is because it's likely you want to have this information for overview screens and such (if your cities consume resources, you'll want a screen telling the player how many each city needs)
Well, the use-case is producing resources (as consuming already works), but it doesn't work like that in the example case of a city being taken. By the time you're checking them each turn, the city belongs to the new owner and the old owner isn't traceable with the city object; the loop can tell who the old owner is from its state (finding buildings owned last turn that aren't owned this turn, for instance).
 
Here is some incomplete but possibly applicable code written by lemmy101 some time ago. In particular, there is a BuildingCreated and BuildingAcquired, and both the player and city are given along with the building id.

Spoiler :

ISEventHandler.lua
Code:
function GetPlayer ()
	local iPlayerID = Game.GetActivePlayer();
	if (iPlayerID < 0) then
		print("Error - player index not correct");
		return nil;
	end
	return Players[iPlayerID];
end

function isValidPlayer(pPlayer)
	return pPlayer ~= nil and pPlayer:IsAlive();
end

function CheckSetup()

   if PlayerData == nil then
		PlayerData = {};
		for iPlayerLoop = 0, GameDefines.MAX_MAJOR_CIVS-1, 1 do
			local pPlayer = Players[ iPlayerLoop ];		
			if isValidPlayer(pPlayer) then
				PlayerData[iPlayerLoop] = {};
				PlayerData[iPlayerLoop].buildingData = {};
				SetupBuildingData(iPlayerLoop);
			end
		end		
	end
end


function SetupCityData(iPlayerLoop, iCityLoop)
	print ("Setting initial city data for ISEventSystem");

	local pPlayer = Players[iPlayerLoop];
	local pCity = pPlayer:GetCityByID(iCityLoop);

	PlayerData[iPlayerLoop].citydata[iCityLoop] = {};
	PlayerData[iPlayerLoop].citydata[iCityLoop].buildingdata = {};
	PlayerData[iPlayerLoop].citydata[iCityLoop].population = pCity:GetPopulation();
	for b in GameInfo.Buildings() do
		PlayerData[iPlayerLoop].citydata[iCityLoop].buildingdata[b.ID] = pCity:IsHasBuilding(b.ID);
		if PlayerData[iPlayerLoop].citydata[iCityLoop].buildingdata[b.ID]==true then
			LuaEvents:ISBuildingCreated(pCity, b);
		end
	end

end

PlayerData = nil;
function SetupBuildingData(iPlayerLoop)

	print ("Setting initial building data for ISEventSystem");
	PlayerData[iPlayerLoop].citydata = {};
	PlayerData[iPlayerLoop].buildingHas= {};
	local pPlayer = Players[iPlayerLoop];
	for building in GameInfo.Buildings() do
		PlayerData[iPlayerLoop].buildingHas[building.ID] = pPlayer:CountNumBuildings(building.ID);
		
	end

	for pCity in pPlayer:Cities() do
		if PlayerData[iPlayerLoop].citydata == nil then
			PlayerData[iPlayerLoop].citydata = {};
		end

		if PlayerData[iPlayerLoop].citydata[pCity:GetID()] == nil then
			SetupCityData(iPlayerLoop, pCity:GetID());
		end
	end


end

--**************************************************************************
-- Internal handling of city being destroyed. Raises ISCityDestroyed event.
--**************************************************************************
function OnCityInternalDestroyed(hexPos, playerID, cityID, newPlayerID)

	local pPlayer = Players[playerID];
	local pOtherPlayer = Players[newPlayerID];
	for i, j in pairs(PlayerData[playerID].citydata[cityID].buildingdata) do
		if j==true then
			LuaEvents:ISBuildingLost(pPlayer, pPlayer:GetCityByID(cityID), b);			
			PlayerData[playerID].citydata[cityID].buildingdata[i] = false;
		end
	end

	LuaEvents.ISCityDestroyed(hexPos, pPlayer, pOtherPlayer, pPlayer:GetCityByID(cityID));


end
Events.SerialEventCityDestroyed.Add( OnCityInternalDestroyed );

--**************************************************************************
-- Internal handling of city being captured. Raises ISCityCaptured event.
--**************************************************************************
function OnCityInternalCaptured (hexPos, playerID, cityID, newPlayerID)

	local pPlayer = Players[playerID];
	local pOtherPlayer = Players[newPlayerID];
	
	LuaEvents.ISCityCaptured(hexPos, pPlayer, pOtherPlayer, pPlayer:GetCityByID(cityID));

	for i, j in pairs(PlayerData[playerID].citydata[cityID].buildingdata) do
		if j==true then
			LuaEvents:ISBuildingLost(pPlayer, pPlayer:GetCityByID(cityID), b);			
			LuaEvents:ISBuildingAcquired(pOtherPlayer, pOtherPlayer:GetCityByID(cityID), b);			
			PlayerData[playerID].citydata[cityID].buildingdata[i] = false;
		end
	end


end
Events.SerialEventCityCaptured.Add( OnCityInternalCaptured );

--**************************************************************************
-- Internal handling of city being created. Raises ISCityCaptured event.
--**************************************************************************
function OnInternalCityCreated ( hexPos, playerID, cityID, cultureType, eraType, culture, population, size, fowState )
	
	-- make the first city a pop 2 city
	local pPlayer = Players[playerID];
	
	LuaEvents.ISCityCreated(hexPos, pPlayer, pPlayer:GetCityByID(cityID));
end
Events.SerialEventCityCreated.Add( OnInternalCityCreated );

--**************************************************************************
-- Updates the building data compiled to determine when buildings are 
-- built / destroyed.
--**************************************************************************
function UpdatePlayerData(iPlayerLoop)
	
	local pPlayer = Players[iPlayerLoop];
	

	for pCity in pPlayer:Cities() do

		if PlayerData[iPlayerLoop].citydata == nil then 
				PlayerData[iPlayerLoop].citydata = {};
		end
		if PlayerData[iPlayerLoop].citydata[pCity:GetID()] == nil then
			SetupCityData(iPlayerLoop, pCity:GetID());
		end

		if PlayerData[iPlayerLoop].citydata[pCity:GetID()].population ~= pCity:GetPopulation() then
		
			LuaEvents.ISCityPopulationChanged(pPlayer, pCity, pCity:GetPopulation(), PlayerData[iPlayerLoop].citydata[pCity:GetID()].population );		
			
			PlayerData[iPlayerLoop].citydata[pCity:GetID()].population = pCity:GetPopulation();
		end
			
		for b in GameInfo.Buildings() do
			if b ~= nil then
				local newNum = pPlayer:CountNumBuildings(b.ID);
		
			

				if b.ID == nil then
				else
					local had = PlayerData[iPlayerLoop].citydata[pCity:GetID()].buildingdata[b.ID];
					local got = pCity:IsHasBuilding(b.ID);
			
					if had ~= got then

						if got then
							LuaEvents:ISBuildingAcquired(pPlayer, pCity, b);
						else
							LuaEvents:ISBuildingLost(pPlayer,pCity, b);
						end

					
					end

					PlayerData[iPlayerLoop].citydata[pCity:GetID()].buildingdata[b.ID] = pCity:IsHasBuilding(b.ID);
				

				end
				
				PlayerData[iPlayerLoop].buildingHas[b.ID] = pPlayer:CountNumBuildings(b.ID);
				PlayerData[iPlayerLoop].buildingData[b.ID] = bHas;
			end
		end
		--end

	end

end

--**************************************************************************
-- Happens once every 60 frames.
--**************************************************************************
function Tick()


end

local iTimer = 0;
local iTimerMax = 60;
--**************************************************************************
-- Runs constantly during player's turn.
--**************************************************************************
function HeartbeatChecker()

	if (Game.IsGameMultiPlayer()) then
		return;
	end

	if (Game.IsPaused()) then
		return;
	end

	local player = GetPlayer();

	if (player == nil) then
		return;
	end

	if (UI.IsPopupUp()) then
		return;
	end

	iTimer = iTimer + 1;
	if iTimer > iTimerMax then
		iTimer = 0;
		Tick();
	end
end
Events.LocalMachineAppUpdate.Add( HeartbeatChecker );

--**************************************************************************
-- Internal handling of active player start. Used to simulate events for
-- AI players.
--**************************************************************************
function doInternalActivePlayerTurnStart()
	CheckSetup();
	UpdatePlayerData(Game.GetActivePlayer());
	for iPlayerLoop = 0, GameDefines.MAX_MAJOR_CIVS-1, 1 do
		local pPlayer = Players[ iPlayerLoop ];
		
		if isValidPlayer(pPlayer) then
			if iPlayerLoop ~= Game.GetActivePlayer() then
				if Game.GetGameTurn() > 0 then
					LuaEvents.ISPlayerTurnEnd(pPlayer);	
				end			
			end
		end
	end		
	LuaEvents.ISPlayerTurnStart(Players[Game.GetActivePlayer()]);

end
Events.ActivePlayerTurnStart.Add(doInternalActivePlayerTurnStart);

--**************************************************************************
-- Internal handling of active player end. Used to simulate events for
-- AI players.
--**************************************************************************
function doInternalActivePlayerTurnEnd()
	
	LuaEvents.ISPlayerTurnEnd(Players[Game.GetActivePlayer()]);
				
	for iPlayerLoop = 0, GameDefines.MAX_MAJOR_CIVS-1, 1 do
		local pPlayer = Players[ iPlayerLoop ];

		if isValidPlayer(pPlayer) then
			if iPlayerLoop ~= Game.GetActivePlayer() then
				UpdatePlayerData(iPlayerLoop);
				LuaEvents.ISPlayerTurnStart(pPlayer);	

			end
		end
	end		
end
Events.ActivePlayerTurnEnd.Add(doInternalActivePlayerTurnEnd);
ISTest.lua
Code:
include ("ISEventHandler")

-- Happens when a city is created
function OnCityCreated ( hexPos, pPlayer, pCity )

	print (Locale.ConvertTextKey(pPlayer:GetCivilizationShortDescriptionKey()).." creates city: "..pCity:GetName());
end
LuaEvents.ISCityCreated.Add( OnCityCreated );

-- Happens when a city is destroyed.
function OnCityDestroyed ( hexPos, pOwner, pAttacker, pCity )

	print (Locale.ConvertTextKey(pOwner:GetCivilizationShortDescriptionKey()).."'s city of "..pCity:GetName().." was destroyed by "..Locale.ConvertTextKey(pAttacker:GetCivilizationShortDescriptionKey()));
end
LuaEvents.ISCityDestroyed.Add( OnCityDestroyed );

-- Happens when a city is captured.
function OnCityCaptured ( hexPos, pOldOwner, pNewOwner, pCity )

	print (Locale.ConvertTextKey(pOldOwner:GetCivilizationShortDescriptionKey()).."'s city of "..pCity:GetName().." was captured by "..Locale.ConvertTextKey(pNewOwner:GetCivilizationShortDescriptionKey()));
end
LuaEvents.ISCityCaptured.Add( OnCityCaptured );

-- Happens start of a player/AI's turn.
function OnPlayerTurnStart ( pPlayer )

	print (Locale.ConvertTextKey(pPlayer:GetNameKey()).." turn start.");
end
LuaEvents.ISPlayerTurnStart.Add( OnPlayerTurnStart );

-- Happens end of a player/AI's turn.
function OnPlayerTurnEnd ( pPlayer )

	print (Locale.ConvertTextKey(pPlayer:GetNameKey()).." turn end.");
end
LuaEvents.ISPlayerTurnEnd.Add( OnPlayerTurnEnd );

-- Happens when building is created.
function OnBuildingCreated ( pPlayer, pCity, building )

	if building == nil then
		return;
	end
	if building.ID == nil then
		return;
	end
	
	local buildingID = building.ID;
	
	local desc = GameInfo.Buildings[buildingID].Description;

	
	local name = Locale.ConvertTextKey( desc );
		
	print (name.." created in "..pCity:GetName());
end
LuaEvents.ISBuildingCreated.Add( OnBuildingCreated );

-- Happens when building is acquired.
function OnBuildingAquired ( pPlayer, pCity, building )

	if building == nil then
		return;
	end
	if building.ID == nil then
		return;
	end
	
	local buildingID = building.ID;
	
	local desc = GameInfo.Buildings[buildingID].Description;

	
	local name = Locale.ConvertTextKey( desc );
		
	print (name.." acquired in "..pCity:GetName());
end
LuaEvents.ISBuildingAquired.Add( OnBuildingCreated );

-- Happens when building is lost.
function OnBuildingLost ( pPlayer, pCity, building )

	if building == nil then
		return;
	end
	if building.ID == nil then
		return;
	end
	local buildingID = building.ID;
	
	local desc = GameInfo.Buildings[buildingID].Description;

	local name = Locale.ConvertTextKey( desc );
		
	print (name.." lost in "..pCity:GetName());
end
LuaEvents.ISBuildingDestroyed.Add( OnBuildingDestroyed );

function OnCityPopulationChanged ( pPlayer, pCity, newPop, oldPop )
	
	if newPop > oldPop then
		print ("Population increased by "..newPop-oldPop.." in "..pCity:GetName());
	else
		print ("Population decreased by "..oldPop-newPop.." in "..pCity:GetName());
	end
end
LuaEvents.ISCityPopulationChanged.Add( OnCityPopulationChanged );
 
Thanks. I actually haven't tested if these serial events fire if a player hasn't discovered a city but I doubt it to be honest. Not sure how to test, probably easiest to create a scenario in world builder with one AI poised to take a city.
 
Not sure if I mentioned this somewhere else, but sometime in January I implemented the fundamentals for the TurnStartLoop events. Just in case anyone still needs a tool like this, it's in my utilities component of the unofficial patch, documentated here, at the bottom of the first post.
 
Back
Top Bottom