Freedom Force – Scripting A Mugging II

This will be an extension of the Scripting A Mugging tutorial, so make sure you’ve done it first.

In this tutorial we will further modify the script so that it is usable in multiple missions and so that using it in a mission is very simple.

To start with you’ll need the mod created in the first Scripting A Mugging tutorial. Since we will be creating a new mission you only actually need the first do nothing script so just the zip file will do if you haven’t done the original or have deleted it.

First A New Mission

Run FFEdit on the mod and go to the Mission tab, create a New mission just as you did for the first tutorial – ‘Mug_Test2’ is the name I used, but feel free to use a better name if you want.

Now pick a Layout File, for the purposes of his tutorial the simplest one to use is the blank_city layout, just like in the first tutorial

Give it a name, say ‘Mugging Test 2’, and click the ‘Edit In-Game’ button to add our characters.

This time we will have two mugging scenes for Minute Man to hopefully save the day with. We had better give the objects better names this time so that the scripting is easier. I used the name ‘victim_a’ for the civilian in the first mugging and ‘mugger_a_1’, ‘mugger_a_2’, ‘mugger_a_3’, ‘mugger_a_4’ for the muggers and ‘mugging_a_area’ for the positional marker. Replace the ‘a’s with ‘b’s and you’ve got the names I use for the second. I made the first mugging be 4 thug_with_gun’s against a single civilian and the second one by 4 thug_with_bat’s against a single civilian. I also surrounded the civilian with the thugs – this makes it much harder for the player to actually make the rescue. Of course you can arrange your’s however you like, but if you want it to look like mine, then it should look something like this: screen shot

Create the mission.py file to be the do nothing one we started with in the previous tutorial:


from cshelper import *
from js       import *
from ai       import *

def OnPostInit():
	print 'postinit'
	setMission(1) 

def OnPrecacheObjects():
	print 'precache'


def OnMissionWon():
	print 'Mission Won'
	
		
def OnMissionLose():
	print 'Mission Lost'

If you want to make sure your setup is similar to mine, you can download mine: Mug_Test2.zip (32KB). You should extract the contents of that zip file into the directory containing your mod’s Missions (in my case that is ‘D:\Program Filed\Freedom Force\Mug\Missions’, but it will probably be different for you).

Creating A New Module

This time we will put the code that runs the mugging scene in a separate file, and then import that file in the missions.py script. That way we can use the same code in as many different missions as we want.

This is where we come across our first problem. ‘cshelper’ is an example of a module that comes with Freedom Force. You can find it in the ‘Data\Missions\scripts’ directory in the missions.ff file (which is a zip file remember – you have probably already unzipped it if you are playing with FFEdit, since you can then edit (after copying usually) the built in missions – and look at the mission.py files for them to learn from).

So creating a ‘scripts’ directory in the Missions directory of our mod and putting our module in it seems like would be a solution. However, I found that Freedom Force didn’t see modules in that directory.

Putting your module code in the ‘Data\Missions\scripts’ directory in the Freedom Force install directory works (that’s what the EZ Danger Room mod does). However, we don’t want to modify anything outside of our mod directory.

You may have noticed that when you first ran the mission to test it a ‘temp’ directory got created which contains a copy of cshelper.py, mission.py, and prestige.py. We can put a module in that directory and it gets seen by the game. However, the name ‘temp’ implies that the game might delete files from that directory that it isn’t using.

For now I suggest creating a ‘scripts’ directory in the Missions directory of you mod and putting your modules in it. And then also putting a copy of them in the ‘temp’ directory, so that the game can find them. Hopefully, someone will be able to find a better solution later, but for now at least this works.

What is a Module Anyway?

A module is just some python code which you can import into your script. Placing your code in a module has many advantages. You can reuse the same code in different scripts without having to copy and paste all of it (and then change all the copies when you fix a bug). Modules also have their own namespace so that means you can use functions and global variables with the same names in different modules. Hopefully people will write Python modules for Freedom Force that you can then use to do things in your own mods.

Get On With This Module…

The module we are about to create will make it easy to setup muggings. The way it will work is the script will list the objective name, the mugger objects, the victim objects, and the area marker. The module will then handle everything else when the script calls an initialise function.

We will make the module so that the objective is optional, since someone might want a mugging scene which isn’t actually an objective but just part of a level. We will also make the existence of the victims optional, that way the module can be used to make a ‘KO this group of baddies’ objective as well.

What we won’t be doing is supporting cut scenes and speech, hopefully that will be added in the next tutorial (I haven’t tried the cut-scene stuff yet, so assuming I get time to play with it I’ll write something up).

Hurry up with the Code

OK, instead of ranting on let’s have a look at the code. To use this you’ll have to put it in a file named ‘mugging.py’ in the ‘Missions\scripts’ directory and the ‘temp’ directory:


# A mugging managing module.
# Sets up scenes in which one group of objects attacks another when a hero
# comes within range. If all the attacking objects are KOd before any of the
# attacked objects are KOd the scene is considered won by the player.
#
#Usage:
# import * from mugging
# # ...
# mugging_data =(('objective_name',('mugger1','mugger2'),('victim1','victim2'),'marker'),
#                ('objective_name',('mugger1','mugger2'),('victim1','victim2'),'marker')
#               )
# # ...mugging_data
# mugging.Init_Muggings()
#
#Notes:
# mugging_data must be in the ff module
# The objectives must already be registered (the module will activate and complete
# or fail them).

from cshelper import *
from js       import *
from ai       import *
from ff       import *


def mugging_all_kod(objects):
    for object in objects:
        if Object_IsAlive(object):
            return 0
    return 1

        
# This will get called whenever a mugger gets KOd		
def mugging_on_mugger_ko(event):
    mugging_index = int(event.user)
    objective = ff.mugging_data[mugging_index][0]
    muggers = ff.mugging_data[mugging_index][1]
    if mugging_all_kod(muggers):
		#check if the objective is defined
        if objective != None:
            #check if the objective is already done
            if not(Objective_IsComplete(objective) or Objective_IsFailed(objective)):
                # if not then set it to complete
                Objective_Complete(objective)

def mugging_on_victim_ko(event):
    # if the mission isn't over it is failed now
    mugging_index = int(event.user)
    objective = ff.mugging_data[mugging_index][0]
    if objective != None:
        #check if the objective is already done
        if not(Objective_IsComplete(objective) or Objective_IsFailed(objective)):
            Objective_Fail(objective)

def mugging_on_enter_marker(event):
    # a hero has entered the marker
    # if the scene is already activated do nothing
    mugging_index = int(event.user)
    objective = ff.mugging_data[mugging_index][0]
    muggers = ff.mugging_data[mugging_index][1]
    victims = ff.mugging_data[mugging_index][2]
    if objective != None:
        Objective_Activate(objective)
    # wake up the muggers and victims
    for mugger in muggers:
        AI_SetEnabled(mugger,1)
        #make the muggers attack the victims
        if victims != None:
            addGoalToAI(mugger,KillObjectsTuple(tuple(victims)))
    if victims != None:
        for victim in victims:
            AI_SetEnabled(victim,1)
        

def Init_Muggings():
    for mugging_index in range(0,len(ff.mugging_data)):
        objective = ff.mugging_data[mugging_index][0]
        muggers = ff.mugging_data[mugging_index][1]
        victims = ff.mugging_data[mugging_index][2]
        marker = ff.mugging_data[mugging_index][3]
        # setup to catch the muggers' deaths
        for mugger in muggers:
            AI_SetEnabled(mugger,0)
            regDeath(mugger,'mugging_on_mugger_ko',float(mugging_index))
        # setup to catch the victims' deaths
	if victims != None:
        	for victim in victims:
            	AI_SetEnabled(victim,0)
            	regDeath(victim,'mugging_on_victim_ko',float(mugging_index))
        #setup to catch the hero entering the marker
        regHeroMarkerEnter(marker,'mugging_on_enter_marker',float(mugging_index))

That’s a reasonable amount of code, but isn’t too different from the previous tutorial’s final code.

An Explanation

‘mugging_all_kod’ is a short function which is passed a list of object names, ‘and simply return 0 if any of them are still alive, and 1 if they are all KOd or non-existent. The reason for it is to demonstrate another option for keeping track of whether all the muggers have been dealt with, which we’ll see in the next function.

‘mugging_on_mugger_ko’ is the function that will be called when one a mugger is KOd. Notice, we use event.user, this is the user data which you can register when you create an event callback function, in our case the code is supposed to deal with multiple muggings so we use is to present the index of the mugging the mugger who just got KOd is part of. We convert it to an int, because all use data is always a float in Freedom Force, but we want an integer. We then use that index to get the objective name and the list of muggers from the global data the mission script has to provide. If all the muggers are KOd we check if an objective exists and if so complete it if it hasn’t already finished.

‘mugging_on_victim_ko’ is the function that gets called when a victim is KOd, again the user data is used to store the mugging index. This function just retrieved the objective and marks it failed if necessary.

‘mugging_on_enter_marker’ is the function that gets called when a hero enters the area of the marker associated with the mugging scene. It’s similar to the code in the previous tutorial, except it has to retrieve the data from the mugging_data global and check if there are any victims.

‘Init_Muggings’ is the function that will be called from the mission script. It extracts the data from the global and sets up all the event handlers as well as turning off the AI in the muggers and victims. Notice how it specifies the user data to be passed to all the event handling functions.

One annoying thing about the module is that the mission script must import it as ‘import * from mugging’ and not just ‘import mugging’. That’s because I can’t find a way to set an event handler to a function that is in a module, so the functions have to be imported into the place where they can be found. All the functions other than Init_Muggings are prefixed with mugging_ so that hopefully they won’t clash with functions the mission scripter used.

Using the Module

Once the module code is written and placed in the mod ‘temp’ directory as the file ‘mugging.py’ (and also put in ‘Missions\scripts’ since keeping something just in a temp directory doesn’t sound safe to me). We can then modify mission.py to use it. Feel free to download my version of the module code: mugging.py.

We could do that for the mission we created at the beginning by using the following script:


from cshelper import *
from js       import *
from ai       import *

from mugging import *

mugging_data = ( ('secondary_1',
                   ('mugger_a_1','mugger_a_2', 'mugger_a_3', 'mugger_a_4'),
                   ('victim_a',), 'mugging_a_area'),
                 ('secondary_2',
                   ('mugger_b_1','mugger_b_2','mugger_b_3','mugger_b_4'),
                   ('victim_b',), 'mugging_b_area'),
               )


def OnPostInit():
	print 'postinit'
	setMission(1)
	registerObjective('secondary',1)
	registerObjective('secondary',2)
	Init_Muggings()

def OnPrecacheObjects():
	print 'precache'


def OnMissionWon():
	print 'Mission Won'
	
		
def OnMissionLose():
	print 'Mission Lost'

Try is and check that it works. The second objective will have a very silly name because there is no second secondary objective in the Freedom Force mission 1. How to make the objectives be what you want is documented in the Scripting documentation that comes with FFEdit. You can download my mission.py: mission.py.

That’s All For Now

And that’s it. I suggest you try creating some mugging scenes in another mission. You can create a scene in which a group of enemies are awoken when the hero enters an area, and an objective completed when they are all dispatched by using a mugging_data like:


mugging_data =( ('secondary_1',('bad_guy1','badguy2),None,'area_marker'), )

Another thing to be careful of, is that if you want a list of one item (like the victim lists in the example, and the mugging_data above) then you need a trailing comma. In other words: (‘victim’,) is a list containing one element. Whereas: (‘victim’) is just a string. I got that wrong numerous times when creating this tutorial…