S3rgeus
Emperor
This mod component allows modders to add powerful new Missions and Notifications to their mods. To see how Missions work, read on here, and for a guide to custom Notifications, see this post.
To use this mod component, download the starter mod and work from there:
Custom Missions and Notifications starter mod download. The usage of these files is explained in this post and the notifications guide linked above.
"Missions" are the actions performed by your units that change the state of either the unit itself or the game. For example, "Spread Religion", "Fortify", "Build Route To", and "Set Up For Ranged Attack" are all missions, even though they do very different things. It's something that I'm sure would be useful for many modders, to be able to have their units take 'arbitrary actions' and add a button to the UnitPanel that lets that be activated.
Now, what if I told you that you could have that button, without replacing UnitPanel.lua? The way Firaxis had it before, adding a new mission to the game was an unbelievable pain in the face. You either had to add it in by hand in the DLL (hard to do) or rewrite the parts of UnitPanel.lua and create your own button that talks to your own other Lua functions. (We all know that UnitPanel.lua is very ugly code to work with.)
This is a DLL mod, so unfortunately it's not compatible with any other mod that replaces the DLL. But if you're going for a total conversion, that isn't so much of a problem. And if you want to integrate my changes into your own DLL then go ahead, I've attached a diff of my code (from the unchanged Firaxis version) which shows you which code to put where. (Or can be applied as a patch.)
How Custom Missions Work
You're going to want to create your new Mission in XML (or SQL). If you want to look at the format for Missions, take a look at: (../Assets/GamePlay/XML/Units/Civ5Missions.xml) to see the ones Firaxis used.
This mod component adds two new fields to the Missions database table "LuaCanHandleEvent" and "LuaHandleEvent". (These are added by the MissionAlterations.sql file in the starter project, so make sure that runs before your new missions are added in your mod's actions panel!)
The value of each of these fields should be unique for each of your new missions. In fact, they should be globally unique, even between separate mods if you plan to run them together, both using this mod component - otherwise they will interfere with each other. So I would recommend being quite specific in your choice of name.
So, what are these new fields doing exactly? You're defining the GameEvent that will fire when the game wants to know if your mission can be undertaken. And a second GameEvent for when your mission occurs. Your subscribers to these two events (which you have named) should look like this:
• GameEvents.CanHandleMission(int playerID, int unitID, bool bTestVisible)
• GameEvents.HandleMission(int PlayerID, int unitID)
I've used "CanHandleMission" and "HandleMission" at my examples, but these are just for demonstration - you should substitute in the event name you used in the database definition of your Mission.
Let's talk about that first event first, "CanHandleMission". In your subscriber to this event, you're telling the game when the new mission you've added can be performed. This function should always return either true or false.
When the function is called, you should run whatever Lua code you need to do to work out if the unit described in the parameters can perform your mission. The first parameter, "playerID", can be used as an index on the Players table to get the player who owns the unit. "unitID" can then be used to get the unit, like so:
Then do whatever assessment your mission requires to work out if the given player and unit can perform that mission, returning true or false appropriately (but always returning one or the other).
Now, before we move on, we should talk about that third and final parameter, the one I've called bTestVisible. This parameter is the game asking you a question. I'm sure you've noticed when playing CiV that sometimes icons in the UnitPanel are greyed out and you can't press them, because you haven't satisfied some requirement right now. (Example, you have the tech to promote a unit, but no money in your treasury.)
When bTestVisible is true, the game is asking you "Can we see this icon right now?". When bTestVisible is false, the game is asking you "Can this unit carry out this mission right now?". Subtle but important difference. If you find that very confusing, ignoring it will just mean that your icon will never be in the 'visible but disabled' state, which isn't terrible either.
Now, on to the second event, "HandleMission". Compared to the last event this one is much simpler! This event fires when a unit has successfully performed your custom mission! Your subscriber to this event also must return true or false every time. You return true when the mission is carried out properly, and false otherwise.
So just go ahead and execute whatever code you need for your mission to take place! The DLL will do nothing aside from notify you that the mission has occurred, so it's up to you to do (or not do) whatever is relevant to your mission. (For example, if your mission is supposed to end the unit's turn, you should set the unit's moves to 0!)
Important Note: At the end of your UnitHandlingMission listener you should return true, otherwise the UI will not update correctly.
The following is an example of a new mission that only warriors can perform. When they perform it, it kills them. Not that useful, but it will show you how to structure things!
Mission database definition:
Lua subscribers:
And now you should have a new mission!
Limitations
This code assumes your mission is "instantaneous". Some people may want this to be an entry point for a kind of 'mode' for the unit, but the database doesn't provide information about whether or not the mission is 'over', and the DLL needs to make that decision as soon as your function returns. If there is a demand for missions that last an amount of time, I can look into adding a column to the Missions table to let people set that themselves and deal with it internally in a future version of this DLL.
Also, it is possible to unintentionally introduce some fun UI bugs through this. None of them should cause a crash (famous last words), but say you were to put a subscriber on "UnitCanHandleMission" that always returned true. Since the event is a TestAny (necessarily, since only one handler should return true for correctly functioning missions), every unit will now be able to perform every custom event you add! Thankfully this doesn't affect existing events in the base game (so no activating interception on your Great Prophets) because they're handled in the DLL and I thought it best not to do more rewriting than necessary.
Other Information
A note about the "OrderPriority" column in the Missions table in the database. The value of this determines how close to the top of the UnitPanel your mission's button will appear. If you give a value less than 100, then your mission will appear in the secondary UnitPanel (alongside scrapping the unit, and often fortify). 199 is the value given to "Spread Religion" which makes it quite prominent (above even "Attack"), but you're encouraged to look through the existing Civ5Missions.xml for information on what value you should use.
Source Code
The diffs attached to this post show how these systems (Custom Missions and Notifications) have been implemented on the DLL side. For data on how Lua and XML files were changed as a part of the Custom Notifications feature set, see the files in the starter project.
To use this mod component, download the starter mod and work from there:
Custom Missions and Notifications starter mod download. The usage of these files is explained in this post and the notifications guide linked above.
"Missions" are the actions performed by your units that change the state of either the unit itself or the game. For example, "Spread Religion", "Fortify", "Build Route To", and "Set Up For Ranged Attack" are all missions, even though they do very different things. It's something that I'm sure would be useful for many modders, to be able to have their units take 'arbitrary actions' and add a button to the UnitPanel that lets that be activated.
Now, what if I told you that you could have that button, without replacing UnitPanel.lua? The way Firaxis had it before, adding a new mission to the game was an unbelievable pain in the face. You either had to add it in by hand in the DLL (hard to do) or rewrite the parts of UnitPanel.lua and create your own button that talks to your own other Lua functions. (We all know that UnitPanel.lua is very ugly code to work with.)
This is a DLL mod, so unfortunately it's not compatible with any other mod that replaces the DLL. But if you're going for a total conversion, that isn't so much of a problem. And if you want to integrate my changes into your own DLL then go ahead, I've attached a diff of my code (from the unchanged Firaxis version) which shows you which code to put where. (Or can be applied as a patch.)
How Custom Missions Work
You're going to want to create your new Mission in XML (or SQL). If you want to look at the format for Missions, take a look at: (../Assets/GamePlay/XML/Units/Civ5Missions.xml) to see the ones Firaxis used.
This mod component adds two new fields to the Missions database table "LuaCanHandleEvent" and "LuaHandleEvent". (These are added by the MissionAlterations.sql file in the starter project, so make sure that runs before your new missions are added in your mod's actions panel!)
The value of each of these fields should be unique for each of your new missions. In fact, they should be globally unique, even between separate mods if you plan to run them together, both using this mod component - otherwise they will interfere with each other. So I would recommend being quite specific in your choice of name.
So, what are these new fields doing exactly? You're defining the GameEvent that will fire when the game wants to know if your mission can be undertaken. And a second GameEvent for when your mission occurs. Your subscribers to these two events (which you have named) should look like this:
• GameEvents.CanHandleMission(int playerID, int unitID, bool bTestVisible)
• GameEvents.HandleMission(int PlayerID, int unitID)
I've used "CanHandleMission" and "HandleMission" at my examples, but these are just for demonstration - you should substitute in the event name you used in the database definition of your Mission.
Let's talk about that first event first, "CanHandleMission". In your subscriber to this event, you're telling the game when the new mission you've added can be performed. This function should always return either true or false.
When the function is called, you should run whatever Lua code you need to do to work out if the unit described in the parameters can perform your mission. The first parameter, "playerID", can be used as an index on the Players table to get the player who owns the unit. "unitID" can then be used to get the unit, like so:
Code:
local pUnit = Players[playerID]:GetUnitByID(unitID)
Then do whatever assessment your mission requires to work out if the given player and unit can perform that mission, returning true or false appropriately (but always returning one or the other).
Now, before we move on, we should talk about that third and final parameter, the one I've called bTestVisible. This parameter is the game asking you a question. I'm sure you've noticed when playing CiV that sometimes icons in the UnitPanel are greyed out and you can't press them, because you haven't satisfied some requirement right now. (Example, you have the tech to promote a unit, but no money in your treasury.)
When bTestVisible is true, the game is asking you "Can we see this icon right now?". When bTestVisible is false, the game is asking you "Can this unit carry out this mission right now?". Subtle but important difference. If you find that very confusing, ignoring it will just mean that your icon will never be in the 'visible but disabled' state, which isn't terrible either.
Now, on to the second event, "HandleMission". Compared to the last event this one is much simpler! This event fires when a unit has successfully performed your custom mission! Your subscriber to this event also must return true or false every time. You return true when the mission is carried out properly, and false otherwise.
So just go ahead and execute whatever code you need for your mission to take place! The DLL will do nothing aside from notify you that the mission has occurred, so it's up to you to do (or not do) whatever is relevant to your mission. (For example, if your mission is supposed to end the unit's turn, you should set the unit's moves to 0!)
Important Note: At the end of your UnitHandlingMission listener you should return true, otherwise the UI will not update correctly.
The following is an example of a new mission that only warriors can perform. When they perform it, it kills them. Not that useful, but it will show you how to structure things!
Mission database definition:
Code:
<Missions>
<Row>
<Type>MISSION_WARRIOR_DEATH</Type>
<Description>TXT_KEY_MISSION_WARRIOR_DEATH</Description>
<Help>TXT_KEY_MISSION_WARRIOR_DEATH_HELP</Help>
<DisabledHelp>TXT_KEY_MISSION_WARRIOR_DEATH_DISABLED_HELP</DisabledHelp>
<EntityEventType>ENTITY_EVENT_GREAT_EVENT</EntityEventType>
<OrderPriority>199</OrderPriority>
<Visible>1</Visible>
<IconIndex>0</IconIndex>
<IconAtlas>UNIT_ACTION_ATLAS_RELIGION</IconAtlas>
<LuaCanHandleEvent>CanDoWarriorDeath</LuaCanHandleEvent>
<LuaHandleEvent>DoWarriorDeath</LuaHandleEvent>
</Row>
</Missions>
Lua subscribers:
Code:
function CanDoWarriorDeath(playerID, unitID, bTestVisible)
if (Players[playerID]:GetUnitByID(unitID):GetUnitType() == GameInfoTypes.UNIT_WARRIOR) then
return true
end
return false
end
GameEvents.CanDoWarriorDeath.Add(CanDoWarriorDeath)
function DoWarriorDeath(playerID, unitID)
Players[playerID]:GetUnitByID(unitID):Kill()
end
GameEvents.DoWarriorDeath.Add(DoWarriorDeath)
And now you should have a new mission!
Limitations
This code assumes your mission is "instantaneous". Some people may want this to be an entry point for a kind of 'mode' for the unit, but the database doesn't provide information about whether or not the mission is 'over', and the DLL needs to make that decision as soon as your function returns. If there is a demand for missions that last an amount of time, I can look into adding a column to the Missions table to let people set that themselves and deal with it internally in a future version of this DLL.
Also, it is possible to unintentionally introduce some fun UI bugs through this. None of them should cause a crash (famous last words), but say you were to put a subscriber on "UnitCanHandleMission" that always returned true. Since the event is a TestAny (necessarily, since only one handler should return true for correctly functioning missions), every unit will now be able to perform every custom event you add! Thankfully this doesn't affect existing events in the base game (so no activating interception on your Great Prophets) because they're handled in the DLL and I thought it best not to do more rewriting than necessary.
Other Information
A note about the "OrderPriority" column in the Missions table in the database. The value of this determines how close to the top of the UnitPanel your mission's button will appear. If you give a value less than 100, then your mission will appear in the secondary UnitPanel (alongside scrapping the unit, and often fortify). 199 is the value given to "Spread Religion" which makes it quite prominent (above even "Attack"), but you're encouraged to look through the existing Civ5Missions.xml for information on what value you should use.
Source Code
The diffs attached to this post show how these systems (Custom Missions and Notifications) have been implemented on the DLL side. For data on how Lua and XML files were changed as a part of the Custom Notifications feature set, see the files in the starter project.