DLL - Custom Missions via XML and Lua

It's not too much work since I've already ported the custom missions and notifications stuff to BNW for my own mods, I'll just need to assemble all of the other little pieces and verify they work as expected. (The XML/Lua shouldn't have to change for BNW, but I've made some improvements to them in SiegeMod and WoTMod which I'll port over to this topic.) I'll see if I can get time to work on this this weekend.

Just to confirm though, my mod doesn't give you Lua events that fire on existing missions. Firaxis' default code is still all there to handle the existing missions and doesn't call up to Lua, I've just made it possible to add new missions from Lua that don't require hacked-together reliance on unrelated events (usually PlayerDoTurn). (And doesn't restrict new missions to the human player, on a technical level, but I have yet to look into how the AI chooses missions and wouldn't be surprised if they ignore new ones despite them being accessible.)
 
I've uploaded a new version of this mod component, now compatible with BNW. Let me know if anyone encounters any new issues with it. Source code in the first post also updated. Added support for ICON_TYPE_CIV, where notifications contain civ icons determined at runtime using arg 6 (GameData) of the AddNotification function.
 
Thank you for your continued support on this mod. I have been thinking about using this in a limited fashion since you first posted it, but it seems way above my level, in spite of your detailed summary.

No problem, I'm happy to give modders some more good tools to work with. If you want any help with setting up a specific new mission/notification, then let me know and I'll try to point you in the right direction. I've put a lot of different capabilities into this component and while that can make it more useful, it also makes it more complicated.
 
I'm very interested in this. Unfortunately, I've already built a massive Lua kludge for my mod to allow custom missions (which aren't really missions as far as dll knows, but seem so in UI and effect).

Although C++ ought to be faster than Lua :))) I do wonder if my Lua kludge might be better than your (more proper) solution here, at least in my particular situation. The situation is that I'm adding literally 100s of actions (which may grow to >1000) for a small subset of units (these are spells or spell-like actions). From my experimentation it seems like the DLL is testing all possible missions for every unit. My Lua doesn't do this, but rather knows ahead of time what (fake-)missions can (ever) be available for a given unit (by Type or Special), and only tests from much smaller pre-cached lists.

Anyway, it's probably only theoretical discussion. It's hard to convert something that already works, even when there is a better solution.
 
I'm very interested in this. Unfortunately, I've already built a massive Lua kludge for my mod to allow custom missions (which aren't really missions as far as dll knows, but seem so in UI and effect).

Although C++ ought to be faster than Lua :))) I do wonder if my Lua kludge might be better than your (more proper) solution here, at least in my particular situation. The situation is that I'm adding literally 100s of actions (which may grow to >1000) for a small subset of units (these are spells or spell-like actions). From my experimentation it seems like the DLL is testing all possible missions for every unit. My Lua doesn't do this, but rather knows ahead of time what (fake-)missions can (ever) be available for a given unit (by Type or Special), and only tests from much smaller pre-cached lists.

Anyway, it's probably only theoretical discussion. It's hard to convert something that already works, even when there is a better solution.

This is very possible - the context switches between Lua and C++ are expensive and handlers can potentially contain multiple switches between the two in their testing process. Your solution is likely faster (from a performance perspective) as the number of units and missions available increases. Though I'd say it's a trade off given the limitations of Lua for dealing with the AI. I'd imagine that the 'real' missions can be integrated with the existing AI and take advanatage of what it does already. Even working from scratch, given that AI should be reasonably computationally intensive (CiV certainly spends a while doing it), the C++ should scale better for that. It would be interesting to actually gather some metrics for this and see how both solutions scale with numbers of missions and how it affects UI speed. (I imagine, eventually, my solution grinds the UI to a halt? I'm not sure if the threshold for affecting the UI is high enough to be irrelevant though.)

I think, were I to optimize for performance, I would have some kind of 'registration' model, where instead of having a single event, units could, at the time they were created, register themselves as sometimes (or always) being capable of carrying out certain missions. (This could even be dynamic, where units can 'unregister' their missions and potentially remove any future overhead for checks if that mission is known to no longer be possible until a specific other 'something' were to happen. That may depend on if we can send strings from Lua to C++? I don't think I've tried that one yet.) That way, the number of context switches required could be decreased, the number of units that would need Lua queries at all would decrease drastically, and many 'default' cases could probably be handled entirely in C++, while still being configurable from XML.

I agree this is a theoretical discussion, I won't be rewriting custom missions for performance unless I'm trying to optimize one of my mods and find it would be an effective place to make necessary gains. But it's an interesting one anyway!
 
Thank you for the mod. I tested under BNW. The new game events work exactly as described.

There is a comma at the end of the Notifications table creation in NotificationProperties.sql which breaks your test mod:
Code:
AlwaysDismissable boolean DEFAULT false,

Regarding bTestVisible, can you clarify what causes the game to return true and what causes the game to return false? Also, you implied this return can be used to shift mission icons into the 'visible but disabled' state. How is that accomplished?

--EDIT
The BNW DLL throws an error on CityBannerManager.lua
Code:
Assets\DLC\Expansion2\UI\InGame\CityBannerManager.lua:215: attempt to call method 'GetWarmongerPreviewString' (a nil value)
 
Thank you for the mod. I tested under BNW. The new game events work exactly as described.

There is a comma at the end of the Notifications table creation in NotificationProperties.sql which breaks your test mod:
Code:
AlwaysDismissable boolean DEFAULT false,

Regarding bTestVisible, can you clarify what causes the game to return true and what causes the game to return false? Also, you implied this return can be used to shift mission icons into the 'visible but disabled' state. How is that accomplished?

--EDIT
The BNW DLL throws an error on CityBannerManager.lua
Code:
Assets\DLC\Expansion2\UI\InGame\CityBannerManager.lua:215: attempt to call method 'GetWarmongerPreviewString' (a nil value)

Thanks for reporting these errors, I'll look into them in the next couple of days. It looks like I haven't yet updated the source code for this mod with some of the newest patches, which is most likely the source of the CityBannerManager error.

Regarding bTestVisible, it's true when the game wants you to work out whether the button should be visible. It's false when the game wants you to work out whether the button should be interactive. Example:

Code:
function CanDoMyMissionThatRequiresMoney(playerID, unitID, iMission, bTestVisible)
	if (iMission ~= GameInfoTypes.MISSION_MONEY_COST) then
		return false
	end
	
	-- this mission can only be done by a specific unit, so only ever display
	-- the button for them
	if (Players[playerID]:GetUnitByID(unitID):GetUnitType() ~= GameInfoTypes.MY_SPECIAL_UNIT) then
		return false
	end
	
	-- the player must have 200 gold to be able to do the mission, but it's helpful for the human
	-- player if their non-fulfillment of that requirement is visible
	if (Players[playerID]:GetGold() < 200 and not bTestVisible) then -- note hard-coded number, don't actually do this, use an XML value!
		return false
	end
	
	return true
end
GameEvents.UnitCanHandleMission.Add(CanDoMyMissionThatRequiresMoney)

So, in this example, if the player selects a unit that can't do this mission (it isn't of type MY_SPECIAL_UNIT) the button won't show up. However, if they select the right type of unit and have less than 200 gold in their treasury, the button will show up and be greyed out. You'll need to supply the text key which explains why the button is greyed out (the red text in the tooltip) in your mission database entry. If you've got multiple possible reasons, I would suggest listing them all.

As a side note, technically, the game isn't returning true or false through bTestVisible, it's passing in true or false as a parameter to our function. We're the ones returning values in this case.

I'll update the SQL at the same time as the DLL source as soon as possible!
 
...
Regarding bTestVisible, it's true when the game wants you to work out whether the button should be visible. It's false when the game wants you to work out whether the button should be interactive. Example:
... If you've got multiple possible reasons, I would suggest listing them all.

I'll update the SQL at the same time as the DLL source as soon as possible!

Thank you for the example! I understand now how this parameter functions. I look forward to the updates so that I can continue to expand and improve the custom missions idea.
 
Thank you for the example! I understand now how this parameter functions. I look forward to the updates so that I can continue to expand and improve the custom missions idea.

No problem for the example. And thanks for your patience while I found time to update this component! I've just uploaded a new version which fixes the SQL error you pointed out and updates the DLLs to the latest source code, so there should be no CityBannerManager.lua error anymore.
 
No problem for the example. And thanks for your patience while I found time to update this component! I've just uploaded a new version which fixes the SQL error you pointed out and updates the DLLs to the latest source code, so there should be no CityBannerManager.lua error anymore.

Thanks Sergeus! I will be expanding my understanding and use of this mod in the near future. Let's hope the DLL is not updated again for a long time.
 
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.

So, how high is the demand right now? :lol:
I really would love to be able to make non-Consuming missions, or setup-like missions as well.
 
So, how high is the demand right now? :lol:
I really would love to be able to make non-Consuming missions, or setup-like missions as well.
I'm using this mod currently in two projects, and if I ever touch Faerun again, I will use it there to add spell casting missions. The current version of this DLL allows non-consuming missions (by default).
 
So, how high is the demand right now? :lol:
I really would love to be able to make non-Consuming missions, or setup-like missions as well.

Non-consuming missions are, as FramedArchitect says, supported already. Missions that 'last' an amount of time (like worker builds) aren't supported in the same way that persistent base game missions are. However, if you maintain some state about the units performing the missions in your Lua, you can have missions that appear to last over multiple turns.

This requires a bit more work from the caller to make those missions continue to 'do things' over those multiple turns, and I think a future expansion to this component (job for me) would be to support some form of "ContinueMission" Lua event for persistent missions like this. I'd need to think more on how it should be structured (probably need to try to use it in a real world scenario myself to get a sense for the technical limitations and sensible caller requirements). I'm prioritizing my Wheel of Time mod at the moment, but I'll keep this in mind going forward.
 
I've just uploaded a new version of this mod which includes a small update to the way custom notifications work. This change is Lua and SQL only, so merging the new NotificationPanel.lua and NotificationProperties.sql into your mod is the best way to upgrade.

This change shouldn't affect any Lua code that sends notifications (it should all work like it previously did). Notifications which use existing icon types (old ExistingIconType column) will need to have their XML definitions amended, because that column no longer exists, having been replaced by a more flexible substitute.

Now, the actual change:

Previously, if you wanted to use an existing icon in your notifications (like you want a notification that can have a unit's icon on it, but you don't know which icon you'll want until runtime) then you had to specify which pre-existing icon type you wanted in the database out of a small, pre-selected list (units, buildings, civs, tech, etc.)

This version changes the way you specify that you want to use an 'existing icon type'. You can now give the name of a database table when defining a notification, to tell it where to look for icons. It will automatically search the 'IconAtlas' and 'PortraitIndex' column in that other table to find an icon from the row you specify with parameter 6 ('iGameData') of the Add function.

Now, that sounds really complicated when I explain it, but actually it's quite simple to use. Say you want a notification that can have a civilization's icon on it. You would specify the following XML:

Code:
<Row>
	<Type>NOTIFICATION_CIVILIZATION_TEST</Type>
	<Message>TXT_KEY_CIV_TEST</Message>
	<Summary>TXT_KEY_CIV_TEST_SUMMARY</Summary>
	<ExistingIconTable>Civilizations</ExistingIconTable>
</Row>

To send this notification to all players in Lua, you would do the following:

Code:
function SendTestNotification()
	
	local pPlayer = <code to get the player your notification is *ABOUT*>
	
	local iX = <some relevant location>
	local iY = <some relevant location>
	local message = "message here"
	local summary = "summary here"
	
	for _,pOther in pairs(Players) do
		pOther:AddNotification(GameInfoTypes.NOTIFICATION_CIVILIZATION_TEST, message, summary, iX, iY, pPlayer:GetCivilizationType())
	end
end

This will send a notification to all players and the icon for the notification will be the civilization icon (ie. shield for America, crown for England, etc.) of pPlayer.

But what if (in true Firaxis style) the table you're trying to use doesn't define its atlases with columns 'IconAtlas' and 'PortraitIndex'? Say you actually want an icon from the Missions table, which uses 'IconIndex'? Then, you'd do this:

Code:
<Row>
	<Type>NOTIFICATION_MISSION_TEST</Type>
	<Message>TXT_KEY_MISSION_TEST</Message>
	<Summary>TXT_KEY_MISSION_TEST_SUMMARY</Summary>
	<ExistingIconTable>Missions</ExistingIconTable>
	<ExistingIconTableIndexColumn>IconIndex</ExistingIconTableIndexColumn>
</Row>

And it would work as normal! ExistingIconTableAtlasColumn can be used to change the atlas column in the same fashion.
 
Ok so this is gonna sound very stupid, but... I can't find out how exactly to use this mod component with the mod I'm trying to build. :cringe:

Do I list this as a dependency, or do I rename the file extension to .rar and move all the files into my project?

I'm sure it's something simple, I'm just not getting it...
 
Getting this error with the most recent version.

Code:
NotificationPanel.lua:266: attempt to index local 'notificationInfo' (a nil value)

EDIT:
I think the issue may be a slight version disjunct between the DLL and Notifications.lua and the following must be included to account for this:

Code:
INSERT INTO Notifications (Type) 
VALUES ('NOTIFICATION_LIBERATED_MAJOR_CITY');
INSERT INTO Notifications (Type) 
VALUES ('NOTIFICATION_RESURRECTED_MAJOR_CIV');
INSERT INTO Notifications (Type)
VALUES ('NOTIFICATION_PLACEHOLDER_1');
INSERT INTO Notifications (Type)
VALUES ('NOTIFICATION_PLACEHOLDER_1');
 
Sergeus, do you have plans to update your mod with the most recent DLL (1.0.3.276)? I think they incorporated a lot of new GameEvents, and it would be great to have access to them.

Either way, thanks for the mod, which allowed me to finally create a spell casting system for one of my old mods!
 
I had not been planning to do another release of this mod component in isolation, but I have updated the source code to use for my own projects. Now that I'm working more on WoTMod I'm feeling like I should change some of the way this mod works for added performance and flexibility.

If you're still in interested in either of the above, let me know and I'll see if I can update the mod component.
 
Top Bottom