DLL - Custom Missions via XML and Lua

It would be great to have this mod with the most recent DLL, which offers some cool new hooks and game events. I hope this would be an easy copy/paste job!

It's actually easier at this point for me to release one with the performance and flexibility changes. The main issue would be that it breaks existing missions. Not significantly, but the crux of the changes are:

  • Each Mission entry in the DB should define a LuaCanHandleEvent - which is the name of the GameEvent that will be called when the game core wants to know if you can perform that mission. (You will only get callbacks on this event for your mission - so you don't need to filter out other mission IDs anymore, your handlers can be simpler.)
  • Each Mission entry should define a LuaHandleEvent which fires when a unit actually performs the mission. It's basically the same as before, you just don't need to filter by missionID here either.
  • Added support for "C++ only" missions, for people who merge the code into their own DLLs rather than just build on top of it.

So you use the DB to define your own GameEvents that you can then be the only listener on. (Names may conflict across mods, so I would recommend being quite verbose.) There's also a new SQL file that adds those columns to the Missions table, but it's quite simple:

Code:
ALTER TABLE Missions ADD LuaCanHandleEvent text DEFAULT null;
ALTER TABLE Missions ADD LuaHandleEvent text DEFAULT null;

Would those changes be a pain for you to adapt your mod for? You should only need to remove the missionID filters and parameter from your lua listeners, define the names of the game events you want for each mission, and then change your subscriber lines. How easy that is depends largely on how many missions you've got and how well find+replace would work on your handlers.

I can do the Firaxis-source-code-only update, but it will probably take me longer to release since I need to put together quite a few things from different source control repos and ensure it all still works.
 
Would those changes be a pain for you to adapt your mod for? You should only need to remove the missionID filters and parameter from your lua listeners, define the names of the game events you want for each mission, and then change your subscriber lines. How easy that is depends largely on how many missions you've got and how well find+replace would work on your handlers.

I can do the Firaxis-source-code-only update, but it will probably take me longer to release since I need to put together quite a few things from different source control repos and ensure it all still works.

Sounds very good... much easier to implement actually! I only have about 50 new missions right now, so that's a small update. Would your revisions also include the support for the Custom Notifications?
 
Sounds very good... much easier to implement actually! I only have about 50 new missions right now, so that's a small update. Would your revisions also include the support for the Custom Notifications?

Yes, it will include custom notifications too! The code for this is relatively in flux at the moment - stuff I'm working on keeps leading to missions-related things. I'm currently adding support for InterfaceModes (like when you click on the "Ranged attack" button and the UI switches over to let the user pick where to attack).

I should have something releasable and verified this weekend!

Also, 50 missions, nice one! That's more than me! :p I've only got like 5 at the moment.
 
Terrific! Thank you!

Glad you're finding it useful! I've just updated the OP and uploaded a new version of the starter project with the new SQL file and updated DLL. Let me know if run into any issues!

I've also dropped support for G&K, this version only works with BNW.
 
I was very sad to see that the missionIDs are no longer passed in as args. Sadly, I used the missionIDs as keys to almost all my functions, so I'm not sure if I'm going to use the new system. :/

Since you specify the Lua event on a per-mission basis, the mission ID was superfluous information. You know which mission ID you should have gotten based on which subscriber is currently executing. Unless you've gone with a per-mod subscriber approach, and all of your missions point to the same lua event?

If you've got a big table, indexed by mission ID, that contains your functions for those missions and don't want to change that, you could have your subscribers be quite simple in the vein of (for each mission):

Code:
function CanHandleMissionOne(playerID, unitID, bTestVisible)
	return g_tableofHandlers[GameInfoTypes.MISSION_CUSTOM_MISSION_ONE](playerID, unitID, bTestVisible)
end
GameEvents.CanHandleMissionOne.Add(CanHandleMissionOne)

I honestly hadn't considered the possibility of a mod-wide single mission handler, I'd always been thinking people would consume each mission with a separate handler.
 
I honestly hadn't considered the possibility of a mod-wide single mission handler, I'd always been thinking people would consume each mission with a separate handler.

Let me clarify. I use the missionIDs as arguments in many of my functions - not as keys in hash tables. I'm just using the GameEvents as wrappers. I ended up pasting the missionIDs into the GameEvents, because I need them there. It's really just a matter of convenience, but it does up the chance I'll make a mistake (i.e. not paste the proper missionID).

The issue is that I added many data columns to Missions table and I use the missionID to retrieve that data (e.g. GameInfo.Missions[missionID].Cooldown)

Thanks again for taking time to update the mod!

best,
FA
 
I'm currently adding support for InterfaceModes (like when you click on the "Ranged attack" button and the UI switches over to let the user pick where to attack).

Just saw this... interesting. I built a spell casting interface for my mod, but DLL method would probably be better! I had to account for a few things the interface doesn't do, though, like highlighting splash damage areas, chained targets, different highlight colors for buffs and debuffs, and more detailed target info.

Regarding the PlayerNotificationActivated GameEvent, is that equivalent to a left click callback?
 
Just saw this... interesting. I built a spell casting interface for my mod, but DLL method would probably be better! I had to account for a few things the interface doesn't do, though, like highlighting splash damage areas, chained targets, different highlight colors for buffs and debuffs, and more detailed target info.

Regarding the PlayerNotificationActivated GameEvent, is that equivalent to a left click callback?

I haven't exposed my InterfaceModes changes to Lua since I've been doing all of my other work C++ side, but it certainly can be done. The InterfaceModes only really give you a way of telling what the UI should be doing at the moment though - it's basically impossible to reuse any of the "select a target for a ranged attack" stuff that Firaxis has. This is because they haven't decoupled the selection from the actions properly - the stuff that does the highlighting directly calls the ranged attack Lua methods, so there's no way to use it for selecting an alternate range.

It is an example you can work from though - that's what I ended up doing. you can do that in an entirely standalone Lua file too, which is nice.

The PlayerNotificationActivated GameEvent is almost exactly like a left click handler. Except it has been filtered by the CanBeActivated function DLL side, which I believe will always return true for new notification types, and has some added functionality like moving the camera to the notification's highlighted location. (If you haven't turned off the highlight FX for your notification.)
 
The PlayerNotificationActivated GameEvent is almost exactly like a left click handler.

I really like the ability to handle code on click for the custom notifications. I haven't used it much, but it will make things a bit easier for users.

If you ever update this DLL again I have a small request. Currently we have a lua method for changing unit combat strength (unit:SetBaseCombatStrength(int)), and I imagine there is a DLL side function to change a unit's ranged combat strength, too. Would it be possible to expose that function to lua? Something like unit:SetBaseRangedCombatStrength(int)? If so, that would save me over 1000 lines of code (literally!).

Best,
FA
 
If you ever update this DLL again I have a small request. Currently we have a lua method for changing unit combat strength (unit:SetBaseCombatStrength(int)), and I imagine there is a DLL side function to change a unit's ranged combat strength, too. Would it be possible to expose that function to lua? Something like unit:SetBaseRangedCombatStrength(int)? If so, that would save me over 1000 lines of code (literally!).

Best,
FA

I expected to be able to just say "yeah, sure!" but I've looked at the code and there's no DLL-side equivalent to this function. :( Ranged combat strength is calculated as needed every time from the unit's base ranged strength (as defined in the DB). It doesn't store any persistent changes to its base ranged strength - I think you might be best going with promotions that modify ranged combat strength, adding and removing them as necessary.

I'll investigate some more to be sure.
 
I expected to be able to just say "yeah, sure!" but I've looked at the code and there's no DLL-side equivalent to this function. :( Ranged combat strength is calculated as needed every time from the unit's base ranged strength (as defined in the DB). It doesn't store any persistent changes to its base ranged strength - I think you might be best going with promotions that modify ranged combat strength, adding and removing them as necessary.

I'll investigate some more to be sure.

Thanks for taking time to look into that. It's unexpected and unfortunate that changing unit ranged strength is not an option. I have special units that increase in strength as they gain levels, which is easy for melee. For ranged I have to kill the lower level unit and init a new unit when it levels, which means I have to have a special unit for each level that can be attained, which clutters the DB and lua code. :/
 
I have been playing around with this with no success. Could you please look at my code and see what I am doing wrong? I am trying to make a unit mission to convert a cavalry to cannon. The logs say the script has run but displays no errors and when I goto the cavalry in game there is no mission available.

XML.
Code:
	<Missions>
		<Row>
			<Type>MISSION_CHANGE_TO_RANGED</Type>
			<Description>TXT_KEY_MISSION_CHANGE_TO_RANGED</Description>
			<Help>TXT_KEY_MISSION_CHANGE_TO_RANGED_HELP</Help>
			<Time>10</Time>
			<Target>0</Target>
			<Build>0</Build>
			<Sound>1</Sound>
			<HotKey>KB_S</HotKey>
			<AltDown>0</AltDown>
			<ShiftDown>0</ShiftDown>
			<CtrlDown>0</CtrlDown>
			<HotKeyPriority>0</HotKeyPriority>
			<AltDownAlt>0</AltDownAlt>
			<ShiftDownAlt>0</ShiftDownAlt>
			<CtrlDownAlt>0</CtrlDownAlt>
			<HotKeyPriorityAlt>0</HotKeyPriorityAlt>
			<OrderPriority>198</OrderPriority>
			<Visible>1</Visible>
			<IconIndex>12</IconIndex>
			<IconAtlas>UNIT_ACTION_ATLAS</IconAtlas>
			<LuaCanHandleEvent>CanDoCannonChange</LuaCanHandleEvent>
			<LuaHandleEvent>DoCannonChange</LuaHandleEvent>
		</Row>
	</Missions>

LUA.
Code:
function CanDoCannonChange(playerID, unitID, bTestVisible)
	if (Players[playerID]:GetUnitByID(unitID):GetUnitType() == GameInfoTypes.UNIT_CAVALRY) then
		return true
	end
	
	return false
end
GameEvents.CanDoCannonChange.Add(CanDoCannonChange)

function DoCannonChange(playerID, unitID)
	local pUnitCannon = Players[playerID]:InitUnit(GameInfoTypes["UNIT_CANNON"], GetUnitByID(unitID):GetX(), GetUnitByID(unitID):GetY())
	pUnitCannon:Convert(Players[playerID]:GetUnitByID(unitID))
end
GameEvents.DoCannonChange.Add(DoCannonChange)
 
Could you please look at my code and see what I am doing wrong?...

Code:
GetUnitByID(unitID):GetX()

That is a player method

Code:
player:GetUnitByID(unitID):GetX()

Probably best if you localize that instance, since you use it a few times there.

Code:
local unit = player:GetUnitByID(unitID)
unit:GetX()

EDIT: Also, I don't think the Convert method works, but all you need to do there is just kill the old unit (if this is indeed some kind of upgrade).

best, FA
 
Thanks FA, I am making progress now, the mission button is showing for the unit now but it does not do anything when I click on it. Lua log says:

...CustomStartingPoint (v 3)\Lua/ActivatedSubscribers.lua:17: attempt to index global 'player' (a nil value)

...CustomStartingPoint (v 3)\Lua/ActivatedSubscribers.lua:17: in function <...\CustomStartingPoint (v 3)\Lua/ActivatedSubscribers.lua:16>

This is line 16 & 17
Code:
function DoCannonChange(playerID, unitID)
	local pUnit = player:GetUnitByID(unitID)

Thanks for your help.
 
Apologies for the stupendously slow reply, folks! I need to check this topic way more often!

Thanks FA, I am making progress now, the mission button is showing for the unit now but it does not do anything when I click on it. Lua log says:

...CustomStartingPoint (v 3)\Lua/ActivatedSubscribers.lua:17: attempt to index global 'player' (a nil value)

...CustomStartingPoint (v 3)\Lua/ActivatedSubscribers.lua:17: in function <...\CustomStartingPoint (v 3)\Lua/ActivatedSubscribers.lua:16>

This is line 16 & 17
Code:
function DoCannonChange(playerID, unitID)
	local pUnit = player:GetUnitByID(unitID)

Thanks for your help.

I'm not sure if you've fixed this yet, Wolfdog, but I think FramedArchitect was referring to your later invocations of GetUnitByID (the ones where you're getting the X and Y coordinates to pass into InitUnit).

I'd do the second function something like this:

Code:
function DoCannonChange(playerID, unitID)
	local pPlayer = Players[playerID]
	local pOldUnit = pPlayer:GetUnitByID(unitID)
	local pUnitCannon = pPlayer:InitUnit(GameInfoTypes["UNIT_CANNON"], pOldUnit:GetX(), pOldUnit:GetY())
	pUnitCannon:Convert(pOldUnit, true)
end
GameEvents.DoCannonChange.Add(DoCannonChange)

I believe convert should work. I've never called it from Lua before, but CvUnit::convert seems to work fine from C++ and the Lua version looks like it calls straight into it. I added a second parameter because it looks like the DLL expects an "isUpgrade" boolean as well. This might default to false if you don't supply it, I'm not 100% certain, but it looks like your usage is an upgrade? (Upgrade being true makes promotions that should expire on upgrade expire correctly, so they don't get given to the new unit.)
 
Thanks for the reply S3rgeus, I did actually get this fixed in the end. I did have other issues with converting turns remaining but got that sorted as well. Thanks though the mission thing works really well.
 
Top Bottom