Modding Tutorial No. 8: Quests

Last time, we used the taskload function to load TaskFlowerOfItaly.xml into the Locris objective and discover the task in it.

Objectives
Objectives are a collection of tasks, largely used to keep the objective window organized; the default game has an objective for prelude tasks, for tutorial tasks, for global hegemony tasks, for the ancient rival event chain, and one for events about each faction. These objectives don't actually control anything about the tasks within, but can be expanded or hidden in the objective window. That said, you can define your own objectives and give the objective its own script to function more as a metatask if you want. If you do want to define an objective, you'll need to create an .xml file for it. These files are not loaded by default; to load one, use the objectiveload script function in a script somewhere (probably an even) with the filename passed in as a parameter:. An objective file should look something like this: Hegemony QuestHegemony::TITLE QuestHegemony::DESCRIPTION An "objective" tag with "name", "title", and "description" subtags. "name" is the unique internal name the code uses for this objective, make sure no other objective uses it, while "title" and "description" are the names of strings that show up in the objective window for this objective, and any number of objectives can have the same strings. You can also add in a "scriptinit" tag, which will run the lua script when the objective has been loaded. Example QuestHegemony::TITLE QuestHegemony::DESCRIPTION print("Example objective loaded and initialized!") You could also define tasks directly in the objective at the start rather than adding them in later via event; to do this, simply put a task tag within the objective.

Tasks
You'll be working with tasks more than with objectives; in Flower of Italy, we don't bother defining a new objective, but simply add a task to the default objective for the Locris faction. Tasks are what you probably think of when you think of quests in Hegemony III: examples of tasks are A Humble Request, Meeting the Neighbours (formerly known as General Tour), and Search and Rescue. They can be seen in the right of the screen in the objective tracker and are also stored as xml, either immediately in their objective's file, or in their own xml and later added to the objective with the  script command. A task xml should look like: FlowerOfItaly QuestBurnLocris::FlowerOfItaly::TITLE QuestBurnLocris::FlowerOfItaly::DESCRIPTION false talkingheadnewobjective( getstring("PH", "QuestBurnLocris::FlowerOfItaly::Introduction"), getplayerfaction:getadvisor("spymaster") ); waitsubtasks; Looks pretty similar to the objective, "name" "title" and "description" all do the same things, though it's worth noting that the name only has to be unique within the objective. Any time you try to look up this task, you'll be using the objective name followed by a dot then the task name, so Locris.FlowerOfItaly, which count as a different name than Prelude.FlowerOfItaly even though both tasks have the same name.

The biggest difference between tasks and objectives is that tasks HAVE to have a script and will shut themselves down the moment this script is done. There will usually be a  or   call in the task script; this will be the point at which the script will stop running and wait for a subtask to finish. If the task has more than one subtask, you'll have to choose between, which will be done the moment any of the subtasks are done, or  , which will wait for all of them. You usually use  to present the player with a choice and waitsubtasks for things like conquering a whole bunch of cities. Anything you put before the wait call will be run the moment the task is discovered, so our talkingheadnewobjective call (more on what that function does later) will happen immediately when the task is loaded. Anything put after the wait will run when the right amount of subtasks are completed, but since Locris.FlowerOfItaly has nothing after, the task will just consider itself over when the wait is completed and close itself.

Subtasks
So we know that tasks wait for subtasks to finish, but what's a subtask look like? Once again, they're xml, and like tasks, they can either be stored directly in the task they belong to or loaded into it later with the  command, using the task's full name (which includes the name of the objective they're in) for the first parameter. Here's a subtask: Subtask1 SubtaskCaptureSpecific trackself; local	locris = getentitybytag("locris"); subtaskposfollow( whoami, locris ); waitcapture( locris ); taskload("Locris", "Resources/Objectives/BurnLocrisBurn/TaskPluckingThePetals.xml"); taskdiscover("Locris.PluckingThePetals", false); Like most subtasks, it's just a "name", "status" and "script". The "name" operates under similar rules to the task, where its name has to be unique within its task's subtasks, the "script" is a lua script that starts running the moment the subtask is loaded (which is when the task is loaded), and the "status" is the name of a string that will show up under the task's name. For example, A Humble Request's subtask uses the SubtaskDefeatNFaction, which is the part in the tracker that says "Defeat Locrian units (4/5)" or whatever the appropriate faction and numbers are. This subtask is about capturing the city of Locris, so we use the string SubtaskCaptureSpecific, which will look like Capture [name]:h3_gototag:.

There are a couple of script commands in this subtask's script that're only ever used in subtasks, so it's worth looking at. The  command just puts the task in the tracker on the right side of the screen; by default, tasks start untracked and thus the player needs to go to the objective window and click the tracker checkbox for the task themselves if they want to keep track of this, but the   puts it there for them.

Afterwards, we call. The  function is a quick way to get the full name of the subtask rather than typing out "Locris.FlowerOfItaly.Subtask1", and one that would remain accurate if the name were changed somehow (by adding the task to a different objective, for example) and so is preferred, but using "Locris.FlowerOfItaly.Subtask1" would work too. subtaskposfollow is a function that takes two things, the name of a subtask (handled by ) and an entity to follow (the locris script entity we saved off) and will then start placing map markers over that entity which remind the player it's the target of a quest.

is similar to the task's  in that the script will stop and wait until something happens, but it waits until the entity passed in is captured by the player. This one can be used outside of subtasks, but it contains a small bonus subtask-only feature where it includes calls to the status functions.

Status Functions, Anchors and Placeholders
statusstring, statusstringref and statusint are subtask-only functions that will replace a certain placeholder in the status string with whatever you've passed in, statusint will use a number, statusstringref takes the name of a string and uses whatever's in that string while statusstring just uses whatever you pass it. If we look in the status string we're using in Locris.FlowerOfItaly.Subtask1, we see the following: Capture  This contains two placeholders, ' ' and 'placeholder:scripttag="gototag"'. The first one, named "name", is in its own placeholder tag; if we call, the entire tag would be replaced by "Example" and the end product would look like Capture Example:h3_gototag:. The  function automatically calls statusstringref with the string the object it's waiting to get captured uses as its name, so that's why the subtask will read Capture Locris:h3_gototag: instead of Capture [name]:h3_gototag:.

The other placeholder is for an attribute to an "a" tag. "a" tags, generally called "anchors", make all the text inside the tag interactive:  would show up as "Click Here" that, when clicked, goes to an entity with the tag "locris". There're a few different tags you can put in anchors, like "gototag" which goes to an entity with the given tag, "gotoid" goes to an entity with a given numerical id, "selectid" which selects an entity with a given id, or "script" which executes a script given. Our placeholder will add a "gototag" attribute to the anchor tag with whatever is handed to it;  will automatically take the tag of the entity handed to it and call , which turns the anchor from   or in our case  , wrapping the img tag inside the anchor tag in a link that will make the camera zoom to Locris. The img tag is comparatively simple, needing only a src attribute containing an atlas reference for the image to lift and a colour attribute saying it should inherit the colour of the link.

Advanced Task Details
So  will have the subtask pause until the city is captured, but then the rest of the script will execute. You may have noticed that we then run the commands to add another task; when Locris gets captured, we don't add any reward immediately but add another quest. This one will be a more complicated one that gives the player a choice; either crush Locris beneath their boot and extract the wealth from their temples, or let them live unmolested under their benevolent rule [other player choices may or may not impact benevolence of rule].

As we touched on before, quests that give a task use waitsubtaskany and close after one of their subtasks completes. So in TaskPluckingThePetals.xml, rather than use  like we did in TaskFlowerOfItaly.xml, we'll use   and have multiple subtasks, then once the player completes one the other is gone. One of these subtasks will have its script be to go through with the looting, one will be not to do anything. In addition, we'll add a time limit to this task so that it'll auto-cancel if you take too long and the moment of conquest passes, since it stops feeling appropriate after Locris has been a loyal subject for years.

To add a time limit, you add a new tag to your task called "maxtime" and set its contents for the number of days you want the task to exist before time runs out. If you want, you can add a cancelscript tag which will run if the time runs out or if the player manually cancels. To make deciding not to loot a somewhat competitive option, we'll create a cancelscript which assimilates the city a little and gives it a population growth bonus, since they're keeping temples to gods of fertility and family.

For the actual subtasks, the positive subtask's script will be based mainly on the sleepuntil function. This function is similar to waitcapture or waitsubtasks, but waits until any wake event is triggered. A wake event can be triggered either by the game (this is actually how the waitcapture function detects there's been a capture) or via script; we'll be waiting for a custom-defined wake event called "PluckThePetals", and in our status string, add an anchor script to trigger it. Thus the subtask's script will wait until the player clicks the anchor in the subtask's status. We can add that anchor script to the string by wrapping the text with ])">____ (note that  ] can be used in lua to replace " when you need to make sure it doesn't think you're closing a different quote). After that fires, we'll give the player the gold from looting by calling the   command on the player faction and raise its hostility with all the greek factions by starting a loop through every faction, checking if it's greek and isn't the player, and if so raising its hostility through the inchostility function.

The other subtask just needs to make the cancelscript run, since it's just the player actively choosing not to do it. Strictly speaking, it doesn't need to exist since manual cancel options and time limits can have it run if the player would prefer this option, but it's better to make the player's choices obvious. Its status string can just be the default SubtaskReject string, which already has a placeholder for scripts we can fill with a request to cancel this task using the  command with the task's name passed in. The actual script for this subtask isn't too important if we do it correctly; all we want is for this subtask to stay paused for the entirety of the task and give the player a chance to cancel the task. I set it to call  with 7 passed in, which means it will wait out the time limit and can't unpause early.

Of course, both when we start the task and when it's completed, it would be nice to have popups tell the player what's going on, which is where talkinghead functions come in. Talking heads is our name for the advisors or ambassadors that sometimes come up to tell the player about something; they are brought up with a talkinghead function. There are a few talkinghead functions, but they're all pretty similar; the first parameter is the text that they say. You'll want to use the getstring command to retrieve the contents of anything you put in stringwrangler, getstring works by taking first "PH" then the name of the string you want to retrieve. The second parameter of the talkinghead functions describes who the advisor is; their name, their job title, what faction they work for, etc. You can get advisor data by retrieving a faction entity (usually through  or  ) and calling the getadvisor function with either "diplomat", "spymaster", "general" or "governor" passed in as its only parameter. The talking head will then pause until the player clicks okay, at which point the script will resume. The talking head functions that follow that format are,  ,  , and  , and the main difference between them is that rewardcity says "A NEW CITY HAS JOINED OUR FACTION!" while newobjective says "NEW OBJECTIVE", rewardgold takes a third parameter for an amount of gold to replace any "gold" placeholders with and news has none of these bells and whistles.

For the introduction, we'll use  and add a string with a player advisor pointing out that they could loot the temples, then just wait on any of the subtasks. We'll add a talkingheadnews to the accept script, have a Locrian point out that the player's committed an atrocity and everyone who loves the greek gods will hate them for it, and add a talkingheadnews to the cancelscript where one of the player's advisors state how grateful the people of Locris are.

Our final task looks like: PluckingThePetals QuestBurnLocris::PluckingThePetals::TITLE QuestBurnLocris::PluckingThePetals::DESCRIPTION false Subtask1 QuestBurnLocris::PluckingThePetals::DoIt trackself; local player = getplayerfaction; statusint("gold", 500 * player:numcities); sleepuntil("PluckThePetals"); player:givegold(500 * player:numcities); for faction in listfactions do                                if faction:getfactiongroup == "greek" and faction ~= player then faction:inchostility(player, 0.5); player:setfactionrelation( faction, "atwar" ); end end talkingheadnews(getstring("PH", "QuestBurnLocris::PluckingThePetals::YouDidIt"), getfactionbyname("Locris"):getadvisor("diplomat")); Subtask2 SubtaskReject trackself; delaydays(7); taskcancel([ ]); getentitybytag("locris"):changeassimilation(200); getentitybytag("locris"):setattribute("populationgrowth", 0.25, "mult"); talkingheadnews(getstring("PH", "QuestBurnLocris::PluckingThePetals::YouDidnt"), getplayerfaction:getadvisor("governor")); 7                talkingheadnewobjective( getstring("PH", "QuestBurnLocris::PluckingThePetals::Introduction"), getplayerfaction:getadvisor("diplomat") ); waitsubtaskany;

So that's how the quests from the Flower of Italy mod were put together.