Development:Missions
Contents
EDITING MISSIONS
Introduction
Vega Strike has a powerful missions scripting interface that allows you to nearly modify Vega Strike at whatever level you choose, from the AI scripts (and the physics thereof) to the missions... to the overall plot and goal of the game.
Basic XML integration
First let me start by explaining how to run a python class... in this case we start with the prebuild "privateer.py" script to run our mission. Open up mission/explore_universe.mission First come the variables that designate how the mission should normally start. By normally I mean if they don't have any save game present.
Variables Section
It loads Crucible/Cephid_17.system as the star system (which is in XML and stores all present planets) After this, comes the actors in the mission, the flightgroups of fighters. If a mission has more than one player, then each player is the leader (first ship) in each respective flightgroup.
Notice that so far there are a lot of values you don't see for long in the default vegastrike game. Credits, System, first flightgroup fighter, difficulty--these are all initial values but can change.... since there is specified a savegame. The savegame makes the mission more of a campaign type mission where things may be saved to disk and reloaded at a later point. If no savegame variable is specified (this is where the autosave goes) then the mission will not save and will be a one time play mission.
<mission> <settings> <origin planet="earth" x="0000" y="100" z="000.0"/> </settings> <variables> <!-- <var name="continuousterrain" value="continuousterrain.txt"/>--> <var name="credits" value="13500"/> <var name="defaultplayer" value="blue"/> <var name="mission_name" value="Privateer Mission" /> <var name="difficulty" value=".05"/><!--starting difficulty--> <var name="system" value="Crucible/Cephid_17" /> <var name="description" value="Like, as if." /> <var name="savegame" value="explore_universe_difficulty" /> </variables>
Flightgroup Section
The rot flag is ignored, and the position specifies the x,y,z coords of the Shlimazel flightgroup, which is of type llama.begin.
<flightgroups> <flightgroup name="Shlimazel" faction="privateer" type="llama.begin" ainame="default" waves="1" nr_ships="1"> <pos x="-95124024.543917" y="412089916.256812" z="-110779667.398050"/> <rot x="180.0" y="180.0" z="180.0"/> <order order="tmptarget" target="omikron"/> <order priority="0" order="superiority" target="enemy"/> <order priority="1" order="bomber" target="omikron"/> <order priority="2" order="escort" target="blue"/> </flightgroup> </flightgroups>
Python Section
Future note: If you wanted to add a campaign to the privateer mission you would most likely modify privateer.py to have a campaign module get loaded.... so you wouldn't necessarily need to modify explore_universe.mission just to add a campaign... lets dig further into the meat of the scripting.
If a mission has python tags...then it is a python mission and may have some embedded python in it. In this case the python makes a new python object of type privateer in the privateer.py module in the modules/ directory.
<python> from privateer import privateer my_obj=privateer(8000,40000,500,3,2,.6,.25,.1,400000,2000) </python> </mission>
You have to be careful about newlines, etc in the XML, but it shoudl usually work just dandy. You notice that the mission may pass in arguments to the privateer module that can make slight changes in teh gameplay. This allows many cargo missions to use the same module in order to change the parameters of the cargo missions. In this case the values I have selected appear to work relatively well.
Python Inheritance In Missions
Ok lets dig further into the privateer.py module:
It is a very short module written in more or less pure python... I say more or less for a very specific reason: it inherits from a C++ class!
Director is a C++ class exported to python. All missions must inherit from director.... in this case the mission is very short as it uses other (pure python) modules. random_encounters.random_encounters, difficulty.difficulty and ,trading.trading all do NOT inherit from C++ as this class takes care of the C++ inheritance.
The constructor __init__ takes the arguments that we passed in from the XML file and passes them on to the various submodules. The only really really important thing you do in the constructor is call
Director.Mission.__init__ (self)
If you do not do this, Vegastrike will not load your module and will silently fail (this calls the C++ constructor, which actually triggers the binding of your module to the python mission).
Each mission module must have an execute function so that something can happen every physics frame. In this case it just calls execute on all the submodules... not very interesting.
Missions also may have 2 functions:
Pickle (self) which returns a string with a XML mission file that will load the mission properly as well as a string representing the mission in serialized form... if the mission were reloaded that mission would be loaded and its corresponding python would be run. Then Unpickle (self, str) would be called (with str being a string) and the expectation that the mission would be reloaded. data/newmission.py has an example of this. You may also choose to save data in a float by float format as follows (the advantage to this format is that it is saved on a PER PLAYER basis... so that each player may have a different value):
There are 4 functions at your disposal for this older way to save missions:
This function adds the key for later retrieval (even after VS quits ) and pushes the float to the end of the hashtable indexed by stringkey:
myindex=Director.pushSaveData(int whichplayer, stringkey,floatVal);
This function modifies a index in a key that has already been pushed back at one point:
Director.putSaveData (intWhichPlayer, stringkey, myIndex, floatVal);
This function gets the number of float values for a given player:
Director.getSaveDataLength (int whichplayer, string key)
This function gets save data saved at that number:
Director.getSaveData(int whichplayer, stringkey, int num)
You can make up stringkeys and save only 1 float per key if you wish... each key is allowed to have an vector of possible values. Use as you wish! For the privateer mission I save things with key 31337ness and the one value in there is the difficulty that I write out.... If a campaign is more global rather than player specific you may just choose to pickle it...however if something is on a per-player basis, it may be best going in these SaveData float vectors.
With the exception of AI modules from here on out it's straight python. I will go over some of the functions you can call inside Vega Strike in a minute.
Python Inheritance with AI Scripts
Let me talk about AI scripts briefly.
You'll notice a nice AI script named printhello.py in the data/printhello.py. It is very similar to a mission with a few notable differences.
- First of all the AI class you make must inherit from the class VS.PythonAI.
- Secondly instead of having an __init__ function it must have an init() function. The reasons for this have something to do with how inheritance works from C++, but this init function takes in self of course and the unit to which thie AI script belongs. __init__ may start off some AI scripts from the get go, or you may wait until you execute each frame. in this case printhello loads the XML script ++turntowards.xml (which is the hard coded C++ equivalent to ai/scripts/turntowards.xml). After loading that AI script it replaces its parents order with the last AI script, in this case the XML script. In the Execute function this AI script calls Execute on the higher level (which takes care of responding to communications) and then prints h to the console for no apparent reason.
- Lastly and most importantly, the AI script makes a new class for itself. So when the C++ code executes printhello.py it will call the C++ constructor which will take care of binding it. So when you make a new unit like so:
VS.launch (fgname,type,faction,"unit","printhello.py",nr_ships,nr_waves,vec,logo)
it will have this python AI attached to it. However if you are too lazy to write AI's you can use the most excellent default AI
VS.launch (fgname,type,faction,"unit","default",nr_ships,nr_waves,vec,logo)
Summary about python-Vegastrike class relationships
- Missions must inherit from Director.Mission
- AI's must inherit from VS.PythonAI
- Missions must have an __init__ function with any number of arguments that calls Director.Mission.__init__ (self); to trigger the C++ binding
- AI's must have an init(self, un) function with those exact 2 arguments... the second one is the parent Unit. I don't recommend you save the unit currently--I'm not sure if it will garbage collect the unit in that case... just call the self.GetParent() function as the AI script.
- Both AI's and Missions may have an Execute(self) function that does somethign each frame depending on the disired result.
- Missions may have a Pickle and Unpickle function in order to save and load the state of a campaign (this can be incredibly complex!)
Python - Vega Strike Bindings
Writing Add On Adventures
Honestly you merely need a basic understanding of python in order to craft your own add on adventures. But I decided to write a framework for consistently and speedily adding adventures to the general exploration of Vega Strike.
I will define "adventure" as follows: A minature mission that gets triggered by a player who goes in its system. This mission may either be persistent or nonpersistent. A persistent mission will reload the next time the triggering player launched it until the player beats the mission.
THE "QUEST" CLASS: THE ACTUAL QUEST LOGIC GOES HERE
The quest, quest_factory and lastly the adventure modules take care of most of the dirty work. All an quest class has to do is the following:
class quest_my(quest.quest): def __init__ (self): #do anything you need here def Execute (self): #do anything you need here...return 1 if you wish to execute again, 0 if you wish to #terminate yourself
That's it.... though there are some useful functions you may wish to call in your superclass
self.removeQuest() #this prevents your player from EVER encountering the quest again in his life self.makeQuestPersistent() #this causes the quest to be loaded the next time your player jumps in after rebooting vegastrike
Generally before returing 0 (terminating self) in a mission you may wish to make the quest either persistent (comes back next time he starts game) or removed. Though you could also leave the quest the way it is and the next time the player goes to the system after rebooting vegastrike he'll get the same quest again :-) But in order for your quest to be complete you must have two more components
THE "QUEST FACTORY" CLASS: A CLASS THAT RETURNS THE QUEST NEEDED
Each quest must have a factory that inherits from quest.quest_factory. and must define both an init, and a create function. Optionally there's a conditional function called precondition.... That returns a boolean whether or not the quest is ripe for creation (when a player encounters it in its native system that is)
class quest_my_factory (quest.quest_factory): def __init__ (self): quest.quest_factory.__init__ (self,"quest_my") def create (self): return quest_my() def precondition(self,playernum): return Director.getSaveData(playernum,"talked_to_sandra",0)>0
The factory must have 2 and can have all 3 of the above functions:
- is the init function (__init__) .. you must call your superclass's init with your name.
- is the create function... this must simply return the quest class you painstakingly created above
- is the precondition. This is optional (by default it returns 1...always true)
- the precondition can look in the save variables and see if you have done a task (set by some other quest perhaps) and only then return 1... :-) in that case when a player encounters it for the first time it will check the precondition before launching the quest. Note the precondition does NOT get checked once the mission has been turned into a persistent mission. This is because the mission already made the decision to make itself persistent I don't expect many missions to need to be persistent. I expect more lighthearted adventures that don't drag out if a player quits, etc.
Anyhow... a good example is the persistent quest known as the quest_drone (quest_drone.py) That quest checks to see if you're near an unknown_derelect and if so it launches the badguy drone.... it makes the drone jump and follow you wherever you go until it is destroyed...it is persistent and launches the drone every time you rerun the game. Luckily if you're close to the derelect chances are you'll find the gun that you can use to kill the drone.... which brings me right smack into my next point: location location location!
THE LOCATION AND PERSISTENCE OF A GIVEN QUEST (i.e. HOOKING IT INTO privateer.py)
adventure.py has the master list of all possible quests in vega strike. Right now there are two (but one is stupid and will be removed soon enough...that's the default one)
adventures = {"gemini_sector/delta_prime":quest_drone.quest_drone_factory(), "sol_sector/celeste":quest.quest_factory("default_quest",0)}
persistent_adventures = [[quest_drone.quest_drone_factory()] These are the only 2 lists that will ever need to be changed in the modules that are already there.
- "adventures" contains a map of location to adventure. Note that there can be only 1 adventure per location (the players need to be encouraged to actually EXPLORE)
- The second list lists all possible "persistent adventures" (though they are only loaded from if the given adventure has gotten around to calling self.makeQuestPersistent)
Anyhow just adding more cool missions to this list will make it a lot more fun to explore around and encounter strang epeople, strange news, and a bunch 'o cash :-P
Note that we should eventually make the news reports line up with the quests... :-) I have some ideas how to do that. Basically you can call
VS.IOmessage ("game","news","A drone was sighted in the delta prime...blah blah blah")
and then people will see it in the game when they click on GNN likewise if you send it to
VS.IOmessage ("game","bar","A drone was sighted in the delta prime...blah blah blah")
then you can hear it when you talk to the bartender :-)