DLL - Custom Missions via XML and Lua

S3rgeus

Emperor
Joined
Apr 10, 2011
Messages
1,270
Location
London, United Kingdom
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:

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.
 

Attachments

  • customsystems.patch.txt
    100.8 KB · Views: 242
bResult should be initialsed, as if there are no registered listeners it's content is indeterminate.

Code:
bool bResult;
LuaSupport::CallTestAny(pkScriptSystem, "UnitCanHandleMission", args.get(), bResult);
return bResult;

But it would be better to test the return value of CallTestAny() (like Firaxis does for all CallTestAll() calls) and only use the value of bResult if it returns true (ie there are registered listeners)

EDIT: It would probably be less confusing to keep the mission parameters together and move iOwner and iUnit first (like the SetXY(iOwner, iUnit, iX, iY) call), eg (iOwner, iUnit, iMission, bTestVisible)
 
How do you set the custom icon for your mission button? NVM it's in the table just not documented in the wiki. *sigh*

Yep. It's nice that it's there, but I haven't looked into how to change it yet, I just used one of the default ones for testing purposes.

bResult should be initialsed, as if there are no registered listeners it's content is indeterminate.

Code:
bool bResult;
LuaSupport::CallTestAny(pkScriptSystem, "UnitCanHandleMission", args.get(), bResult);
return bResult;

But it would be better to test the return value of CallTestAny() (like Firaxis does for all CallTestAll() calls) and only use the value of bResult if it returns true (ie there are registered listeners)

Very true! I've updated the first post with a new version which implements this for Unit.CanHandleMission and Unit.HandleMission.

EDIT: It would probably be less confusing to keep the mission parameters together and move iOwner and iUnit first (like the SetXY(iOwner, iUnit, iX, iY) call), eg (iOwner, iUnit, iMission, bTestVisible)

That was how I set it up at first, but I moved the mission ID to the first parameter to stress it needs to be dealt with by every Lua subscriber. If the subscriber doesn't deal with it and checks any other logic first (possibly returning a value), they can introduce bugs with custom missions that aren't associated with that subscriber. (Possibly with other missions created by other modders if there are multiple working on a common project.)
 
That was how I set it up at first, but I moved the mission ID to the first parameter to stress it needs to be dealt with by every Lua subscriber.

As must iOwner and iUnit.

Look at the standard Firaxis events for policies, techs, city builds, etc, etc, etc they are all player/team first, then object id, then specific data - so your's is not following the established pattern.
 
As must iOwner and iUnit.

Look at the standard Firaxis events for policies, techs, city builds, etc, etc, etc they are all player/team first, then object id, then specific data - so your's is not following the established pattern.

Good points, I am convinced. I've updated the first post again with new code. :)
 
I don't know if this will get folded into the main dll eventually, but I'm going to start using it for testing purposes if nothing else. The functionality is simply too useful for the mods I am working on (Follow and Cover & Formations).

For formations, I'm looking for something that mimics the build menu, with one of the build items being 'disband formation' and the others being various formation types. Is this within the capabilities of the current mod?
 
I don't know if this will get folded into the main dll eventually, but I'm going to start using it for testing purposes if nothing else. The functionality is simply too useful for the mods I am working on (Follow and Cover & Formations).

For formations, I'm looking for something that mimics the build menu, with one of the build items being 'disband formation' and the others being various formation types. Is this within the capabilities of the current mod?

This might be a bit challenging if you want to be able to "cancel current mission" with the default mission canceling button. My DLL currently assumes your missions occur and then are done in the time it takes your function to execute, because the mission is deleted from the unit's queue immediately. (Sort of like Spread Religion)

However, I think it would be possible to have multiple missions that set which "mode" that unit is in. Say there are 2 modes: follow and normal (there may be more?). Add two new missions, one that switches to follow mode, the other that switches to normal. Each one's UnitCanHandleMission subscriber returns false when the unit is in that mode should then do the trick. (Eg. Follow button doesn't appear when already in Follow mode.)

The system basically allows you to associate any arbitrary Lua code you like to a given mission and shows a button for it, so there's probably room for quite a bit of flexibility for things like this.

EDIT: Oh, it does look like I may need missions that last for amounts of time (like building improvements or fortifying) for my current mod, so I may extend this DLL to support that too. (That would likely involve new columns in the Missions table.) Then it would also work with the default mission management code (one would hope) like "cancel mission" and "wake unit", but I haven't looked too deeply into how that all works. (The CvUnitMission::ContinueMission function is monstrous and evil looking. Comment at the top reads: "// Yes, hit me again. I like pain." Not even joking.)

I doubt I'll be doing anything this/next week with it though, university exams consuming my life.]

EDIT 2: Just saw your post in the SDK/Lua forum about this in the Self-Aware units topic. If you want your missions to be in the side popup (next to disband unit, so they aren't always on display) just give them an OrderPriority < 100 in the Missions table. When reading parts of UnitPanel.lua I also got the impression that there's a third list of buttons in the UnitPnale *somewhere* but I'm not sure where and may be mistaken.
 
The missions don't have to last as far as I am concerned. In fact, I'm not even sure from a lua standpoint what that would mean, unless they where boxed by CanDoSomething() and HasDoneSomething() checks and events and the logic was in the DLL for how the mission worked.

For me, I just need three things: Ability to add a button. Ability to disable/enable a button. Ability to capture said buttons press in an event handler. I can do all the rest in lua.

My thinking is, if this doesn't get rolled into the main DLL I'll have to re-write unitpanel extensively anyway. So this allows me to put that work off at least for now, and focus on making the thing work the way I want. I can encapsulate the interface in my lua so when and if I need to switch it around the impact on the rest of the code would be minimized.
 
The missions don't have to last as far as I am concerned. In fact, I'm not even sure from a lua standpoint what that would mean, unless they where boxed by CanDoSomething() and HasDoneSomething() checks and events and the logic was in the DLL for how the mission worked.

For me, I just need three things: Ability to add a button. Ability to disable/enable a button. Ability to capture said buttons press in an event handler. I can do all the rest in lua.

My thinking is, if this doesn't get rolled into the main DLL I'll have to re-write unitpanel extensively anyway. So this allows me to put that work off at least for now, and focus on making the thing work the way I want. I can encapsulate the interface in my lua so when and if I need to switch it around the impact on the rest of the code would be minimized.

The only real difference for missions that last after they start is that you can "cancel" them using the cancel mission button (or the wake button, whichever corresponds). Seems like you should be able to do what you say here with this version of the DLL, no problem! The adding/disabling will be determined by when your UnitCanHandleMission subscriber returns true/false.

I'd be happy if my changes could be integrated into Whoward's DLL, but as I said somewhere else, I'd understand if Whoward didn't want to introduce other people's code into a code base he's maintaining. It's not too complicated a change though.

There is no hurry. Kick some exam butt!!

Thanks! :D
 
I'm very interested in this (I'm one of those folks that rewrote UnitPanel.lua to implement my own parallel actions). This is one of several areas where I've kludged a Lua solution but it is very messy.

My main fear of jumping into these dll changes is: What will happen when the BNW dll updates come? I don't mean BNW itself but the "back-ported" updates to G&K dll. I'm scared of being dependent on a modded dll (something I can't do myself yet) rather than my own (admittedly messy) Lua code. And what if Firaxis waits 6 weeks (or longer) to release the source like they did last time?

Nothing you can do about that, of course. Just expressing why I'm not more actively engaged with this ... so far. I'll probably jump in after the expansion dust settles a little. But I am enthusiastic about seeing this folded into the community dll with some other things that I need.
 
This looks like an extremely powerful tool for adding to the human player experience. I suppose one still has to consider the AI when creating new missions, and that seems to be a monster of a problem for a beginner like me. Thank you for sharing this.

Thanks, glad you like it. In theory, the AI interacts with the mission system at the same sort of level as my changes here. I haven't yet looked into how it chooses missions for its units/workers, but I imagine (total guess here) that it shouldn't be beyond possibility for it to execute new missions, provided there is some kind of flavoring used by the base game and the decisions for the missions aren't just hard coded.

I'm very interested in this (I'm one of those folks that rewrote UnitPanel.lua to implement my own parallel actions). This is one of several areas where I've kludged a Lua solution but it is very messy.

My main fear of jumping into these dll changes is: What will happen when the BNW dll updates come? I don't mean BNW itself but the "back-ported" updates to G&K dll. I'm scared of being dependent on a modded dll (something I can't do myself yet) rather than my own (admittedly messy) Lua code. And what if Firaxis waits 6 weeks (or longer) to release the source like they did last time?

Nothing you can do about that, of course. Just expressing why I'm not more actively engaged with this ... so far. I'll probably jump in after the expansion dust settles a little. But I am enthusiastic about seeing this folded into the community dll with some other things that I need.

Thanks, I know what you mean about the Lua solutions. I looked into that first but I figured the time investment now to make a modular system would pay off as I would need many different missions as a part of the mod I'm working on.

Compatibility with BNW will be a problem at first, but I think there's a lot to learn working on it now about the structure of CiV's C++ code base that will transfer quite well into BNW. I spent quite a lot of time before changing anything working out where things were done, and how some changes propagated to other parts of the code. (Still definitely don't have a complete handle on that yet.) I still need to do a lot of reading before moving into any new systems as well, so it's a constant learning process.

I can definitely see why you'd want to wait, BNW isn't that far away. I prefer, since I have the motivation to work on this now, to work with G&K and see how it goes. I'd say porting my changes to a BNW DLL (or just a new Firaxis-altered G&K one) will be easier than writing the functionality in the first place.

If they delay a source release, it isn't guaranteed (just very likely) that an 'old' G&K DLL won't continue to function correctly, given that it replaces the default one. (I imagine BNW would need to be disabled though.)
 
Thanks, I know what you mean about the Lua solutions. I looked into that first but I figured the time investment now to make a modular system would pay off as I would need many different missions as a part of the mod I'm working on.
Whoward takes it about as far as you can in lua I think. Util - Modular Unit Panel
 
The next expanded feature of this DLL mod is custom notifications, which allow for powerful new notifications (with significant visual variations) to be specified directly in XML and then deployed from Lua. There are a lot of options, so it may be difficult to follow at first, but if you seek out the functionality you want from your notifications (and read all of the warnings) then you should be able to build up a knowledge about how to combine the different capabilities provided in inventive new ways over time.

How Custom Notifications Work

Download the starting point project, which contains all of the files required for you to start adding your own new notifications.

New Notifications are added in much the same way as any other GameData entries. If you need a reference for the syntax of that, check out the Modding Wiki XML/SQL Cheat Sheet.

Your new notification requires an XML definition and at least one (possibly two) Lua functions, depending on your needs.

The XML Section
Here, I'll describe the functionality of each new XML/SQL tag I've added to Notifications, and how they can be used:

  • Message - The default text that displays when the user hovers their mouse over your notification
  • Summary - The default text that appears next to your notification when it first appears and scrolls down the screen
  • IconIndex - The default index used to find this notification's icon from an icon atlas
  • IconAtlas - The default icon atlas this notification's icon is in. (The atlas itself should be specified in the IconTextureAtlases table)
  • IconSize - The icon size used by this icon. Defaults to 80. WARNING: For large style notifications, you should set this to 128!
  • BlocksEndTurn - True or false. If true, this notification being active prevents the player from ending their turn. Be sure to specify a blocking message and tooltip if this is true
  • BlockMessage - The message displayed when the player can't end their turn due to this notification
  • BlockToolTip - The message that displays when the player hovers their mouse over the 'end turn' button and your notification is actively blocking
  • LargeButton - True or false. (Defaults to false) When true, this notification is treated as a larger notification, in the style of the "Choose Production" notification from the base game. WARNING: you should use an icon size of 128 if you wish to have a large notification. Also, see the note at the end of this list for information about usable images.
  • MiniCivIcon - True or false. (Defaults to false) When true, this notification will have a small icon representing a Civ that is alive in the current game. Large notifications ignore this option. See the Lua section for notes on how to specify which icon should appear.
  • ChecksKnown - True or false. (Defaults to false) When true, the game checks if the receiver of this notification knows the Civilization you specify as the Mini Icon before displaying the notification. If they don't, then the notification is replaced with a generic "Unknown" notification, its text defined below. For notes on how to create more complex "Unknown Civ" messages, see the Lua section.
  • UnknownMessage - The text that appears when the player hovers over this notification with their mouse and this notification has been transformed into a hidden "Unknown Civ" notification.
  • UsesRuntimeIndex - True or false. (Defaults to false) If true, you may change which index in your given icon atlas is used from Lua, while the game is running. For more details see the Lua section.
  • ExistingIconTable - Text. (Defaults to null) This column tells the game that this notification will use an icon defined by an entry in a different database table. For more details see the Lua section.
  • ExistingIconTableAtlasColumn - Text. (Defaults to 'IconAtlas') This column tells the game which column to look in another table for an existing icon atlas. For more details see the Lua section.
  • ExistingIconTableIndexColumn - Text. (Defaults to 'PortraitIndex') This column tells the game which columns to look in another table for the index of an existing portrait. For more details see the Lua section.
  • DoubleCivIcon - True or false. If true, this notification will have a second small frame for a civilization icon. This tag overrides both UsesRuntimeIndex and ExistingIconType. ChecksKnown does not do anything if DoubleCivIcon is true. (This doesn't mean you can't have double civ icons that only work for known Civs, check the Lua section.)
  • Civ1Anchor - This tells the game where to put the small civilization icon frame, for both DoubleCivIcons and normal ones. It should use the following format:
    "H,V" (without quotes in XML), where H is horizontal and V is vertical. H can be any of the following:
    • L
    • C
    • R
    Which represents Left, Center, and Right respectively. V can be any of the following:
    • T
    • C
    • B
    Which represents Top, Center, and Bottom. Example, say you want the icon in the top left: "L,T". Civ1Anchor defaults to "L,B".
  • Civ2Anchor - The same format as Civ1Anchor, but for the second icon in a DoubleCivIcon notification. Civ2Anchor defaults to "R,T".
  • ExpiresAtTurnEnd - True or false. (Defaults to true) If true, this notification will be automatically dismissed when the player ends their turn.
  • PlayersFXOnPlot - True or false. (Defaults to true) If true, when the player activates (left clicks on) this notification, the camera will move to the X,Y position this notification was given. (See the Lua section for details on X,Y position)

The following is an example of a notification I use in one of my mods:

Code:
<Notifications>
	<Row>
		<Type>NOTIFICATION_HORN_OF_VALERE_DISCOVERED</Type>
		<Message>TXT_KEY_HORN_DISCOVERED</Message>
		<Summary>TXT_KEY_HORN_DISCOVERED_SUMMARY</Summary>
		<ExistingIconTable>Units</ExistingIconTable>
	</Row>
</Notifications>

As you can see, you don't use every tag at once. (Using all of the tags at once is counterproductive, since many override each other.)

Note about large notifications: Large notifications use a different size icon from every other asset in the game. Though it uses an atlas size of 128, the image within that file is not the same actual size as the other 128 atlases. For help creating new large notifications, I've included a .xcf template of a suitable layer mask to use for creating icons that fit properly into the large notification frames.

The Lua Section

Firstly, I'll talk about how to send your new notifications, but be sure to read on about the two new Lua events in the section after because they're essential to your notifications working correctly!

Sending New Notifications

This section will go through how you actually send your notifications to players using Lua while the game is running! This mod component uses the standard Player:AddNotification function, so if you're familiar with that, then you're already most of the way there.

Here's an example of how I use the notification example from above:

Code:
function GiveHorn(playerID, unitID)
	pPlayer = Players[playerID]
	pUnit = pPlayer:GetUnitByID(unitID)

	local message = Locale.ConvertTextKey("TXT_KEY_HORN_DISCOVERED")

	pPlayer:AddNotification(GameInfoTypes.NOTIFICATION_HORN_OF_VALERE_DISCOVERED, message, nil, pUnit:GetX(), pUnit:GetY(), pUnit:GetUnitType())
end

The important part is the line that begins with pPlayer:AddNotification. That actually sends the notification to the player. AddNotification takes 6 (optionally 7) arguments. They are, in order:

  1. The ID of the Notification being sent
  2. The message to display when the player hovers their mouse over the notification
  3. The text which pops up next to the notification when it first appears
  4. The X position of the plot this notification highlights
  5. The Y position of the plot this notification highlights
  6. A game data field, used for a variety of things, described below
  7. An extra game data field, also describe below

Using Parameters 2 and 3: Dynamic Messages
You may be worried that because the message and summary for a notification are specified in XML, that you can't vary it from Lua. But what if you want to use a different key depending on the state of the game? Don't worry, if you supply message and summary arguments to AddNotification, they will override any messages or summaries set in XML. If you want to use your default ones, just pass nil.

Note: The messages and summaries you pass as parameters should already be localized strings, not the actual text keys. So you should use Locale.ConvertTextKey, like I've done in the above example.

Using Parameter 6: Game Data
This parameter to the AddNotification function can do a variety of things, depending on which options your notification uses from the available XML tags.

If you have set DoubleCivIcon to true, then this should be the playerID (Player:GetID()) of the first civ's icon you want to appear.

If you have set UsesRuntimeIndex to true, this should be the portrait index you want to use in the texture atlas. (Atlas must be specified in XML.)

If you have set ExistingIconTable to any value, then this should be the DATABASE ID of the game object that you want the icon from. For example, you want the icon from the Archery Technology, use: GameInfoTypes.TECH_ARCHERY. You have a unit object in Lua, and want its icon? Use: Unit:GetUnitType(). (Note: Unit:GetID() will not work, that's a different kind of ID.)

This allows you to use any existing icon, but be wary of tables that don't use the default column names for setting their atlases and indices. In the event that you wish to use one of these tables (or a table of your own creation), you can change the column in which the atlas and index will be searched for in, using the columns ExistingIconTableAtlasColumn and ExistingIconTableIndexColumn.

Using Parameter 7: Extra Game Data
This optional parameter to AddNotification can, like above, has a variety of uses.

If you set MiniCivIcon to true, then this should be the player ID (Player:GetID()) of the player whose civ icon will be displayed next to the notification. If you don't supply a value for Extra Game Data, then the mini icon will not appear.

If ChecksKnown is set to true, then the game checks that the receiver knows the player whose ID is given by this parameter.

If you set DoubleCivIcon to true, then this should be the playerID (Player:GetID()) of the second civ icon you want to appear with the notification.

If you're familiar with the way the GameData database is set out and have looked at the Notifications table before, you might have noticed that I've dropped the old table entirely and replaced it with a new one. This was to allow modders to use the GameInfoTypes Lua 'object' to refer to their notifications.

More Flexible ChecksKnown Functionality
You may have noticed that setting ChecksKnown to true will only deal with a subset of possible needs for "Unknown Civ has done something" type notifications. If you need a more robust messaging/data setup for your unknown messages, then it may work best to create two notifications, one for 'known' and one for 'unknown'. That way you can manage when each notification is sent yourself, from your own Lua code.

And if you're looking for it, that question mark icon is index 23 in the "CIV_COLOR_ATLAS" atlas.

The New Lua Events
There are two new Lua events that you're going to use with your new custom notifications:

  • PlayerCanDismissNotification(playerID, notificationID, notificationLookupIndex)
  • PlayerNotificationActivated(playerID, notificationID, message, summary, X, Y, gameData, extraGameData)

Let's talk about PlayerCanDismissNotification first. For most people, you can ignore the third argument and just focus on the first two. playerID is the ID of the player who received the notification, notificationID is the database ID of the notification in question. If the player is allowed to dismiss your notification, then return true. Otherwise, return false.

Here's an example subscriber for the example notification I've used above, which can always be dismissed:

Code:
function CanHornOfValereDiscoveryBeDismissed(playerID, notificationID, notificationLookupIndex)
	-- You can always dismiss the Horn of Valere discovery notification
	if (notificationID == GameInfoTypes.NOTIFICATION_HORN_OF_VALERE_DISCOVERED) then
		return true
	end

	return false
end
GameEvents.PlayerCanDismissNotification.Add(CanHornOfValereDiscoveryBeDismissed)

The second event, PlayerNotificationActivated, fires when the player left clicks on your notification. It gives you all of the information about this notification that the game has. This is where you perform the functionality related to your notification. (Open a popup window, create some units, destroy the world, whatever you need.) Note that you don't have to provide a listener for this event for every new notification (unlike PlayerCanDismissNotification, which needs one for each). As long as PlaysFXOnPlot is left at true, clicking your notification will move the camera to the hex that corresponds to the X,Y position you sent to AddNotification, way back when you sent the notification in the first place.

If you don't want the glowing highlight effect but still want to center the camera, Set PlaysFXOnPlot to false, and move the camera manually from your PlayerNotificationActivated subscriber.

Limitations

Updating existing notifications (those that exist in the game, as added by Firaxis) via the new columns added to the Notifications table will not affect their behavior. All of Firaxis' code is still there to handle their native notifications, and this mod doesn't alter that.

Related to the above, you should not use the GameInfoTypes.NOTIFICATION_NAME system to send notifications which exist in the base game. (Use the pre-existing NotificationTypes enum for that.) For a variety of Firaxis-related reasons, the GameInfoTypes method may or may not work, depending on which notification you send.

Currently, only notifications that fit one of the two standard notification appear profiles can be added. (Either the larger 'choose production' style notifications or the smaller 80x80 notifications that make up the majority of notifications in the base game.)
 
Apologies to the one person who downloaded this after I posted the Custom Notifications guide a little while ago, I've just uploaded a new version of the starter mod. There was a bug in the initial DLL that prevented existing, non-large notifications from being dismissable. It is now fixed! Source code updated too.
 
Has this been updated for BNW? I really need to add a mission to for a replacement for Venice's Great Merchant.

I have updated it locally, but I still need to update its associated startup mods (the Lua that drives it and such) with changes I've made for SiegeMod and my Wheel of Time mod and make sure it all still works in isolation. I didn't know if there was any demand for this left, but if you're keen to use it I'll try to put up an update as soon as I can.
 
As far as I can see, your DLL is the only game in town for this aspect. Any other DLL I use will be a cludge using event handles that only indirectly allow me to activate lua at the time an existing mission happens.

But I don't want you doing all that work if I am the only modder interested.

If there is interest, and you update the mod, though, I'll be first in line to use it (assuming I can figure out how :) ).
 
Top Bottom