Level Editing: Scripting

Talk about Severance Blade of Darkness modifications and maps here. No tips or tech support questions please, use the forum above. Note that the game is rated 18 so some content may be unsuitable for younger readers.

Moderators: prospero, Ade

User avatar
Ancient Dragon
Posts: 1816
Joined: Wed Nov 21, 2001 1:42 am
Location: United Kingdom

Post by prospero »

Tut14. Gates

Unlike Doors, opening Gates use a movable object rather than a sliding sector. You can use any object for this purpose, but practically all the RAS gates use the "Rastrillo". (There are various flavours.) I'll stick to using this object in this tut.

Use the EBrowser to place the Rastrillo in the closed position in you doorway. It is up to you if you want to make a channel in the doorway
for the gate to slide into. It will happily slide into a solid wall but it looks more convincing if it has a channel. Save the position/orientation in a temporary file.

You can put all your gates in the same file as Doors. First make some
clanky-type sounds:

Code: Select all

snd_slidegate=Sounds.CreateEntitySound("..\\..\\Sounds\\rastrillo.wav", "GateSlideSound")

snd_hitgate=Sounds.CreateEntitySound("..\\..\\Sounds\\golpe-metal-mediano.wav", "GateHitSound")

#then create the gate:
# take the Position/Orientation/Scale from the temp file you saved with EBrowser
gate1.Frozen=1 # dunno what this is for
gate1=Sparks.SetMetalSparkling(gate1.Name)# This makes sparks if you hit the gate.
gate1din=Objects.CreateDinamicObject(gate1.Name)# Assigns data to handle moving.

You now need a couple of functions in DefFuncs.py:

Code: Select all

def OpenGate(gate,d1=2200,d2=600):

def CloseGate(gate,d1=2200,d2=600):
These functions will handle all your gates. I have put in default args for the distance of travel. These should wotk fine for the majority of gates but if you have an extra big one you can pass args in the function call to alter them for that particular gate.

You can use a lever to open in the same manner as doors, but you must tell the functions which gate you want to open:


to alter the displacement use:

gate1.OnTurnOnArgs=(gate1din,3000,600,)# adjust values until it moves far enough.

For other different objects you can make other functions based on these, customised to the object in question.


Locks work like levers, but you need a KEY.

First, pick one of the lock plates and position it with the EBrowser.
Generate script to a temp file.

Create lock with this code:

Code: Select all

#make the key first

darfuncs.SetHint(gate1key,"Key") # This name will show onscreen
Stars.Twinkle(gate1key.Name) # this is optional

# You can place the key anywhere with EBrowser or give it to an enemy.
If you give it to an enemy, create it at 0,0,0 and omit the Orientation setting. Use Actions.TakeObject("NameOfEnemy","NameOf Key"). Bear in mind that the key must be created before the enemy can take it.

#Make the lock
darfuncs.SetHint(gate1lock.obj, "Lock")# put appropiate name here

Use the settings you saved in the temp file. (Position),(Orientation) The last arg is the Scale. Unlike the Lever code the Scale will have effect.

That about covers the subject.

A word on Elevators. They work in much the same way as Doors and Gates, except yopu ride up and down on them. They can be slide sectors or dinamic objects or sometimes a combination of both. You just have to give them greater displacement settings. They can get complicated if you want to be able to 'call' them from different floors, especially if you want them to stop at multiple levels. It can be done but I won't go into all that at the moment.[;)]

User avatar
Ancient Dragon
Posts: 1816
Joined: Wed Nov 21, 2001 1:42 am
Location: United Kingdom

Post by prospero »

Tut15. Death of Enemies.[}:)]

Sometimes you need to call a function when an enemy is killed. All chars have a function in their data that processes their deaths: launches death animation, drops their weapons, etc. You need to tap into this function. (It is called an ImDead func).

After enemy is created, add this after the creation code:

Code: Select all

ork1.ImDeadFunc= Ork1DED

# In DefFuncs:

def Ork1DED(entity)
    if me.Data.OldImDeadFunc(entity)
    print "Ork1 is dead..hahahahaha. Opening door"

When "Ork1" is killed, "Door1" will open.

Little mention here about Scheduled functions. Sometimes you want to delay function call.

Bladex.AddScheduledFunc(Bladex.GetTime() + 5.0, door1.OpenDoor,())

This will delay the call for 5 secs.

If the delayed function has arguments, put them in the (). (It is called a 'tuple' in Python.)

Bladex.AddScheduledFunc(Bladex.GetTime() + 5.0, OpenGate,(gate1din,))

** don't forget the , after the args. (gate1din)) don't work.

But say you have group of enemies (3 Orks?) in a room. You can't get out and you have to slay them all before the exit door will open. This type of scenario occurs lots in the game. Ok, you use the ImDead
code above. BUT how do you know which one will die last? You can't rely on them being killed in order. The answer is use a variable as a counter:

Code: Select all

Group1Orksdead = 0  # declare you var

def Group1OrksDED(entity):
    global Group1Orksdead # declare the var as a global var
    if me.Data.OldImDeadFunc(entity)
    Group1Orksdead = Group1Orksdead +1
    print "Another one bites the dust"
    if Group1Orksdead ==3:
         Bladex.AddScheduledFunc(Bladex.GetTime() + 5.0, door1.OpenDoor,())
         print "Group1 Orks all dead - open exit door"
Make it so that all three Orks have the Group1OrksDED func assigned to the ImDead routine. As each dies the function is called and the value of the Group1Orksdead var is increased by 1. As the last Ork is killed (don't matter which one) this value is increased to three and the 'if' clause becomes TRUE. The scheduled func is called and exit door opens 5 secs later.

Actually, there is a ready made method in darfuncs.py that does the same thing. I thought I would demostrate the counter principle as it is usefule in lots of other cases.[:)]

Code: Select all

# define the group
Group1Ork.OnDeath= DoSomething

# add enemies to group
Group1Ork.OnDeath= DoSomething

Group1Ork.OnDeath= DoSomething

Group1Ork.OnDeath= DoSomething
The DoSomething func is called when all group members are dead.

big truck
Posts: 103
Joined: Mon Dec 27, 2004 2:13 pm
Location: Canada

Post by big truck »


Have you now new infos about the teams of enemies, so that they organize to place themselves around the Player1 to avoid killing each others during a fight ? And what about the leader of this team, how do that works, etc [;)]?

User avatar
Ancient Dragon
Posts: 1816
Joined: Wed Nov 21, 2001 1:42 am
Location: United Kingdom

Post by prospero »

Tut 16. Classes.

Heavy stuff here.[B)] Classes are basically databases. When the data contained within a class is assigned to something the something is controlled by the class. Any number of things can be controlled individually by the same class. Whats more you can create sub-classes
that add extra features to the parent class. All the Characters in the game derive their data from the class 'PlayerPerson' in Basic_Funcs.py. This initializes all the core functions and variables.
Player chars use their data directly from this class.

A sub-class. NPCPerson in Enm_Def.py derives (or'inherits') from this class and adds other data as well as all the AI features for enemies.

Each enemy race then has it's own sub-class of NPCPerson in the file EnemyTypes.py.

Now comes the interesting bit. You can make your own classes to alter enemy behaviour (within certain limits). In the last tut I demo'ed how to get a function called from enemy death. Perfectly sound method, but say you had 100 orks in your map and you wanted that code on each one. You would have a lot of repetitive code to write. Better if you use a subclass with that feature built in:

Make a new file. MyMapTypes.py

Code: Select all

import Bladex
import EnemyTypes
import Enm_Def
import GameStateAux

class MyMapOrk(EnemyTypes.Ork):


     def __init__(self,me):                
          EnemyTypes.Ork.__init__(self, me)# initialize parent class

     def NewImDeadFunc(self, EntityName): # define new ImDeadFunc
          if self.Deathfunc:

     # Saving and Loading code

     def __getstate__(self):
          NPCPerson_state= EnemyTypes.Ork.__getstate__(self)
          if (NPCPerson_state[0]!=1):
               return NPCPerson_state
          return NPCPerson_state

     def __setstate__(self,parm):
           if version==1:

Now you must import MyMapTypes to your enemy.py file. Instead of the
EnemyTypes.EnemyDefaultFuncs code you must point the enemy to your new class:


#and assign them the function to be called on death


# you can pass arguments with this


The class will remember all the Deathfuncs/args for all members of the class. (1000's of them if you want.[:p])

This is just a quick (but useful) example. Use the same principle to add/modify any aspect of the class. It is complicated stuff, but study some of the classes in the files to get an idea of how it all works.

User avatar
Ancient Dragon
Posts: 1816
Joined: Wed Nov 21, 2001 1:42 am
Location: United Kingdom

Post by prospero »

big truck.[:)] It's all to do with the AI. Look at the sorting functions in Enm_Def. It's all clever stuff and I don't really have full understanding of how it all works. (It's called 'fuzzy logic' in the trade).

User avatar
Ancient Dragon
Posts: 1816
Joined: Wed Nov 21, 2001 1:42 am
Location: United Kingdom

Post by prospero »

Tut 17. Traps

Bit of a cop out here.[:p] There are lots of traps in the game and most are quite complicated. To explain them all is not really practical. Best course of action is to find one in the game that you would like and copy/adapt the code to suit your own map. It may take time and a lot of head-scratching but by the time you have finished you will be an expert trap maker.[^]

Tutorial on traps. (Submitted by Raptor.[8D])

<i>By the way it looks, you might say that making a trap is very difficult (strange code and stuff). Nevertheless, it really is relatively easy. As a start, you should decide what type of trap you would like to use and where to place it. For example, you might want one of those balls style "Indiana Johnes" that would squeeze the player as this one gets to the sector that would activate the ball. You also might want a sector that breaks as the player walks on it, making him fall on lava, water or "pinchos".</i>


To make rolling balls, I would say, it is te easiest trap one could make. Basically, we can create a place somewhat similar to this one:


<b>1.</b> This is the place where the ball will be held before this one gets activated. It isn't neccesary to make a sector like this one since when you create an entity, this one stays in it's place (floating in the air [:P]) only if it isn't assigned the parameter o.Impulse(0,0,0) (Strongly not recommended to use in a ball)

<b>2.</b> This is the slope that is needed to make the ball roll and kill the player that enters it's territory (a trigger sector). It isn't neccesary to make a slope nor it is recommended since you can easily make the ball roll horizontally, but this one will stop somewhere in the sector it is rolling as the strength of the push decreases. By creating a slope, the ball will roll on it and it will not stop until it crashes against a wall or falls into a deep hole.

<b>3.</b> This will be the trigger sector that will make the ball roll down the slope. This one can be connected to a room, door, etc. tat will lead the player to this trigger sector, therefore activating the trap.

<b>4. </b> This is the hole where the ball will fall after rolling over the slope. It isn't neccesary to make it, but remember that if you don't create a place where the ball will be stored after it finishes rolling, the player will not be able to enter or exit from this sector since the ball will be blocking the way. If you decide to create a hole for tha ball, remember to make it deep enough so that if the player falls through it, he would die (to avoid getting trapped [:(])

<b>Having this information in mind, now let's create our ball:</b>

Code: Select all

import Bladex
import stone
import heavyObjects


p=3000,5000,4000 #Coordinates of the ball
r=0.707107,0.707107,0.000000,0.000000 #Orientation of the ball (not neccesary)
s=1.0 #Scale of the ball (default)
stone.lock("Bola1",stone.DROPBROWNDUST,0,1100,0.0,0.2,20.0,stone.STONESOUND,0.1) # Sounds and dust, the arg just before the stone.STONESOUND is the time the sound will be played (20 secs). The last arg (0.1) is the volume.

bola1Sector=Bladex.GetSector(41000,-12000,-17500) #Trigger sector
bola1Sector.OnEnter= DropBola1
<b>Function that will activate the ball</b>

Code: Select all

import stone

def DropBola1(sector,entity):
 if entity == "Player1":
  bola1Sector.OnEnter= ""
Ok, now we have the code needed to make our trap work. Let me explain how all of this works. If we don't add the "if entity == "Player1":" to the function, this one could be activated by anything, including wandering enemies, arrows, etc. By adding this command to the function, we arrange it so that it will only be activated by the player ("Player1"). And if other entities such as an ork enter the sector, we have our "else:" and "pass" that will ignore the entities entering the sector.
In "stone.drop("Bola1",-9999999,0,0)" The first value is the name of our ball and the three remaining are the coordinates of movement of the ball. In this case, the ball will move to the East. "stone.drop("Bola1",9999999,0,0)" would make the ball go West, "stone.drop("Bola1",0,-9999999,0)" would make it go uo. Oh, and "stone.drop("Bola1",0,0,-9999999)" would make the ball go North.
By adding bola1Sector.OnEnter= "" to the function, will make it only be activated once.

Now we know how to create our ball. Let's roll!


[:)] Another opportunity to die in an interesting way from Raptor....

<u>Spike Trap.</u> [:0]

<h4>LED PROCESS</h4>
These are the things you should do in your map through the LED in order to have a nice and neat trap. First, make a hole that goes underground -1 LED unit, and it's length and width of 0.5. Note that if you make the hole too big, the player might fall in it. And also remember to put the trap in a narrow passageway so that the player won't go around it:
Again, the hole is not neccesary since objects can freely move wherever and through any thing they want [:)]. It will just look more convincing if you make the hole [;)].
Now, with the <b>EBrowser</b> place a PinchoManuel inside the hole. It is located in the section "Arquitectura" (Arquitecture). Like this:
We're done with the LED. Now let's go to the code!

<h4>THE CODE</h4>
Create a new .py file (pinchos.py?) and add this code to it <b>(Remember to change the values set in yellow in the code below with the values you got from the EBrowser)</b>:

Code: Select all

import Bladex
import Ontake

sonidoroturahueco=Bladex.CreateSound("..\\..\\Sounds\\single-boulder-impact.wav", "SonidoRoturaHueco")
sonidopinchosaliendo=Sounds.CreateEntitySound("..\\..\\Sounds\\blunt-impact3.wav", "SonidoPinchoSaliendo")
pinchosgolpeando1=Sounds.CreateEntitySound("..\\..\\Sounds\\golpe-metal-mediano.wav", "PinchosGolpeando1")
pincho1=Bladex.CreateEntity("Pincho1","PinchoManuel",<font color="yellow">5753.8,1945,-22769.8</font id="yellow">,"Weapon")
pincho1.Scale=<font color="yellow">1.6</font id="yellow">
pincho1.Orientation=<font color="yellow">0.707,0.0,0.0,0.707</font id="yellow">
pincho1activate=Bladex.GetSector(5000,0,-24000) #Trigger sector that activates the pincho
pincho1deactivate=Bladex.GetSector(3000,0,14000) #Trigger sector that deactivates the pincho
Now add this on <b>DefFuncs.py</b>:

Code: Select all

import Objects

Pinchoactivated = 0
def ActivatePincho1(sector,entity):
 if entity == "Player1":
  global Pinchoactivated
  Pinchoactivated = 1
def DeactivatePincho1(sector,entity):
 if entity == "Player1":
  global Pinchoactivated
  Pinchoactivated = 0
def Pincho1Baja():
 Bladex.AddScheduledFunc(Bladex.GetTime()+<font color="red">0.5</font id="red">,Pincho1Sube,())
def Pincho1Sube():
 global Pinchoactivated
 if Pinchoactivated == 1:
  displacement=(1150,1150) #Displacement of the Pincho (2300 total)
  vel_init=(15000,15000) #Initial speed of movement
  vel_fin=(15000,15000) #Final speed of movement
  whilesnd=(sonidopinchosaliendo,"") #While sound
  endsnd=("",pinchosgolpeando1) #End sound
  Bladex.AddScheduledFunc(Bladex.GetTime()+<font color="red">0.5</font id="red">,Pincho1Baja,())
There it is! That's all you need to make your pincho trap. Now let's review how it works:

<h4>THE REVIEW</h4>
The player reaches a trigger sector:

The trap is started:

The player luckily makes it to the other side...:

...which is the trigger sector that deactivates the trap:

The pincho will move up and down every 0.5 seconds and it won't stop until it gets deactivated. If you want to make it move at a faster pace, just modify the numbers on <font color="red">RED</font id="red"> in the code above [:)].
Well, now you know how to make another trap. And if you notice that I add too much explanation is because it is easier! Like one man said (I don't know who): "I made this letter long because I didn't had the time to make it short" [;)]

Btw, I made a little map if you are having some trouble with the code: Click Here
Just put it in your ..\..\BODLoader\Maps folder and install it from Blade.

User avatar
Ancient Dragon
Posts: 1816
Joined: Wed Nov 21, 2001 1:42 am
Location: United Kingdom

Post by prospero »

Tut18 Torches.

Torches (and all other flaming objects) have a light bulit into the model and a particle system linked.

They are created as with all other objects, but they have two extra parameters.


Set the FiresIntensity to [0.0] for the maximum flame. As you increase this value, the flame will get less until none is visible.
(about [46] in think)

Lights has a number of values.

Lights = [(10.0,0.03125,(255,196,128))]

First is the brightness. 0 is no light. 10 is about average. You can increase fora brighter light. (The max in the game is about 60).
Second value is the Precission. Not exactly sure how this works. [:I]
Last set of three is the colour (r,g,b). You can alter the colour of the light, but the flame will alway remain orange.

A nice feature in the game is the ability to light hand torches and to light wall torches with hand torches. Only three types can be used like this:

"Antorcha" (hand torch)
"Antorchaenpared" (wall torch)
"Palangana" (fire bowl)

To make a hand torch that can be lit, create it 'dead', that is Lights= [(0.0,0.03125,(255,196,128))]

then add this:


First value is the brightness of the light when the totch is lit.
Second the fire intensity when torch is lit.

The last sets the time it will burn. -1 is everlasting. 180 will make it go out after 3minutes. You can only use them once.

The same method applies for wall torches, but the Player must have a lit torch in hand to use them.

btw. The "Antorcha" is officially a weapon. It's hit points are low in it's unlit state. BUT when you light them they aquire FIRE damage.[:p] If you create them ready lit you must still apply the SetUseable
code to enable the FIRE damage.

There is only one carryable torch, but lots of static ones. If you want to light other types you must ust this little trick:

in DefFuncs...

Code: Select all


# This adds the obj "LamparaNegra" to the useable torches.


Reference.EntitiesSelectionData["LamparaNegra_367"]=(8,2500,MenuText.GetMenuText("Sacred Torch"))
Do the last part for each individual torch object. The text at the end will show as the onscreen 'hint'

[;)] This code in this tut needs two extra imports. I won't say what as you should be able to spot them by now.

Certain flaming objects are just flames: They are "BoladeFuego" and the like. You can create lights in the LED which are compiled into the .bw file but you can also create lights seperately in script under the Kind:

"Entity Spot"

They have various parameters like:

Color= 255,113,10
Precission=0.8 #(?)

Set CastShadows param to 1 for shadows,0 for none. If you want to kill the cast shadows on a torch object use:

lo=AuxFuncs.GetSpot(lite1) # lite1 being the var of obj

Lastly, You may have noticed in the game that hand torches tend to go out when you go outside. There is a sound reason for this. Waving a light source about in a big area can affect framerates dramatically and you don't really need a torch outside anyway. Assign a function
'Apagala' (name doesn't matter. It's Spanish for 'turn it off' [;)])
to any sectors near the exit(s) from an interior place.

Code: Select all

def Apagala(sector,entity):
    if a.Kind == "Antorcha":
        print"My Torch has gone out!"

big truck
Posts: 103
Joined: Mon Dec 27, 2004 2:13 pm
Location: Canada

Post by big truck »

OK[:)][:)]. Now, I think we can speak about the most interesting subject of scripting (from my point of view[:p]). I mean, create a new race with the pre-programmed elements dispo in the game. As I know, the existing elements we can use are:

1/ the set of meshes (can't modify at this point of our knowledge of BOD)
2/ the textures according with each type of meshes (can be customized)
3/ the differents sets of movments, compatibles with the type of meshes or bones of the mesh (can't modify I think)
4/ the set of sounds (can be customized)
5/ the AI which controls the character behaviours, particularly in combat mode (very complex, but can be modified I think)
6/ the functions list to do something else (hide, freeze, turn to face entity, etc) (can be modified)
7/ the parameters list (Level, blind, etc)
8/ ...and some others stuff I don't know it exists[:I]

I know it's very complex but I know you've made new races[:p]. So, could you make a good description of all that stuff[:D]. How to do to set all these elements to make an entire new race, without alteration of the original files (all the files would be included in the MyMap folder)

Example of new enemie: An Amazon with a new skin, who fights like an ork, dodges like a knight, blocks like a barbarian, yells like a dwarf, dies like my mother-in-law (ooops, excuse me, I mean like a dark knight [:D]) etc

Is it possible to do all that?[:)]

User avatar
Ancient Dragon
Posts: 1816
Joined: Wed Nov 21, 2001 1:42 am
Location: United Kingdom

Post by prospero »

Ok. You asked for it.[:0][:D] Make yourself comfortable and put on a big pot of coffee.

Part1. Send in the Clones
I won't go into the actual modeling of a new char here. Frankly, others know a deal more about the subject than I do. I'll concentrate on implementing the model into the game once you have made it. A sneaky way is to 'clone' one of the existing models. Ok, it will be the same structure as the orig, but you can assign it a new Texture name.

Look in folder 3Dchars and select a .BOD file. I'll use the Kgt_N.BOD
for demo purposes. (This is the Knight no armour mesh). To do this job properly you need an text editor that can handle Hex code and has
search/replace feature. (UltraEdit is the only one I have used).
Make a copy of this file and save it somewhere temporarily.

Open the .BOD file copy and goto Edit->HexEdit. Most of the code is totaly incomprehensible, but look near the top for the internal name Knight_N. This is the 'Kind' name that the char is created with. This name occurs only once in the file. You need to enter a new name here.
I have found that if you edit this part directly, the file can be corrupted. Instead, select the name Knight_N and goto Search->Replace.
The name Knight_N will appear in the FindWhat field. Enter an alternate name in the ReplaceWith box. It's just my experience, but I have found that you need to keep the same number of characters in the name. Let's call him Knight_A. click Start and the name will be updated in your file. Warning here. Don't select the ReplaceInFiles option. It will replace the same phrase in every file on your PC.[:0]
Now you can either leave it at that or change the texture names. If you leave it, he will adopt the same skin as the original knight. Changing them will allow him to have his own skin. Scroll down the file until you see word KGTNF. This is his front texture. Select the word and Search/Replace with KGTAF. The texture names occur many times, so search/replace is great for this. Next, find his back texture KGTNR. Replace with KGTAR.

All char models stick to this protocol. (Except the Amazon who has a seperate texture for her face AMZFACE. (Typical woman.[:X])

Save the file. Rename it Kgt_A.BOD and put it in 3DChars folder.

Now you need to test it. Before this, you need to add it to the file
BodLink.list, in the main game folder. I have devised a routine to do this automatically in a map with new stuff, but for testing add these lines manually.

..\\..3dChars\Kgt_A.BOD # name of .BOD file
Knight_A #internal name

Now you can try creating him in a testmap. Do this exactly as with other objects but omit the entity class at the end. He is not a "Person" yet. He has no animation set and no data. He is an Un-Person.[|)]

testknight= Bladex.CreateEntity("TestKnight1","Knight_A",x,z,y)

When you load the map, several things might happen. You could get an error message saying something like "not enough memeory,etc". In this case there is a problem with the .BOD file. Scrap it and try again until you get one that works.[B)] If you get "Trying to create and object that is not preloaded....etc", check the lines you added to BodLink.list. If the Gods are smiling on you, the map will load OK and
the cloned knight will be where you put him. He will be laying horizontaly and have no texture, but you now know the .BOD file is sound.[:D] At this point you can make him a skin using the material names:


The _W denotes the 'wounded' version of the skin.

*If you can't be bothered to do this at this stage you can borrow a skin with the SetMesh command:


OK. Now you have a model. You need to create a new race. It is possible to tweak the existing races to add combos, alter behaviour,etc but this way the existing races can be used in the same map without the risk of interference.

A word on installation: It's nice to make a map 'user-friendly'. You shouldn't expect the end user to have to go editing source files. For that reason, in a finished map the BodLink.list needs to be appended using Python. At this point you need to convert your 'map' to a 'mod'.
Start game and uninstall your map from the menu screen. Exit game.
Make a Folder "MyMap" in the BODLoader Mods folder. Then move your entire map folder from BODLoader/Maps to the MyMap folder in BODLoader/Mods.

You should then have:


You need two new files:


To save a lot of time, look at these two files in 'ProsperoArena3' map. I'll use then as templates.

Copy these two files to the same place in your own map. They go in the outer 'MyMap' folder.

Open BLModInit.py. At the top, under the title header are three lines. What you need to do here is wherever it says 'ProsperoArena3',
replace this with MyMap name. Don't change anything else; just this phrase.

import MyMapMenu
global ModMenu

Next you will see lots of blocks of code starting:

if os path.exists......etc

Delete them all EXCEPT for the top one. So that the last line in the file reads:

print "BodLink.list already appended"

The first block loads the Amz_B model in ProsperoArena3. Edit this block of code to load your new Knight. In this case, just replace 'Amz_B' with 'Kgt_A' and 'Amazon_B' with 'Knight_A'.

* It occurs to me that sooner or later two people are eventually going to make a models with the same name.[:0]. This is not a good thing. A bit of communication is called for here.[:)][:)]

What this code does is write to the BodLink.list. You must delete the lines you manually added previously to test this routine. When the map is started and the BLModInit file is exe'd the system will check for the presense of the new .BOD file you made. If it exists and is where it should be, a check is made for a Kgt_AInst.cfg. If there isn't one(and there won't be on first load) then it knows that the model is not installed. The necessary lines will be added to the BodLink.list and the Inst.cfg file is created. This file serves no other purpose than that of a marker. (It just carries a little explaination from me.) Now next time and each subsequent time the map loads this check is made. But once the Inst.cfg file is in place the system will 'know' that the model has been installed an the BodLink.list has been updated. (It may be a clunky method by the standards of a professional programmer but it does work.[8)])

Next the BLModInfo.py file

Again use the ProsperoArena3 BLModInfo.py as a template. This file creates the necessary folders and copies all your files to the Main/Maps folder.

Edit the Mod Info section to your own specs. This is the text that will appear on the menu screen.

In the Mod Data section, first edit the MakeDirs bit. Replace ProsperoArena3 with MyMap name. Change NUSuperZoeStuff

I'll refer to the new Knight as 'Dave'

Code: Select all

          '../MyMap/Spanish' # you might need this later

In the lower part of the file all your files will be listed so they can be copied to the Main/Maps folder. Your map will not have all the same ones. Edit this section to list all the files you have up to now.
(Replace ProsperoArena1 with your MyMap name). Later as you create new files, they must be added to this list. (Watch the numbering after

* The actual model file should not really be included in this list. Include it with your map, with instructions to copy it manually to 3DChars folder.(Not too much of an imposition.[8)]) Once installed it needs to stay there and not be uninstalled again.

[:D] You are probably suffering extreme brainache at this point.[|)]

There is a LOT more to come.

Next you must do the Menu stuff, but before I delve into that I am going for a coffee....[:p]

big truck
Posts: 103
Joined: Mon Dec 27, 2004 2:13 pm
Location: Canada

Post by big truck »

[:D] OK you're the boss... here is it Image

... and this one for your great work (but finish it before drinking[:D])Image

[:o)] hips ! ... oops


User avatar
Ancient Dragon
Posts: 1816
Joined: Wed Nov 21, 2001 1:42 am
Location: United Kingdom

Post by prospero »

Cheers.[:p] I needed that.

Ok, moving on. You can make your new Knight an NPC or Player. I'll make him a Player. If you make him an NPC you can't use him as a Player without extra stuff, but if you make him a Player you can quickly convert him to NPC later.

Make folder 'MyMapMenu' in inner MyMap folder.

Copy ProsperoArena3Menu.py from you know where.[:)] to this folder.
Rename it MyMapMenu.py.

Open it.

This menu allows selection of all orig Players and SuperZoe.

Think of a name for your new Knight. I'll refer to him as 'Dave' for now.

Replace all mentions of 'SuperZoe' with 'Dave'

In the CharBitmaps part, replace AmzSkin2 with KgtSkin2. (you can alter this later.)

What you need to do now is to replace every mention of ProsperoArena3
with MyMap. Do it carefully and don't miss any. (use search/replace if you like.

Make a MyMapMenu.bmp. Use the ProsperoArena3Menu.bmp as a pattern. Note that you have to swap the colours RGB ->BGR. (Irfanveiw veiwer will do this.) Save to MyMapMenu folder. Add these two files to list in BLModInfo.py (MyMapMenu.py dest = '../../Scripts')

Replace your pj.py with the one from ProsperoArena3.

# comment out the lines 'import NUSuperZoeData' and 'import PlayerTypes'

replace 'Amazon_B' with 'Knight_A' (You can tweak the resistances table if you like. Super Zoe uses same settings as original. 1.0 is max resistance, -1.0 min)


Change initpos to suit your own map. (Start Point)

replace ProsperoArena3 with MyMap name in try/except bit.

replace "SuperZoe" with "Dave" in the last elif clause below.

in the 'if PlayerName=="SuperZoe":' bit near the end, type


in front of the line:


(keep the indentation)

OK, you should now be able to test the menu. If all is well and you get as far as the Player selection screen, select any Player apart from "Dave" and see if the map loads OK. Check whether the Player pics cycle in sync with the names. Menu code can be very tricky so take your time with it.

Ok. If all that works with no probs the next step is making all the files to create the Knight_A as a new race.

btw. Note on the savegame. When you edit the internal name, keep the
K as a first letter. (same with other Player Chars). If the int name begins with any other letter than A,D,K,B then the savegame won't work. <u>UNLESS</u> you have replaced the file Lib/SaveGame.py with the modded version issued with the New Orc City demo map. In that case, any new Player char will appear on the savegame menu screen as 'New Char'.

It's 3:20am so this tut will continue tomorrow. [|)][|)]

Ok, next step.

First thing before I forget. To keep the savegame integrity you need to add some code. Open pj.py copy all the Stats tables (including imports) to your DefFuncs.py file.

Next you need to declare some filepaths in cfg.py:

import sys


(put this just after the LoadBar code)

Make a new file 'ActorsInit.py' (exec in cfg.py and add to list in BLModInfo) Put the same filepath code in this file. (This is another
key file and must have that exact name) This file is exec'd before DefFuncs as the savegame loads.

Open the NUDaveStuff folder. Copy the file NUSuperZoeData.py from ProsperoArena3 to this folder. Rename it 'NUDaveData.py'. Open it and
replace all mentions of 'SuperZoe' with 'Dave'. Replace 'Amzazon_B' with 'Knight_A' Where is says 'Amb', replace with 'Kga'. Replace the
var 'amb' with 'kga'. Do this very carefully and make sure you don't miss any.[8D] Lastly, remove the # comments from the code in func 'SuperZoeWhenFirst'. (It will be 'DaveWhenFirst' now). When you have finished the char, this code must be commented out again.

This is the core file that creates the race. It refers to several other files that you now have to create. From the top, first you need:



import Bladex

at the top and save it to NUDaveStuff folder.

Now open file Scripts/anm_def.py. This file contains one big function called 'Init'.

Make a function in DaveAnm_def.py called InitDave

def InitDave():

What you have to do now is to copy all lines out of anm_def.py that have 'Kgt' in them. Most are all in one block near the middle, but there are a few sneaky ones at the begining and some others at the end. Don't miss any. Paste these into your 'Dave' file. Preserve the indentation. Once you have done this, you need to replace all instances of 'Kgt' with 'Kga'. This is where search/replace capability
in invaluable. It is a boring job doing them one at a time.

This file adds all the events to be raised in the course of an animation with timings. Most are to do with enabling the damage on weapons and starting trails.
btw. Don't exec any of the files in the 'NU'subfolders in cfg.py.

Next, you need to do a new animation set file. Dave does not have any animations of his own.[:(] (Unless you care to make him some. [:)]) He will have to borrow them all from Sargon. This may seem a bit pointless, as he will behave exactly the same as Sargon. Fear not, once he is working properly with Sargon's moves you can begin to alter the animation set to make him unique. His animation set will be his own and will not affect other chars.

Tune in to-nite for the next thrilling installment.[:p]

big truck
Posts: 103
Joined: Mon Dec 27, 2004 2:13 pm
Location: Canada

Post by big truck »



BIG suspense [:0]


User avatar
Ancient Dragon
Posts: 1816
Joined: Wed Nov 21, 2001 1:42 am
Location: United Kingdom

Post by prospero »

OOh. Spooooky[:0]

Anyway, back to business.....

Look in Lib/AnmSets for file KnightAnimationSet.py. Copy/paste it to
your NUFiles/NUDaveStuff folder (As you add these files, update the BLModInfo.py. Keep the same folder structure).

Rename the file 'NUDaveAnimationSet.py' and open it. Now comes the really tedious bit. Start at the top. Alter the function name to

def LoadDaveAnimationSet(ct_name):
(alter the 'print' statement if you like)

The first anim is Rlx_no This is the animation the char will do when standing doing nothing with no weapon in hand. You need to alter all
instances of 'Kgt' to 'Kga', EXCEPT where it comes before the extension .BMV. These files contain animation info. Because you are basing 'Dave' on Sargon's animation set, you still need to reference these. What you are doing here is assigning them to your new race.
Lastly, on the first line of each code block there is a 1 (sometimes a 0) After all these 1s and 0s add ,Knight_A. Like this:

Code: Select all


Now work your way down the file altering as you go. The file is very long and you will probably loose the will to live about halfway down.[|)]. As you go, try to guess what each anim is for.

Kgt_rlx_vt = very tired [B)]

Now just to make things awkward some blocks of code are written differently.[}:)] they begin:


Do these like this:

Copy the whatever bit (not the quotes"")
Paste it into the filepath to replace"+ anm_name + .Paste over the first quote but leave the second. The end should look like this:


Now alter the anm_name= "Kgt......etc" to "Kga.....etc

If you are clever, you could try using the search/replace feature. It is tricky as you have to be selective. When you get to the death animations you are near the end of the file. (You will probably feel like death when you reach here.[|)][|)][|)])

Alright, thats the AnimationSet made. Next Biped action.[:p] Look in Scripts/Biped for KgtBAct.py. Copy it across as before. Rename your copy 'NUDaveBAct.py'.

Open it and swap 'Knight' for 'Knight_A' and 'Kgt' for 'Kga'.

This time you can use search/replace. Take a moment to study this file so that you get some idea of whet each anim does. e.g

Attack_f_1h = moving forward in combat mode with one-handed weapon but no shield.

D_b = Dodge back

hurt_head = guess what! [B)]

Next file Combos. Copy KgtCombos.py from Scripts/Combos. Rename your copy 'NUDaveCombos.py'

Replace 'Knight' with 'Knight_A'
Replace 'Knight_N' with 'Knight_A'
Replace 'Kgt_N' with 'Kga'
Replace 'kgt' with 'kga'

Lastly, NuAnmFact.py. Make a file of this name and save it to the NUFiles Folder (Not in The NUDaveStuff folder) This file sets the timing of some animations. If you happen to have some new NPC races they can share this file.

Open the file Lib/AnmFact.py and copy the entire function 'AnmFactKnight' to your file (import Bladex at the top)

Rename the function 'AnmFactDave', then change all the instances of'Kgt' to 'Kga'. The values at the end denote the time of the animation. Now you may think that if you increase the value, the animation will be slower, but in fact it works the other way. Increase values to speed up animations.[;)]

Ok add this file to list in BLModInfo.py with the others so that it is installed in the right place.

There is still more to do before Dave will operate, but that is the basic work done.[:D]

Before you can test Dave out, you need to give him some sounds.

Look in Scripts folder for file AniSoundKght.py. Copy it across to NUFiles folder. Rename it 'ExtraAnimSounds.py' This file contains one big function 'AsignarSonidosCaballero'. Change this to 'AsignarSonidosDave'. Now another boring exercise. Go the file and
change all the 'Kgts' that come directly after per, to 'Kga'. Don't alter the others such as 'AndarKgt1'. At the moment you are just giving Dave Sargon's sounds. (You'll see that Sargon also shares a few sounds with Tukaram.) Later, you can create and assign any sounds you wish to personalise Dave.

Ok. Now make a file 'PlayerTypes.py. Put it in the inner MyMap folder with the rest of your main files. this file will contain a sub-class of the 'PlayerPerson' class.

Code: Select all

import Bladex
import XtraAnimSounds
import Basic_Funcs
import PlayerTypes

class Dave(Basic_Funcs.PlayerPerson):
    def __init__(self,me):


    def ResetSounds(self,EntityName):

That's all you need for now. If you have made Dave a custom skin you can omit the MeshName line.

Open your pj.py file.

At the top you need:

import NUDaveData (in place of import SuperZoeData)

Change the PlayerName in the last elif clause from SuperZoe to Dave.
Below this , I put in a little failsafe clause that checks that the Knight_A model has been installed. Alter the filepath to Kgt_AInst.cfg. You can alter the starting weapons at this point.

Lastly, at the end of the file change the if clause:

Code: Select all

if PlayerName == "Dave":

Finally, open ActorsInit.py and add

import NUDaveData

This is purely a savegame thing. ActorsInit file is exe'd early in the savegame load routine. As he is a new char Dave needs his data loaded before the savegame re-creates him. This principle also applies to new weapons. (Check out ProsperoArena3 ActorsInit File)

You should now be able to use Dave in your map. Load the map and try
him. I should mention at this point that if he works first time with no problems then you are indeed Ianna's Chosen One.[^] You will probably have a bit of de-bugging to do. There are 1000s of lines of code involved so the possibility of errors is quite high.

After you do get him working, there are a few refinements you can add:

As he is, the FX on combo's won't work and you won't get the New Attack messages. For these you need to make new files and add stuff to the Player class. Refer to ProsperoArena3 to see how these work. In fact, if you have any probs, refer to this map.

Once you have Dave working, he will have the same abilities as Sargon.
Next I'll go into how to customise him.

btw. You can in theory use any char as a Player. I used the Knight as an example purely because he already is fully functional as a Player.
NPCs have lots of animations missing as they never need to open doors and pick stuff up,etc. If you use a Traitor Knight as a Player for instance, using his own animation set, you can walk him round OK but
lots of moves will be missing. You have to add the missing animations
by borrowing suitable ones from other chars. All the 'human' types (they have 25 nodes in the structure) will share animations and there are LOTS to choose from. Other chars (Dal for one) will not carry weapons on their backs. Non-Humans (Minos, Demons) have a diffrent number of articulations in their structure and will not use human animations.

What to do with new Player now you have got one.[8D]

Almost anything.[:0] Now you have a complete set of files dedicated to the new char you can edit all aspects of the behaviour without affecting the other chars.

You can replace animations with those from other chars or add attacks
of other chars.

As 'Dave' was based on a Player char, he already has all the attacks bound to keystrokes. If he were say, a Traitor Knight all this code would be missing as well as many animations. You need to borrow anims from other chars. Simply find an anim in the relevant animation set and c/p it into Dave's animset. Edit the move name so that it reads 'Kga' instead of 'Dwf' or whatever. Add 'Knight_A on the end as with all the others. Then you must add it to the DaveBAct.py file.
If it is an attack you must add it to the DaveAnm_def.py. (again c/p
from source Anm_def.py and edit name) Add to NUDaveCombos.py. Look at the rest of the file to see how it should be implemented. (This is the most complex file to do.) Lastly you may need to add it to NUAnimFact.py to set the right speed. This isn't always required, but if your char does a move very slowly you can be sure it does.

There is also the sounds of the animations to consider. Look in the NUAnimSounds.py to see how they work. Each animation has several sounds bound to it. The numbers at the end of each line set the time from the start of the anim that the sound will play. The actual sounds are created in another file (AniSoundCharType+'X') These are imported with the statement

from AniSoundCharTypeX import *

You can import more than one file of this type if necessary, or better, pick sounds from multiple files and make your own AniSoundDaveX.py.

There are many things you can do to customise your char. Too many in fact for me to go into here. When I did the SuperZoe attacks I first removed all the existing ones and then added the complete set one at a time, starting with the basic 1-handed sword attack. (Testing after each new one was added). NUSuperZoeCombos.py is a good file to use for reference.[8)]

All her Spear moves are original
1Hand swords/maces are various Knight moves, but where the Knight will use Dwarf weapons with less skill, S.Z. uses them as well as Dwarf.
2Hand Swords all Barbarian.
(I didn't implement the 2Hand Axe moves, but I have 'taught' them to NPC Dwarfs.)

big truck
Posts: 103
Joined: Mon Dec 27, 2004 2:13 pm
Location: Canada

Post by big truck »

Well Pro[:D], after that, we should find now new races in maps[:p]


1/ How to convert the new race in NPC character? ... it would be nice to fight against[:p]

2/ Do you have some infos about th IA in combats? The behaviours are not the same, depending on the race. So it would be nice to built or, at least, modify the existing IA so that the behaviours and decisions in combat of your new NPC race would bring a new interest to the player[:p][:p][:p].

3/ How to set him as an Ally/Enemy?

4/ How to handle the cam (travels, rotations, etc...)?

And thanks again for your very clear explainations [8D][8D]


User avatar
Ancient Dragon
Posts: 1816
Joined: Wed Nov 21, 2001 1:42 am
Location: United Kingdom

Post by prospero »


To use a new char an NPC, you need to give them a class and a combat chart. (This also applies to existing Player chars.)

If you look in Scripts/EnemyTypes.py the first class is Knight_Traitor. This is a good one to use as a basis. There is a function in every NPC class, 'ResetCombat' that sets certain parameters and also assigns the combat chart:


This refers to the file 'Combat.py' where all the combat charts are defined.

To make your own class for your char, copy the Knight_Traitor class
to a new file in your map and name it MyMapTypes.py or whatever. (Don't exec it in cfg.py) Put in whatever imports you need.
Now go though the class and replace all mentions of Knight_Traitor with 'Dave' (Or whatever char you are using. This name is not critical.) One exception is the ResetSounds func. There is a sorting clause here to set the right sounds for Traitor and Dark knights. They share the same data, but have different vocals. Remove all the 'if' clauses and put:

Code: Select all

def ResetSounds(self,EntityName):

    # for other Player chars...e.g
    # AniSound.AssignarSonidosCaballero(EntityName)

You can make another file for combat charts, but I tend to put them in the same file as the classes. Copy the entire TraitorKnightAttackData=[.........] into your file, below the class.
(Don't miss the closing

As the charts are no longer in Combat.py you need to remove the


in the ResetCombat func to give:


Rename the combat chart, DaveAttackData

Also you now need to refer back to things in Combat.py, so add



You also need to do this for the words TempMoveInProc etc, but for now
comment # out all the lines - Laugh/Insult/GiveOrders/UsePotion and just add


If you are finding this all very confusing, look in ProsperoArena1 for the file 'EnemyCharTypes.py' There are classes/combat charts for all Player chars. Study them to see how it all works. The orig Traitor Knight has attacks defined as GA/GM1/GM2 etc. Your char's attcks will be different (G1_1H/GM6_1H/etc) You need to put the correct attack names in the combat charts. Notice how some of the Traitor Knight's attacks are defined in the charts in sequences. This way you can get them to repeat attacks or follow one attack with another.

Now this stuff is very complex and I don't understand it all myself.
The value before attack name is the 'probability factor' If I understand correctly, it sets the likelyhood that that attack/move will be executed in a given situation. After the attack name there is
the min distance/best distance/max distance settings plaus a value at the end (not too sure what that does)

Having written all the data, you assign it to your char with:


This replaces usual EnemyDefaultFuncs assignment. dave1 is the var you used to create your char. You can create as many 'Daves' as you wish and they will all use the same data. Don't forget to import 'MyMapTypes' [:)]

If all is well, your new NPC should attack you when he sees you. (There's gratitude! [}:)]

All this is just scratching the surface of a very complex subject. Once you have your basic data you can tweak it and generally play around. Alter a certain aspect and see how it effects the chars behaviour.
You'll notice from the ProArena1 classes that you have to modify
the Amazon and Barb attacks from the ResetCombat function according to how they are armed. Otherwise they do 1Handed attacks with spears
and vice-versa. The Barb will do 2H AXE moves with 2H Swords if all the attacks are available to him. Notice also in this map that all the enemies have an extra sub-class 'EnemyChar'. This contains code that makes the corpses fade away. All the other classes in the file inherit from this. You could get the same effect by putting the fading code in all the classes, but it is neater this way and makes a nice example of how the class stuff works.

You can go on from here to add other attacks and give Traitor Knights
combos, etc. Check out the enemy classes in Masklin's Inn map for examples of this.


Thanks once more to Masklin you can now have friendly chars. If you have Allies mod installed they are easy to create. The file Ally_Def.py is a modded Enm_Def.py that makes the NPCs only attack enemies of the Player. (I'm still trying to figure out how it works.[:I]) To use this data is easy. In your NPC class, use


instead of


More on Allies and Enemy behaviour later.....[;)]

Post Reply