Development:Quests

From VsWiki
Jump to: navigation, search

Writing Quests and Adventures

This page explains the structure of a quest file, its integration and internal workings and is useful for campaign/mission/quest programmers.

A quest is the same as an adventure and is a series of events that happen around the player when he is in a specific system. Quests can be run independently of campaigns or missions, though they can also be part of them. It is a miniature mission that gets triggered by a player who goes into the system in which the quest is defined. A quest may either be persistent or non-persistent. A persistent quest will reload the next time the triggering player launched it until the player beats the mission.

Vega Strike has a lot of different ways of doing one thing. Just pick the most appropriate one for the job. It supports campaigns, missions, independent fixers, quests, adventures, etc., all of them having their specific ways of hooking them up into the game. You merely need a basic understanding of python in order to craft your own add on adventure. But I decided to write a framework for consistently and speedily adding adventures to the general exploration of Vega Strike.

Quest Framework

The python code for quests resides in the directory data/modules.

The "QUEST" Class: The Actual Quest Logic

Each script requires at least those 2 elements:

  • def __init__ (self): - here you initialize your script variables
  • def Execute (self): - this part is executed each simulation frame; this is the place for writing the actions of the adventure.

The quest, quest_factory and lastly the adventure modules take care of most of the dirty work. This is how your basic empty quest class should look like (replace questname with an individual quest name):

import quest
class quest_questname (quest.quest):
    def __init__ (self):
          # initialize your variables and units 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
                           # restarting vegastrike

Quests are run inside of "random_encounters.py". If it returns 1 or True, it will keep going. If it returns 0, False or None (python returns None if you don't say what to return) then the quest will stop Execute()ing and be deleted until you jump into that system again or reload.

Generally, before returning 0 (terminating self) in a mission you may wish to make the quest either persistent (comes back next time game is started) or removed. Though you could also leave the quest the way it is and the next time the player goes to the system after restarting 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

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). In order to integrate the quest into Vega Strike, you'll need to also include this factory class (again, replace questname with an individual quest name) into your quest file:

class quest_questname_factory (quest.quest_factory):
    def __init__ (self):
       quest.quest_factory.__init__ (self,"quest_questname")
    def create (self):
        return quest_questname()
    def precondition(self,playernum):
        return Director.getSaveData(playernum,"talked_to_sandra",0)>0

The factory must have 2 (init and create) and can have all 3 of the above functions:

  • The init function (__init__). You must call your superclass's init with your quest name.
  • The create function. This must simply return the quest class you painstakingly created above.
  • The precondition. This is an optional condition (by default it returns 1...always true). It can be the evaluation of a save variable and should return 1 (true) for the quest to run.
    • 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 derelict station and if so, it launches the bad guy 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 derelict station, 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!

Hooking The Quest, Location, And Persistence

adventure.py has the master list of all possible quests in Vega Strike. There are only 2 lists that will ever need to be changed in the modules that are already there. In the adventures.py file, add the information required to initiate the quest (replace questname with an individual quest name) to the adventures list including the sector/system names where the quest should be initiated and the call to your quest factory class:

import quest_questname
import quest.drone
adventures = { 
   "Crucible/Cephid_17":quest_questname.quest_questname_factory(),
   }
persistent_adventures = [quest_drone.quest_drone_factory()]
  • adventures contains a map of locations and the respective adventures. Note that there can be only one adventure per location (the players need to be encouraged to actually EXPLORE).
  • The second list (persistent_adventures) lists all possible "persistent adventures" (duh; 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 strange people, strange news, and a bunch 'o cash :-P

The Executor Class: Quest Testing Using A Mission File

In order to speed up the scripting and testing for quests and adventures that are not already inheriting form the Director.Missions, you can create a mission xml file and call the adventure file directly. In order for it to work properly, it must inherit the Director.Mission class *and* call its constructor. Unfortunately quests must inherit from the quest class, so you need to use a wrapper class that takes an instance of a class as an argument:

import Director
class Executor(Director.Mission):
   def __init__(self, classesToExecute):
      Director.Mission.__init__(self)
      self.classes = classesToExecute
   def Execute(self):
      for c in self.classes:
         c.Execute()

This is a very simple class, and it's similar to what privateer.py does. I'm just making it general by passing the list of classes as an argument. You don't need to change any code here, just paste it into your quest file as it is.

Then, in the mission xml file, you need to call that executor class by adding the python section:

   <python>
from quest_tutorial import Executor, quest_tutorial
tmpvar=Executor([quest_tutorial()])
   </python>

With this, you can start the quest directly from the command line with:

./vegastrike test/questname.mission


Now, that you have the main classes set up, and the quest is hooked up to the game, you are ready to start writing the quest contents.

Quest Content

Since the Execute() function is called in each every frame, I want to discuss some useful ways to control when things should happen in your quest.

Controlling the Flow

Sample Quests

Sample 1: The Docking Dispute quest_dispute.py is a mostly-working custom mission that has been written, and heavily commented to help describe what is going on. The premise is this: When the player enters a system, and gets near the selected base... the player will overhear a dispute between two ships trying to dock first. The player can choose to help either ship, or just go on his way. There are rewards and consequences for each action.

See also