Quantcast
Channel: Civilization Fanatics' Forums
Viewing all articles
Browse latest Browse all 12856

DLL - Custom Missions via XML and Lua

$
0
0
"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 It Works
Include the attached DLL in the root of your mod and set it to VFS=True.

Next, 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.

Now, here's where the fun part starts. My DLL introduces two new game events:

• GameEvents.UnitCanHandleMission(int iMission, int playerID, int unitID, bool bTestVisible)
• GameEvents.UnitHandlingMission(int iMission, int PlayerID, int unitID)

You're going to need to provide two new functions and subscribe one to each of these events. Being able to run arbitrary code when a player tells a unit to do something is powerful, but, as Uncle Ben says, with great power comes great responsibility.

Let's talk about that first event first, "UnitCanHandleMission". 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.

Let's talk about the parameters first then. iMission is a number and it corresponds to the ID of the mission your event is being asked about. (After all, if you add multiple events, you need to be able to tell the game when each one is available separately.) The very first thing your Lua function should do is check if iMission is the number it's expecting. If it isn't, and this is important, return false immediately.

Example:

Code:

function CanDoMyCustomMission1(iMission, playerID, unitID, bTestVisible)
        if (iMission ~= GameInfoTypes.MISSION_MY_CUSTOM_1) then
                return false
        end
       
        ...more code here...
end
GameEvents.UnitCanHandleMission.Add(CanDoMyCustomMission1)

After you know you're dealing with the right mission, 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 fourth 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, "UnitHandlingMission". 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.

Like the last event, iMission tells your subscriber which mission the unit is carrying out. Like last time, it is important that you check iMission matches the ID of the mission your function is dealing with. If it doesn't return false immediately. Otherwise, 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.

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.

Attached Files
File Type: zip DLL - Custom Missions.zip (1.36 MB)
File Type: txt CustomMissions.patch.txt (3.2 KB)

Viewing all articles
Browse latest Browse all 12856