Adding Events

lemmy101

Emperor
Joined
Apr 10, 2006
Messages
1,064
Not sure if this has been covered already here, but you can do this:

Code:
function MyCode()
     if whatever then
          LuaEvents:MyCustomEvent(param1, param2, whatever);
     end
end

specifying your own event name and parameters.

Then elsewhere, when you want to respond to that event, you can do:


Code:
function OnMyCustomEvent(param1, param2, whatever)

   print("Custom event fired");

end
LuaEvents.MyCustomEvent.Add(OnMyCustomEvent);

This will probably be most use for those doing mod components in Lua that are likely to be used in other people's mods. I for example am currently working on an ISEvents lua script that simulates a lot of the events that are currently missing in Lua, such as OnBuildingCreated or OnPlayerTurnStart.

Will release it when it's had more testing.
 
Well you can have it now but there are no guarantees the city destroyed / captured ones work with buildings being gained / lost, you may need to tweak that in the meantime...

It's also not as neat as it could be..

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 - example of usage.
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 );

ISBuildingAquired - Someone have gained control of a building either through building or capturing one. Doesn't happen on load.
ISBuildingCreated - A building is created in the game. Happens on load so any effects can be reapplied.
ISBuildingLost - A player loses a building, either through selling or losing the city it's in.
ISPlayerTurnStart - A player, AI or human, has started their turn.
ISPlayerTurnEnd - A player, AI or human, has ended their turn. Note, AI players all start their turn, then all take their turn, then all end their turn. This shouldn't cause any problems though.
ISCityCreated - A city has been created.
ISCityDestroyed - A city has been destroyed.
ISCityCaptured - A city has been captured.
ISCityPopulationChanged - City's population has changed.
 
awesome analysis of events and very nice wrapper. but there's a few events that are already handled by the API, there's no need to readd them it's just going to add to the lua's memory footprint. everything in the game's events table can be handled on it's own
 
I feel I'm missing something obvious... what's wrong with this?

PHP:
LuaEvents.AnswerToLife.Add(
function ()
	return "42";
end)

print("AnswerToLife = " .. LuaEvents:AnswerToLife());

Runtime Error: attempt to concatenate a nil value.

Same error with the second method.

PHP:
function getAnswerToLife()
	return "42";
end

LuaEvents.AnswerToLife.Add(getAnswerToLife);

print("AnswerToLife = " .. LuaEvents:AnswerToLife());
 
awesome analysis of events and very nice wrapper. but there's a few events that are already handled by the API, there's no need to readd them it's just going to add to the lua's memory footprint. everything in the game's events table can be handled on it's own

Not true. For one, there's stuff that this event system does that occurs in a specific order, therefore using the core city created / destroyed would screw that up (you would get buildings before you got the city the buildings were in, for e.g.). Secondly, this nicely wraps the player objects etc to make it easier to use, and lastly these events are only occuring when cities are created / destroyed, so it's not going to effect performance, and the minute increase in memory is a small price to pay for it working and consistency in your events I think. :D
 
looking it over a second time i see what you're doing there, and you're right it is a minimal increase ;)

did you know that you can catch the AI's turn with gameinfodirty and a activeplayer check?
 
Wooo thanks for that! Very handy thing to know. :D

How do I do that exactly? I know how to call the event, but when do I call it? at the end of player / AI's turns? Or do I just capture it? In which case what if something else dirties the game data? When is that called exactly as I figured it'd be every time you changed something.
 
Code:
function test ()
	for n=0,63 do
		if ( Players[ n ]:IsTurnActive() ) then 
			print("Player " .. n .. " turn");
			break;
		end
	end
end
Events.SerialEventGameDataDirty.Add( test )
it's triggered at the very start of a player's turn, including after the game starts. for a human player it also triggers when switching around windows and popups so personally i dont like it much for human stuff, but it works great to catch the AI
 
you can't return values from a lua event.

I guess I still don't completely understand what LuaEvents.*.Add() does, then. :crazyeye:

First I thought Events and LuaEvents were like Blizzard's (WC3/SC2) hardcoded event generators you register listeners with (triggers).

Then I was trying to understand how this mod works. It seemed like it's simply adding a function pointer to the LuaEvents table (a public object) so other mods can call that function. From what you're saying, it appears this was not the correct interpretation either...

So what exactly are Events and LuaEvents?

More specifically to what I'm trying to figure out... how can I define or make a class public? Classes are package private by default in Lua, right?
 
Actually guys, I believe the reason you can't get a return from a LuaEvent is because LuaEvents are run on a separate action event thread. Thus the return ends up spitting out inside the action event handler function, not the function from where you called the LuaEvent. That merely places the event on the stack for execution by the other thread. This is also why you can appear to use a LuaEvent to save to a local global, then use another LuaEvent to send the saved data back to the original file in lou of a return, yet can't save that data to the local global of the target file despite being able to output the data to the console. This is something smellymummy pointed out in another thread. Basically, because the functions are being executed on another thread, the data scope is volatile.

FEAR NOT! My recent experiments with LuaEvents breaks the boundaries. Soon it will be possible to share what ever dynamic data you want between all lua states, by reference. Oh yeah... it's a big deal. :)

I'll have it posted in a couple of days.
 
@Thalassicus

The call LuaEvents.*.Add() makes the game add the given function within the parenthesis whenever the event is triggered. The given function would then be able to take in the parameters submitted by the event in its own set of parenthesis when you first make the function. Making the event trigger is done through LuaEvents:*() where the parenthesis are the parameters to be submitted for any function to retrieve that may be called when the event is triggered.

@lemmy101
How did you find what the parameters to the base events were? They're not on the Civ V wiki.
 
:D http://forums.civfanatics.com/showthread.php?t=398814

I need you to test the heck out of this, if you don't mind... ;)

If you're curious about the underlying code, the key to getting a return from a LuaEvent is to pass in a table, let it put the "return" in the table, and then you have a reference to what ever it put in there. This way the data exists in your own thread's stable data scope, not the other thread's volatile data scope.

Interestingly, tho I haven't tested it extensively, it appears that the current thread waits for the LuaEvent thread to complete the function before resuming execution. So while LuaEvents don't provide a true return, thread execution is at least synchronized. Good thing. There are few among us who would want to mod in an unsynchronized multi-threaded lua environment. :scared:
 
Thanks for the information I am trying to learning modding with lua and I have great difficulty with it because there are no detailed guides on this.

How do you know the in Game Defined functions, references, events that you are using in your scripts?

for example:
Spoiler :
PlayerData[iPlayerLoop].buildingHas[building.ID] = pPlayer:CountNumBuildings(building.ID);


how did you learn about the function CountNumBuildings?
 
This is SO awesome guys :). This whole thread is just win in so many ways. LUA doesn't seem too difficult, but when compared to something as simple as XML it seems harder than it is most likely.

I'm currently attempting to add my own reversible policies in my own Policy Branches in the game, and although the XML (for the most part) I have formatted to where I want it to be, I haven't a clue what I'm doing with LUA.

For example, I have set my policy branches to, rather than using the traditional policy squares, simply have five of the "Adopt"-size buttons with the policies on them that can be clicked. Visually, it is already there. The problem is these buttons do not yet function, and the LUA drives me crazy. Here's a sample of some code I think I've narrowed it down to:

Spoiler :
Code:
-- Adjust Policy Branches
	local i = 0;
	local numUnlockedBranches = player:GetNumPolicyBranchesUnlocked();
--	if numUnlockedBranches > 0 then
		local policyBranchInfo = GameInfo.PolicyBranchTypes[i];
		while policyBranchInfo ~= nil do
			local numString = tostring( i );
			
			local buttonName = "BranchButton"..numString;
			local backName = "BranchBack"..numString;
			local DisabledBoxName = "DisabledBox"..numString;
			local LockedBoxName = "LockedBox"..numString;
			--local EraLabelName = "EraLabel"..numString;
			
			local thisButton = Controls[buttonName];
			local thisBack = Controls[backName];
			local thisDisabledBox = Controls[DisabledBoxName];
			local thisLockedBox = Controls[LockedBoxName];
			--local thisEraLabel = Controls[EraLabelName];
			
			local strToolTip = Locale.ConvertTextKey(policyBranchInfo.Help);

I can see here that it defines the buttons in the policy branches (BranchButton which, in the XML, is the definition of these buttons that I am using, as well as the "thisButton" control for them, but I'm not sure how to get these to do anything in the LUA file. DO you think it's possible i can just copy this definition below:

Spoiler :
Code:
-- Branch is not yet unlocked
			if not player:IsPolicyBranchUnlocked( i ) then
				
				-- Cannot adopt this branch right now
				if (not player:CanUnlockPolicyBranch(i)) then
					
					strToolTip = strToolTip .. "[NEWLINE][NEWLINE]" .. Locale.ConvertTextKey("TXT_KEY_POLICY_BRANCH_CANNOT_UNLOCK");
					
					-- Not in prereq Era
					if (bEraLock) then
						local strEra = GameInfo.Eras[iEraPrereq].Description;
						strToolTip = strToolTip .. " " .. Locale.ConvertTextKey("TXT_KEY_POLICY_BRANCH_CANNOT_UNLOCK_ERA", strEra);
						
						-- Era Label
						--local strEraTitle = "[COLOR_WARNING_TEXT]" .. Locale.ConvertTextKey(strEra) .. "[ENDCOLOR]";
						local strEraTitle = Locale.ConvertTextKey(strEra);
						thisButton:SetText( strEraTitle );
						--thisEraLabel:SetText(strEraTitle);
						--thisEraLabel:SetHide( true );
						
						--thisButton:SetHide( true );

And change it to not only be defined when the Branch is not unlocked and set the tooltip AND Button to always display what I want it to display? If so, how would I go about that?
 
Yeah I think we really need a good beginner tutorial on Lua. Even I had trouble finding my way in and I've been modding with Lua for some years now.
 
Top Bottom