Mission Architecture

Freeform discussion about anything related to modding Transcendence.
Post Reply
george moromisato
Developer
Developer
Posts: 2998
Joined: Thu Jul 24, 2003 9:53 pm
Contact:

It is difficult to implement missions in the game today. That is partly because the engine itself does not know about missions; instead, the modder has to implement missions as a set of events, dock screens, and script. I propose adding new types to the engine so that missions can be first-class objects (like stations, ships, and items).

GOALS
My goals in creating a mission architecture are as follows:

Encapsulation: I would like to create a "mission type" that entirely implements a mission. This would make it easier to add new missions without disturbing other missions. [To understand why this is hard today, think of how you would implement a mission to destroy an enemy ship. You need an event on the mission giver (the station giving the mission) to detect when the target is destroyed. But if you want to add a new mission to destroy a different enemy, you would have to modify the same event (making sure to test for which mission is currently active). I propose having each mission implemented by its own type; thus the event would be on the mission itself.]

Mission/Trigger Separation: It is common today to have the station that gives (triggers) the mission to also be the object that implements the mission. This means that if you want to add a new mission, you have to override the entire object. By separating missions into their own type we can break this connection. The implementation of a station should not know what missions it can give. Instead, it should enumerate appropriate missions with typFind (or something). This would allow, for example, mods to add new Commonwealth Fleet missions without having to override an entire CSC.

Standard Mission Interface: Because each mission is implemented separately, there is no way to determine (in a generic way) which missions the player has accepted. I propose adding functions to enumerate and manipulate the set of open and completed missions. This would allow the UI to show this information to the player; it would also allow mods to use this information.

Cross-System Missions: Finally, I propose that missions should be accessible across systems. At minimum this allows for a mission to properly handle the player leaving the system and returning. It also allows multi-system missions. [For example, a mission could require the player to travel to n different systems and destroy m different enemies.]

PROPOSED IMPLEMENTATION
In order to achieve the above goals, I propose the following concepts:

MissionType

A MissionType is a new design type that defines a type of mission. It is the encapsulation object that implements everything about the mission. For example, imagine something like:

Code: Select all

<MissionType UNID="..."
      name="Destroy Sung station mission"
      attributes="commonwealthMilitia"
      >
   <Events>
      <OnCreate>
         ; generate a random mission. E.g., pick a random Sung station to destroy in
         ; the current system.
         ; gMission is the mission object (use it to store mission details)
         ; gSource is the object giving the mission.
         ; Returns Nil if mission could not be created.
      </OnCreate>

      <OnMissionAccepted>
         ; Player accepted mission.
      </OnMissionAccepted>

      <OnObjDestroyed>
         ; If obj destroyed is the target, then mission accomplished.
         ; Call msnSuccess
      </OnObjDestroyed>
   </Events>

   <Language>
      <Text id="MissionBrief">
         ; Return string describing the mission
         ; gMission is mission object
         ; gSource is object giving the mission (e.g., station)
      </Text>
   </Language>
</MissionType>
Mission Objects

A mission object represents a mission that has been created. Some of these mission objects have been accepted by the player (and are thus "active"). Others are available but have not been accepted (these are "open" missions).

A station that wants to give missions can create a mission as follows:

(msnCreate {unid})

Where "unid" is the UNID of the mission. Note that the station could have randomly chosen a mission using typFind. msnCreate returns a mission object, which can be manipulated.

The station can describe the mission to the player as follows:

(msnTranslate gMission 'MissionBrief)

If the player accepts the mission, the station can call:

(msnAccept gMission)

The above will start the mission (calling OnMissionAccepted).

At any time an object may call

(msnGetProperty gMission 'status)

To get the status of the mission. The result is one of the following:

'open -> mission has not been accepted or rejected by the player
'active -> mission has been accepted by the player and is in progress
'rejected -> mission has been rejected by the player
'success -> mission has been completed successfully
'failure -> mission has failed

To control when a player completes a mission, script may call:

(msnSuccess gMission)

or

(msnFailure gMission)

You may get the list of missions as follows:

(msnGetMissions criteria) -> list of mission objects

The above is a very rough description of my proposal. I haven't yet begun implementation, so the eventual design might differ. I would love to hear feedback.
RPC
Fleet Admiral
Fleet Admiral
Posts: 2876
Joined: Thu Feb 03, 2011 5:21 am
Location: Hmm... I'm confused. Anybody have a starmap to the Core?

This is great. Can you elaborate more on how missions will be shown through a dockscreen?
I was looking through the Mission Framework posted by GiantCabbage and I was wondering if you'd go for the same implementation:
one dockscreen for the player to keep track of current missions (accessible through the ship interior screen)
one dockscreen for the mission giver to give missions/award for completed missions.
The example seems pretty solid for now, I can't really think of any improvements atm.
Tutorial List on the Wiki and Installing Mods
Get on Discord for mod help and general chat
Image
Image
Der Tod ist der zeitlose Frieden und das leben ist der Krieg
Wir müssen wissen — wir werden wissen!
I don't want any sort of copyright on my Transcendence mods. Feel free to take/modify whatever you want.
CYRUS KGABO
Militia Lieutenant
Militia Lieutenant
Posts: 109
Joined: Fri May 04, 2012 2:07 pm
Location: Franscistown,Botswana

I say go for it George.trans could use some new missions.
giantcabbage
Militia Lieutenant
Militia Lieutenant
Posts: 107
Joined: Thu Apr 07, 2011 9:05 pm

I've attached that latest version of the Mission Framework. It comprises two parts, the MissionFramework library which is mostly a set of hacks and functions which allow a virtual ship/station with the mission attribute to behave a bit like the proposed MissionType design type. The second is the MissionModule extension which contains a set of test missions.

The framework has been tidied up quite a lot since the last version, but the missions themselves have not changed too much. I think the player visible changes are:
Cross-system missions work correctly
Bounty hunter rewards from Corporate Enclave (not actually a mission though)
Cross-system recovery mission from Corporate Enclave
george moromisato wrote: A station that wants to give missions can create a mission as follows:

(msnCreate {unid})

Where "unid" is the UNID of the mission. Note that the station could have randomly chosen a mission using typFind. msnCreate returns a mission object, which can be manipulated.
I think it would be useful if missions could also be created automatically by the engine rather than needing to call msnCreate from a station. This could be done with something similar to the levelFrequency attribute for the StationType. In my framework I tried to hack this using the CreationCriteria static data and the onDock / sysEnter / sysCreate triggers.
This means you don’t have to reproduce the mission creation call in every station type. It also allows the MissionType to be used for scripted events. For example, in the framework I have a version of the Centauri Occupation which the framework attempts to create when a level 1-3 system is created. If there is an isolated commonwealth station in the system it can become occupied
george moromisato wrote: The station can describe the mission to the player as follows:

(msnTranslate gMission 'MissionBrief)
I found that sometimes I wanted to override the data in the Language tag, so my version (msfGetString mission key) will use the object data text{key} if available. However, this may not have been necessary if I'd written my Language tags a bit better.

I found it was useful to define string substitution tokens on a per-mission basis. So in the mission code I could do something like:
(msfAddStringSubs gMission "targetname" "qwerty")
Then I can use %targetname% in the Text elements.

Also, for some mission briefings I wanted to display two or three screens before displaying the accept / decline text. When I was retrieving object data I would just use a list of text strings, but that doesn't work with Language Text elements.
george moromisato wrote: 'open -> mission has not been accepted or rejected by the player
'active -> mission has been accepted by the player and is in progress
'rejected -> mission has been rejected by the player
'success -> mission has been completed successfully
'failure -> mission has failed
I found that sometimes I wanted to have a partial success outcome. But this may just be bad mission design - in some of the later missions I wrote I set the state to success and modified the success message and reward as necessary.

In the framework I also allowed missions to be active even if they hadn't been accepted (such as the Centauri occupation mission). But it's probably not necessary to distinguish open inactive and open active

Dockscreens:
Missions need someway to override dockscreens. I added <GetDockScreen> and <OnPaneInit> which are called for on all mission objects when the player docks i.e. similar to the Global versions but called on the object rather than the type. My versions are a bit messy as gSource is the mission object not the station object..

I’ve also tried using a function to register dockscreens for display e.g.
(msfSetDockScreen mission station [screen] [priority])
If screen is absent then a default dsMissionDone is displayed, otherwise screen should be a dockscreen UNID
The function is much more convenient for simple cases, but the events were needed for more complex situations.

Orders
As it is possible to have multiple missions active at once I added a function
(msfSetOrder mission order target text)
Which stores the data in the mission object, the order is then displayed to the player with:
(msfOrderPlayer mission)

SubMissions
In the sisters of domina mission I was experimenting with chaining simple missions together to create more complex ones. In this case there is a rescue captured miner in the middle of the sisters mission, but I had planned to make it randomly select a sub mission to occur in the middle.
However, all this really needs is some form of callback system which will call an event on the mission parent (either another mission or the station which created the mission) when the mission status is updated.
george moromisato wrote: The above is a very rough description of my proposal. I haven't yet begun implementation, so the eventual design might differ. I would love to hear feedback
I intended the framework to be part feature request (can we have something a bit like this) and part experimenting with a design for a MissionType. So most of my feedback is the framework itself - take what you like and ignore/modify the rest
Attachments
MissionFramework.zip
MissionFramework for 1.08f
(39.32 KiB) Downloaded 267 times
george moromisato
Developer
Developer
Posts: 2998
Joined: Thu Jul 24, 2003 9:53 pm
Contact:

giantcabbage wrote:I've attached that latest version of the Mission Framework....
This is hugely useful. I was hoping you would chime in since your Mission Framework has already explored this space. More detailed comments below...
giantcabbage wrote:I think it would be useful if missions could also be created automatically by the engine rather than needing to call msnCreate from a station. This could be done with something similar to the levelFrequency attribute for the StationType. In my framework I tried to hack this using the CreationCriteria static data and the onDock / sysEnter / sysCreate triggers.
This means you don’t have to reproduce the mission creation call in every station type. It also allows the MissionType to be used for scripted events. For example, in the framework I have a version of the Centauri Occupation which the framework attempts to create when a level 1-3 system is created. If there is an isolated commonwealth station in the system it can become occupied
This is a great idea. A levelFrequency attribute makes sense. Of course we would need some way to hook the mission screens to arbitrary stations as you mention below.
giantcabbage wrote:I found that sometimes I wanted to override the data in the Language tag, so my version (msfGetString mission key) will use the object data text{key} if available. However, this may not have been necessary if I'd written my Language tags a bit better.
The Language tag is currently evaluated as an expression, so you should be able to access object data. In the mission case, you will have access to the mission object. For example, you could do something like:

Code: Select all

<Language>
   <Text id="...">
      (cat "Your mission is to go to " (msnGetData gMission 'destination) " and blow stuff up.")
   </Text>
</Language>
giantcabbage wrote:I found it was useful to define string substitution tokens on a per-mission basis. So in the mission code I could do something like:
(msfAddStringSubs gMission "targetname" "qwerty")
Then I can use %targetname% in the Text elements.
I believe this can be accomplished as of 1.08e with subst:

Code: Select all

(subst "Go to %targetname% and blow stuff up." { targetname: (objGetName ...) })
And of course, you could use subst inside of a Language tag.
giantcabbage wrote:Also, for some mission briefings I wanted to display two or three screens before displaying the accept / decline text. When I was retrieving object data I would just use a list of text strings, but that doesn't work with Language Text elements.
This is a great point. How do you handle this in Mission Framework?

Maybe we can create a dock screen that automatically splits up text across multiple screens.

[One digression: I think there will still be a need for a Mission Framework even after I do this work. That is, I hope to provide some engine-native pieces (like MissionType) but I suspect that many people will want a friendlier interface and thus will still want to use a Mission Framework. Just like people want to use DSF. My goal isn't to replace Mission Framework but to put things in the engine when they are better there.

Thus the solution to the problem of using multiple dock screens for mission brief might be to continue to use Mission Framework (or a newer version that uses the native MissionType).]
giantcabbage wrote:I found that sometimes I wanted to have a partial success outcome. But this may just be bad mission design - in some of the later missions I wrote I set the state to success and modified the success message and reward as necessary.
I like setting the state to success and just modifying the message. I think this will work. I like a binary outcome so that we can report total outcomes in stats (e.g., "Completed 5 out of 7 Commonwealth Fleet missions" or something).
giantcabbage wrote:In the framework I also allowed missions to be active even if they hadn't been accepted (such as the Centauri occupation mission). But it's probably not necessary to distinguish open inactive and open active
Good point. I think it could be up to the mission to activate in OnCreate vs. OnMissionAccepted. For example, the Centauri occupation mission should activate in OnCreate (an explicit OnMissionAccepted is not needed). Gotta make sure I handle that case.
giantcabbage wrote:Dockscreens:
Missions need someway to override dockscreens. I added <GetDockScreen> and <OnPaneInit> which are called for on all mission objects when the player docks i.e. similar to the Global versions but called on the object rather than the type. My versions are a bit messy as gSource is the mission object not the station object..
Makes sense. I was initially thinking that I could rely on the Global versions, but I think your method is better. All the active missions should get called.
giantcabbage wrote:I’ve also tried using a function to register dockscreens for display e.g.
(msfSetDockScreen mission station [screen] [priority])
If screen is absent then a default dsMissionDone is displayed, otherwise screen should be a dockscreen UNID
The function is much more convenient for simple cases, but the events were needed for more complex situations.
Is this for accepting a mission or mission debrief or both?
giantcabbage wrote:Orders
As it is possible to have multiple missions active at once I added a function
(msfSetOrder mission order target text)
Which stores the data in the mission object, the order is then displayed to the player with:
(msfOrderPlayer mission)
I'm not sure I get this. Can you give me an example?
giantcabbage wrote:SubMissions
In the sisters of domina mission I was experimenting with chaining simple missions together to create more complex ones. In this case there is a rescue captured miner in the middle of the sisters mission, but I had planned to make it randomly select a sub mission to occur in the middle.
However, all this really needs is some form of callback system which will call an event on the mission parent (either another mission or the station which created the mission) when the mission status is updated.
Could this be done by auto-accepting new missions in the middle of an existing mission? I.e., perhaps in some event inside MissionType some script can call (msnCreate ...) and (msnAccept ...) to create and activate a new mission. When this submission completes, the original mission can continue (setting some state, or whatever). That implies that we need <OnMissionComplete> events for objects and missions.
giantcabbage wrote:I intended the framework to be part feature request (can we have something a bit like this) and part experimenting with a design for a MissionType. So most of my feedback is the framework itself - take what you like and ignore/modify the rest
This has been very helpful!
User avatar
digdug
Fleet Admiral
Fleet Admiral
Posts: 2620
Joined: Mon Oct 29, 2007 9:23 pm
Location: Decoding hieroglyphics on Tan-Ru-Dorem

I think this is a great step forward to a friendlier way to make missions and consequently adventures.

George, is MissionType going to support typCreate ? you know, we could have a number of mission skeletons in CDATA and then typCreate them and make random missions appear in stations.
george moromisato
Developer
Developer
Posts: 2998
Joined: Thu Jul 24, 2003 9:53 pm
Contact:

digdug wrote:I think this is a great step forward to a friendlier way to make missions and consequently adventures.

George, is MissionType going to support typCreate ? you know, we could have a number of mission skeletons in CDATA and then typCreate them and make random missions appear in stations.
Yes--there is no reason why it should not [If it doesn't work with typCreate, please file a ticket.]

Note, however, that you won't need to for the normal cases. A MissionType can create any number of random, unique missions. It is up to the OnCreate of the MissionType to create whatever mission it wants (just like the OnCreate of a ShipClass can add whatever devices it wants to a ship). But I can imagine more sophisticated cases that might require typCreate.
giantcabbage
Militia Lieutenant
Militia Lieutenant
Posts: 107
Joined: Thu Apr 07, 2011 9:05 pm

george moromisato wrote:
giantcabbage wrote:I found it was useful to define string substitution tokens on a per-mission basis. So in the mission code I could do something like:
(msfAddStringSubs gMission "targetname" "qwerty")
Then I can use %targetname% in the Text elements.
I believe this can be accomplished as of 1.08e with subst:

Code: Select all

(subst "Go to %targetname% and blow stuff up." { targetname: (objGetName ...) })
And of course, you could use subst inside of a Language tag.
Yes, the msf String functions are pretty much just convenience functions for doing things like:

Code: Select all

(subst "Go to %targetname% and blow stuff up." { targetname: (msnGetData gMission 'targetname) ...})
BTW - I'm not using subst at the moment as it strips out any unknown %tokens% and I want pass the strings through plyComposeString when they're displayed on the screen. There maybe a way around this, but I already had msfSubst (which ignores unknown %tokens%) so I didn't investigate much.

Also, using (objGetName target) in the Language element will be a problem if you decide to display that text element after destroying the target. I had this problem as I've added a "Mission Computer" screen to the ship interior screen for examining all the currently active / accepted missions.
george moromisato wrote:
giantcabbage wrote:Also, for some mission briefings I wanted to display two or three screens before displaying the accept / decline text. When I was retrieving object data I would just use a list of text strings, but that doesn't work with Language Text elements.
This is a great point. How do you handle this in Mission Framework?

Maybe we can create a dock screen that automatically splits up text across multiple screens.
This happens in dsMissionPicker which shows the list of missions available at the station and then displays the mission intro text with accept/decline similar to dsMission from StdDockScreens.xml. When the player selects the More Info option the dockscreen basically does:

Code: Select all

(setq gMissionText (msfGetString gMission "Intro"))
(setq mPage 0)
(scrSetDesc gScreen (item gMissionText mPage))
(setq mPage (add mPage 1))
If there are more pages then it shows a continue button, otherwise it shows the Accept/Decline options

george moromisato wrote: [One digression: I think there will still be a need for a Mission Framework even after I do this work. That is, I hope to provide some engine-native pieces (like MissionType) but I suspect that many people will want a friendlier interface and thus will still want to use a Mission Framework. Just like people want to use DSF. My goal isn't to replace Mission Framework but to put things in the engine when they are better there.

Thus the solution to the problem of using multiple dock screens for mission brief might be to continue to use Mission Framework (or a newer version that uses the native MissionType).]
In the particular case of the multiple screen mission briefs you may want to add it to dsMission or whatever new dock screen you use - the built in commonwealth mining missions are all multi-screen briefings. But in general - yes, once the MissionType exists I'll keep the framework as a collection of useful mission related functions and dockscreens etc.

george moromisato wrote:
giantcabbage wrote:I’ve also tried using a function to register dockscreens for display e.g.
(msfSetDockScreen mission station [screen] [priority])
If screen is absent then a default dsMissionDone is displayed, otherwise screen should be a dockscreen UNID
The function is much more convenient for simple cases, but the events were needed for more complex situations.
Is this for accepting a mission or mission debrief or both?
It is used for mission debrief and for displaying dockscreens during missions e.g. the sisters of domina mission. You could also use it for a Fleet delivery style mission. However, if you have native <GetDockScreen> and <OnPaneInit> events then the msfSetDockScreen is just an alternative to save a bit of typing and could be left in the framework rather than moved to the core engine.
george moromisato wrote:
giantcabbage wrote:Orders
As it is possible to have multiple missions active at once I added a function
(msfSetOrder mission order target text)
Which stores the data in the mission object, the order is then displayed to the player with:
(msfOrderPlayer mission)
I'm not sure I get this. Can you give me an example?
Say the player accepts a destroy enemy base within the MissionAccept event it does.

Code: Select all

(msfSetOrder gSource 'attack target "Destroy %target%")
(msfOrderPlayer gSource)
The player then goes and accepts another mission to deliver some cargo, which does:

Code: Select all

(msfSetOrder gSource 'dock destination "Dock at %target%")
(msfOrderPlayer gSource)
If the player was not paying attention they now have no idea which station they were supposed to destroy... However, the framework includes the Mission Computer in the ship interior which lists all the active mission and can call (msfOrderPlayer...) on any listed mission so the player can easily switch between orders for each mission.

george moromisato wrote:
giantcabbage wrote:SubMissions
In the sisters of domina mission I was experimenting with chaining simple missions together to create more complex ones. In this case there is a rescue captured miner in the middle of the sisters mission, but I had planned to make it randomly select a sub mission to occur in the middle.
However, all this really needs is some form of callback system which will call an event on the mission parent (either another mission or the station which created the mission) when the mission status is updated.
Could this be done by auto-accepting new missions in the middle of an existing mission? I.e., perhaps in some event inside MissionType some script can call (msnCreate ...) and (msnAccept ...) to create and activate a new mission. When this submission completes, the original mission can continue (setting some state, or whatever). That implies that we need <OnMissionComplete> events for objects and missions.
Yes, that's more or less how the framework does it at the moment.
I call msfCreateMission and (objFireEvent mission 'MissionAccept) from within the parent mission, then when the msfSetStatus function is called for the child mission it does:

Code: Select all

(objFireEvent (msfGetObjRef mission "mParent") 'SubMissionDone)
However the framework version is a bit fragile as most missions assume that the 'mParent data to refer the the station the mission is based around rather than the object that created the mission.
Post Reply