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: Ade, prospero

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

Level Editing: Scripting

Postby prospero » Thu Jul 21, 2005 12:53 pm

I thought I would start this topic to answer scripting questions and do mini tutorials on various Python stuff for the benefit of mappers.
(This is just how I do things, and I don't know everything by any means.[8)])

(caravel. Maybe it could be made into a 'sticky'.[:D])

---------------------------------------------------------------------
Tut1.

OK, you have made a map, compiled and loaded it and your Player char is stood alone wondering how to fill this new world with interesting stuff.

All the gameplay flow is controlled from a file:

DefFuncs.py

Create a file with this exact name and save it to your map folder in BODLOader/Maps.

This file is one of the Key files that is executed again when loading a saved game. For that reason it must always have this name. All your other .py files that create objs, etc are only executed on first load.



In a basic map you will have three essential files:

cfg.py # initiates the map and loads all the files
pj.py # creates the Player character
MyMap.lvl # loads all the texture files + dome (sky) and .bw (compiled 'Blade World' file)

*I will use the term MyMap to refer to the name of your map folder.(case sensitive)

The BODLoader will create these files automatically if they don't exist in the BODLoader/Maps/MyMap folder. NEVER edit your files in the main Maps folder. ALWAYS edit in the BODLoader and unistall/install your map to test it. If you don't stick to this practice you risk hours of work being overwritten by older files.
(Yes, I have done this more than a few times.[}:)])

Next, open the cfg.py file and add this line at the end:

execfile("DefFuncs.py")

All the files you create will be executed from here, but keep DefFuncs
at the top of the list.

Now open DefFuncs.py

There is nothing to add yet, but for a start you can add the code to enable the savegame.

First add this:

import GameText

You will need lots of imports as you proceed, but at the mo you only need this one. An import statement allows you to access to all the code in the imported file from the file it is imported to. If you try to do this for example:

darfuncs.HideBadGuy("Ork1")

without importing the file darfuncs.py, you will cause an error. Python reads code from the top of a file down, so put you imports before you use their code. (usually they are all at the top of the file.)

Underneath this import add:

GameText.MapList["MYMAP"] = ["MyMap"]

First box is a CAPITALIZED version of your map name.

Save and load your map (remember to uninst/inst). If all is well, your map will appear in the save menu screen. Try saving/loading the map. You should have no probs at this point as there is nothing else in the map to foul things up. Making the save games reliable can get very tricky as you add more things.....

Try the F1 key. This code also enables the Travel Book.


To be continued.....

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

Postby prospero » Thu Jul 21, 2005 1:40 pm

Tut2. Stopping 'Ice Skating'.[B)]

As you proceed to build your map in the LED and add new parts, you will eventually encounter the 'Ice Skating' syndrome where the Player
will slide about when entering certain areas. This is because when the map is first loaded, certain info on the structure is compiled into files in a 'pak' folder which is created in the main map folder.
You need not pay any attention to this as it takes care of itself. However, this folder and files is only created if it does not exist and making changes to your map will confuse the engine if these files contain info about an earlier version of the *bw file. This folder should be removed everytime you recompile the map.

To achieve this, add this code to your cfg.py file

Code: Select all

import os try: os.remove("..\\..\\Maps\\MyMap\\pak\\BODPak.dat) os.remove("..\\..\\Maps\\MyMap\\pak\\pf.dat") except: pass
This will force an update of the files. When you have finished building the map structure, delete this code to save a bit of load time. Speaking of which, as you add to your map the load times will increase and you will find the loading bar will reach the end before the map has finished loading. To adjust this, look at your cfg.py for the line:

LoadBar.ECTSProgressBar(333,"BladeProgress.jpg")

Increase the value 333 (or whatever) as your load times increase. (It's mostly guesswork, and maps don't always take the same times to load.) You can make a Blade_progress.jpg if you like. use fav graphics prog and size to 640x480. Save in BODLoader\Maps\MyMap folder. They tend to appear lighter in the game than in graphics prog so allow for this. BOD will look for a file of this name, but it will cause no probs if you don't have one. When the load bar reaches about halfway BOD will look for "BladeProgress2.jpg". Again, this is optional.

One more little thing I have found. If you are playing a RAS map and
stop and load a custom map, you might find that the Player will maybe not be the one you were expecting. The Player from the RAS map will continue into the custom map at their current level and inventory.[:0]. Use this code before LoadBar code in cfg.py to clear out current Player info:

Code: Select all

import MemPersistence try: Bladex.DeleteStringValue("MainChar") props=MemPersistence.Get("MainChar") props[2]["Objects"] = [] props[2]["Weapons"] = [] props[2]["Shields"] = [] props[2]["Quivers"] = [] props[2]["Keys"] = [] props[2]["SpecialKeys"] = [] props[2]["Tablets"] = [] props[2]["InvLeft"] = None props[2]["InvLeft2"] = None props[2]["InvRight"] = None props[2]["InvLeftBack"] = None props[2]["InvRightBack"] = None except: pass
more later.....

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

Postby big truck » Thu Jul 21, 2005 2:22 pm

Pro, this topic is an excellent idea[8D].

With your permission I will copy/paste all these very usefull infos on my site with a special page for you[:)]

No doubts it will help a lot of mappers (including me) to do some good stuffs[:p]

Thanks for your efforts.

[;)]

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

Postby prospero » Thu Jul 21, 2005 5:15 pm

Feel free big truck. [:)] Don't know why I didn't do it before....

Tut3. A bit of boring theory. [|)]

This is a variable:

a

so is this:

iamavariable

A variable can be (almost) any combination of Letters,Numbers or under_scores, up to 256 characters long. The only exception is that they must not be Python Keywords such as:


and / assert / break / class / continue / def / del / elif / else /
except / exec / finally / for / from / global / if / import / in /
is / lambda / not / or / pass / print / raise / return / try /while

Variables 'store' values about things. Assign a value by:

var1 = 1
var2 = 2

The values can be changed during the course of a program. Which is why they are called variables. They can store all the info about certain things in the game. e.g. When the Player is created, all the
info (or 'attributes') about he/she is usually assigned to the variable

char

as in

char= Bladex.Create Entity("Player1",..........etc)

This calls a function in the Blade engine which creates the Player and
returns all the default values to the variable char.

the attributes can be reset by:

char.Life=2000
char.Level=10
and can be acessed and assigned to other variables by:

charlevel= char.Level
charlife= char.Life
charpos=char.Position

When objects are created they are usually assigned the much used variable o. As in

o=Bladex.CreateEntity("Rock1",........etc)

Once the object is created the o variable is used by the next object to be created. Only the last o used will retain the values of the last
object to be created. In practice you very rarely need to access an objects attributes during the course of a map, so it is safe to reuse the o.

If you do need to do this, any object/enemy can be re-assigned to a variable by:

rock1=Bladex.GetEntity("Rock1")

and altered:

rock1.Scale=2.0

It is nice to make the var names descriptive (orc1, box34, boss1,etc)
as it makes code easier to follow.

Functions.

Functions drive the whole gamplay flow. This is a function:

Code: Select all

def Function1(): *block of code here*
The code within the function is not executed when the file is loaded. You have to invoke or 'call' the function to execute it. This is done by a number of events in the game: operating a lever, trigger sector,
death of enemy, etc

Function1()

When the function is called, the code within it is executed. Functions are reuseable and can be called as many times as you like.

Values can be passed to a function. e.g.

Code: Select all

def HowHealthyAmI(entity): pers=Bladex.GetEntity(entity) lifeleft=pers.Life return lifeleft health=HowHealthyAmI("Player1")
This function caller 'asks' the function the current life points of the charcter specified (Player1 in this case). The 'answer' is returned to the caller and assigned to the var health. Get the picture? [:)]

Clever bit is, it also works for any character:

orc1health=HowHealthyAmI("Orc1")
orc2health=HowHealthyAmI("Orc2")

If you try this on something that doesn't have the attribute 'Life', you will get an error.

Functions can pass any number of values (or 'arguments' as they are more correctly known. If a number of arguments are listed in the function, it will expect that many from the caller. You can use default arguments:

Code: Select all

def WhatLevelAmI(entity="Player1"): pers=Bladex.GetEntity(entity) perslvl=pers.Level return perslvl charlvl=WhatLevelAmI()
Calling this function without arguments returns the Player1 current Level to var charlvl. However if you do this:

enlvl=WhatLevelAmI("Zombie1")

it will return the Level of Zombie1. See what I mean?

* One small but important point here. Python counts from 0, not 1.
The level returned by the above function will be one less than the actual level of the character. If you need to know the actual Level put:

enlvl=WhatLevelAmI("Zombie1")+1

If you assign

char.Level=2

they will actually be at Level 3 in the game

This rule does not always apply, but is worth bearing in mind.

Ok, that's enough boring stuff. I don't intend to go into all the Python theory. This was just to clarify how things work on a basic level.

More practical stuff next.......

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

Postby prospero » Fri Jul 22, 2005 2:16 am

Tut4. Adding Objects

The map looks a bit empty, so now you want to add some objects.[:p]

Before that, just a word about map coordinates.

In the LED, you have three Axes (not the chopping sort) The X axis runs West-East. West from the origin the values are all negative: postive East of the origin. The Y axis runs North-South. The values North are negative and positive to the South. The Z axis runs Up-Down.
this is the one you set as Floor/Ceiling hight.

Now when you use the LED readings in script it is <i>not quite straightforward</i> [B)]. Firstly, all the LED units need to be multplied by 1000. (If the LED units are meters, they should be expressed as millimeters in script. The X axis reads true, but the Y and Z axes don't. Basically what you have to do with the Y and Z is to call a positive number negative and vice versa.

e.g. Put the LED mouse cursor over a spot where you would like to create an object and say the x[] y[] boxes in LED read 279.000 and 100.000. The sector is 0 floor and 10 ceiling and you want the object about one meter up off the floor. The correct coords for the scripts would be:

279000, -1000, -100000

They are always written X, Z, Y

It is important to get a feel for this as you will be needing lots of coords.

Ok, now we have got that straight, let's make something. Create a file
and call it objs.py (You can name these files whatever you like, but don't call them the obvious 'Objects.py' or 'objects.py'. There is a BOD source file of that name.) At the top type:

import Bladex

The basic creation code is:

Code: Select all

o=Bladex.CreateEntity("Thing1","Cazo",279000, -1000, -100000, "Weapon")
Before you can use this file it must be added to the execs in cfg.py.

execfile("objs.py")

Thats all.[:)] Load the map and you should have a ladle at the spot you picked.

"Thing1" is the individual Name of the ladle. You can't have two Thing1s in the same map.

"Cazo" is the object Kind. Kind is an proper attribute of an object, as is Name, Scale, Orientation and Position.

There then follows the three coords to fix the Position of the object. x, z, y

The last argument is the entity class. In this case it is a "Weapon".
If you are not sure of the entity class, there is a neat function in file Lib/Reference.py that will return the correct entity class:

Reference.ObjType("Cazo")

Put this function call in place of "Weapon". [;)] As you are now refering to another file, remember to

import Reference

Objects that can be moved are classed as "Physic". Static objects have no class so Reference will return "".
(an empty string in Python terminology).
If you know and object is Static, you can omit the last arg completely.

You can give each object whatever name you like. As with variables they can be any combination of letters and numbers and under_scores.

The 'Kind' arg is critical. Most of the Kinds are in Spanish. In practice you soon get familiar with them. (Unless you <i>are</i> Spanish, in which case you have no probs.[:D]) If you d/l the Objects catalogue the Kinds are all listed with nice piccies. The case is also critical. "antorcha" is not the same as "Antorcha". (This is a hand torch btw.)

Accurately placing objects is best done using the EBrowser tool (by Masklin.[8D]) I will come to that later.

Now you can create anything you like and position it (approximately).
As you proceed you will notice that some things don't behave as expected. You can't use Potions or smash up boxes for example. Your Bows/Quivers won't work, etc. Some objects need extra data contained in a class structure that controls how they work.

To make a box breakable add

Breakings.SetBreakable("Box1",12,100)

after creation. If you use the variable o to create the box you can put

Breakings.SetBreakable(o.Name,12,100)

Same thing really, but if you have a lot to do it's quicker to copy/paste this line rather than type all individual names. If you use another var, say box1, put box1.Name. Don't worry about the last two args. All the ones I have seen in the game use the same values.

(*Don't forget import Breakings at top of file)

Not all objects are Breakable. Check Breakings.py. If the object is not mentioned there you can't break it. What actually happens when you break an object is that the original object is instantly removed an is replaced by a number of seperate objects that together look like
the original. These are all named ObjNamePieza1 etc and are numbered sequentialy. Some objs have a lot of Piezas, some have just two. As soon as they are all created in the position of the original object they are exploded. Later they will fade away for the sake of performance. You can create piezas seperately if you want a bit of wreckage lying about.

With Potions, you make them useable with:

pocimac.CreatePotion("Potion1") # or (o.Name)

again remember to import pocimac file.[;)]

for food

pocimac.CreateFood("Cheese1")

for Power Potions

pocimac.CreatePowerPotion("PowerPot1")

You can now eat and drink and smash things up.

Weapons next.....[:p]

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

Postby prospero » Fri Jul 22, 2005 10:15 am

Tut 5. Weapons

Most weapons will work properly with the basic creation code. Some need extra data. These are the 'special' weapons that have a particle system linked and have special damage (Fire,Ice,Poison,Light) that is only activated by the special combos.

What you need is:

ItemTypes.ItemDefaultFuncs(o)

*import ItemTypes at top of file

As well as special weapons, all sheilds need this code as well as Bows and Quivers. The Ianna sword also.

Though they are not weapons, the Ghost Medallion ("Amuletofantasma"),
the Crown ("Corona") and Bracelet ("Brazalete") also need this code.

Enemies next.....[:(!]

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

Postby prospero » Fri Jul 22, 2005 11:07 am

Tut6. Enemies

Enemies are created in exactly the same way as objects, but they all have the entity class "Person". They will always stay on the ground and will always remain upright (until you kill them). They don't have an Orientation attribute, instead they have an 'Angle'. An Angle of 0
will make them face North in the map, going counter-clockwise to 6.

You should not alter their Scale. I think you can make them smaller, but you cannot make them bigger.

I usually have them in a seperate *.py file, enemies.py. Exec this file in cfg.py as with objs.py. Don't name your file 'Enemies.py' or
'enemies.py'. As with 'Objects', there is a BOD source file with this name.

I like to give each enemy their own variable, usually a lowercase version of the individual name:

ork1=Bladex.CreateEntity("Ork1",Ork,x, z, y,"Person")

When you have lots of baddies running about you will appreciate the value of this.[:)]

To enable the AI, enemies need a lot of data. Assign this with:

EnemyTypes.EnemyDefaultFuncs(ork1) # import EnemyTypes [8)]

Enemies need weapons. Apart from the ones that naturally fight with no weapons,(Golems, Demons, etc),they have no collision edges in the mesh for unarmed combat and no data to handle it anyway. If you don't arm them they will just run about in confusion. Also DO NOT try giving them Two-Handed stuff. It will crash the game when they try to move.

Create their weapons/shields in the usual manner Coords don't matter here, just use 0,0,0,. Don't forget the ItemDefaultFuncs code on shields and bows/quivers. To make nice neat code I name the weapons of an enemy the same as the enemy but with WP on the end for weapons, SH for shields.

0nce you have their kit created you need to get them to take it. There is a handy function in file Actions.py that handles this:

Actions.TakeObject("Ork1","Ork1WP")
Actions.TakeObject("Ork1","Ork1SH") # import Actions

Now they are all set to rock'n'roll.[:p]

If you want to use Player characters as enemies you need to create a data class and combat charts for them. I won't go into that at the mo.

One complication is Dal Gurak. He has two phases. He will start in phase1 where he will teleport about and throw stuff at you. This phase
needs scripting to assign him teleportation coords. The answer is to start him in his melee phase:

dal1.Data.Phase=2

Same applies to the "DarkLord". He needs loads of scripting with his existing data, coords for teleporting, meteorites, etc. His whole class data needs rewriting in order to use him simply. I won't dwell a that for now.

The 'Kind' names for enemies are:

Ork
Great_Ork
Knight_Traitor
Dark_Knight
Knight_Zombie
Lich # Mummy type guys
Cos # Little pink critters
Spidersmall
Skeleton
Little_Demon
Vamp
Minotaur
Troll_Dark
Troll_snow
Ragnar
Salamander
ChaosKnight
DalGurak
Golem_stone
Golem_clay
Golem_lava
Golem_metal
Great_Demon
DarkLord

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

Postby prospero » Sat Jul 23, 2005 3:57 am

Tut7. Controling Enemies.

Once you have your enemy, he is free to do and go wherever he likes.
He will chase you all around the map. You may not always want this.
Enemies will freact to sounds and go off exploring to investigate sounds. Because of these things it is a good idea to try and control their behaviour.

Most enemies are 'hidden' until needed. Do this directly after they are created with:

darfuncs.HideBadGuy("Ork1")

Remember to import the darfuncs file. This file is in the Lib folder.
Browse though some of the files you have imported so far. You can start to see how things work this way. darfuncs has a lot of useful functions in it. btw. You may have noticed in the Lib and Scripts folders a lot of files with the extension *.pyc. When a file is imported, Python makes a copy of it in compiled machine-readable code in order to read it faster. Later, when some of your own files need to be imported, you will see these .pyc files appearing in the installed copy of your map in the main Severance/Maps/MyMap folder.
You need pay no attention to these files. They look after themselves.
You don't need to include them in the release version of your map.

Ok you have hidden your Ork. He and his weapons will be totally invisible and he will not react to anything. To 'unhide' him use:

darfuncs.UnhideBadGuy("Ork1")

This call wil be contained in a function in your DefFuncs file, waiting for the event that triggers it.

Code: Select all

def UnHideOrk1(): darfuncs.UnhideBadGuy("Ork1") one function can do more than one thing... def UnHideOrkGroup1(): darfuncs.UnhideBadGuy("Ork1") darfuncs.UnhideBadGuy("Ork2") darfuncs.UnhideBadGuy("Ork3") darfuncs.UnhideBadGuy("GreatOrk6")
What you are doing is using a function to call other function(s). These functions in turn will call others and so on.

When the Ork is unhidden, he will be instantly visable and come out fighting if he sees you. You have to arrange things so that the event that unhides him happens when the Player cannot see the place where he is hidden. This event is usually a trigger sector. Sectors are coded in the engine to raise an event when anything enters or leaves them. A function can be assigned to these OnEnter and OnLeave events.
I'll come to this later. Also the unhide function call can be tagged on to other functions such as the opening of a door. This way, opening the door will unhide the Ork standing on the other side.

Now you are writing functions, a bit more theory. In most cases functions are always put in DefFuncs file. Don't put them in the .py files that you use to create stuff and don't create stuff in the DefFuncs file (unless within the body of a function). Doing these things will not affect anything in a 'live' game, but will certainly
foul things up in a saved game. Keep testing the savegame as you proceed. It can be a nightmare to sort out savegame bugs when you have a lot of code.

You made have noticed that some code is indented in the functions. This is part of the Python structure. It is important to be consistant with your indentation. One space is enough but using a Tab makes the code easier to read. You can use any text editor to write code. Notepad is OK, but as your files get bigger Windows will eventually tell you that the file is too big for Notepad and would you like to use Wordpad? A word of warning here. Wordpad is Ok but is not ideal. I have had trouble using Wordpad. Even if you stick to plain text it can foul up the code and I have had perfectly sound code
that won't work due to some invisible formatting introduced in Wordpad. If you are going to do some serious coding, get a proper code editor. (I use UltraEdit. Available for d/l on 30-day trial).

Back to the map. You have probably noticed in the game that some enemies have their own small territories. If you eneter a certain area
they will come and attack you but if you back off they abandon the attack and retreat. This is a nice feature to stop enemies wandering off and getting lost. To do this, you need to define action areas in the LED. An enemy needs two contiguous areas: Primary and Secondary.
You start him in the Primary area. He will venture into the Secondary area only to attack the Player, as long as the Player is in the Secondary area. Move out of Secondary area and the enemy will retreat.
(He may stand there making obscene gestures, but will not chase you.[:p])

To set these areas in LED, select a sector (or group of sectors). Goto properties window and scroll along to the 'Groups' tab. You see
32 little tick boxes. Like a lot of things in LED they are not logically numbered from 1-32.[}:)] Past 9 you have to count them but they do represent 1-32. Tick box 1 and press Enter in the usual fashion. Next. Select any sectors around the Primary one(s) that you want to use as the Secondary area. Tick box 2 in the Groups box, press Enter. You need to recompile the map for these groups to work.
A few observations here...[8)] You will realise that only 16 areas are possible. It <i>may</i> be that if another area is far enough away that you can repeat the groups numbers but I have never proved this. Make sure all the Secondary sectors join the Primaries. Don't leave even small gaps. Also beware sectors that are spilt horizontally so that an enemy has his feet in Primary area and head in an unassigned one above. They do not like it.[:)] In any case, try to avoid this arrangement of sectors in any places wher you intend to stage fighting. It can confuse enemy pathfinding and I have noticed that with Chaos Knights it can stop the using attacks altogether.
As with slopes, any moves you layer make to sectors that have action areas defined can cause the settings to be lost. If you move any of these sectors, check the AAs again.

Alright, you have your map recompiled with an action area. Create your enemies in the Primary area. Now you have to tell them where their own patch is. Add this code after creation code for area 1-2:

ork1.ActionAreaMin=pow(2,0) # Prim
ork1.ActionAreaMax=pow(2,1) # Sec

(** to use the pow func in the math file use:

from math import pow

You will notice something peculiar here. The 2nd arg is the area but
I have 0 and 1, not 1 and 2. This is back to the Python counting from 0 not 1 thing. Basically, whatever group number you assigned to a sector, represent it in the scripts by a number 1 less.

If you have a lot of areas, make a note in the enemies file.

# MainArena pri 1
# MainArena sec 2
etc....

* you may notice in scripts a lot of ######s Putting a # at the begining of a line makes it 'invisible'. Python will ignore it and anything that follows it om the same line. It is called a 'comment' in code terminology. This way you can add notes (or comments) to your
code. Also useful for temporarily disabling lines of code when debugging. If you want to add a lot of notes in a block or disable a lot of lines, add triple quotes at begining and end:

"""
lot
of
lines
here
....
....
"""
Python will skip the bit between the """s

Ok, now test the action area. If you have archers up on high walls, AAs are essential to stop them trying to jump off. Even so, archers
sometimes do fall. (There is a Knight in the Temple map who regularly does this. Once he his out of his AA he will go into a trance and try in vain to get back to his Primary AA.[:D])

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

Postby prospero » Sat Jul 23, 2005 5:16 am

Tut8. Trigger Sectors.

You can put the code for trigger sectors in any *.py file (NOT DefFuncs.py) I like to put them all in a seperate file (triggers.py?)

import Bladex

at the top and exec it in cfg.py as with others.

Ok, say you have an Ork 'hidden' around a corner. You want to 'unhide' him as the Player rounds the corner. Select a sector that the Player will enter out of veiw from the place where the Ork is 'hidden'. Note the sector coords. You need a point anywhere within the sector (somewhere near the middle?) . To assign a function use this:

sect_ork1=Bladex.GetSector(10000, -3000, -550000)
sect_ork1.OnEnter= UnhideOrk1

The details about the sector are contained in the variable sect_ork1
and the function UnhideOrk1 bound to it.

Define the function in DefFuncs:

Code: Select all

def UnhideOrk1(sector,entity): darfuncs.UnhideBadGuy("Ork1")
Notice that the function will expect two arguments. The Enter Sector
Event will pass two parameters to the function: the sector id and the id of the entity that enters. There is a glitch here. As the function stands, it will be triggered by any entity that enters the sector. This could be an wandering enemy, a stray arrow or flying limb. To cure this, it is time to introduce the concept of the 'if'.
---------------------------------------------------------------------


<font color="yellow">

Code: Select all

def UnhideOrk1(sector,entity): # first, 'get' the info on the entity and assign to a var per= Bladex.GetEntity(entity) # Check it is the Player if per == "Player1": darfuncs.UnhideBadGuy("Ork1") else: pass
</font id="yellow">
-------------------------------------------------------------------
<font size="4"><font color="red">Enormous Mistake Here</font id="red"></font id="size4">
The code above in <font color="yellow">yellow</font id="yellow"> is wrong.[:I] It should be:

Code: Select all

def UnhideOrk1(sector,entity): # Check it is the Player if entity == "Player1": darfuncs.UnhideBadGuy("Ork1") else: pass
Apologies to all who have been wondering why their Ork doesn't appear.
I have only used this code about a million times and I still screwed up. (There is a moral to this story, but I can't think what it is.)
[:I][:I][|)]

-------------------------------------------------------------------
The == is a 'comparision operator'.(NOT the same as =). It means 'is equal to'. Others are:

!= Is not equal to
> Is greater than
< Is less than

What happens is, the argument 'entity', that is the thing that enters the sector is compared after the 'if' to "Player1".
If it <i>does</i> equal "Player1" then the indented code that follows is executed and the Ork is unhidden. If it is not "Player1", then the comparision is False and Python will skip the code and move to the 'else', which in this case has a pass statement. This tells Python
to do nothing. (The 'else' clause is not strictly necessary. It can be omitted)

There is one other glitch with the function as it is. The function will be called everytime the sector is entered by the Player. This is
bad, because the Ork only needs to be unhidden once. Next time the sector is entered, he will probably be dead.

You must cancel the function after it is called:

Code: Select all

def UnhideOrk1(sector,entity): if entity == "Player1": darfuncs.UnhideBadGuy("Ork1") # Add this bit sect_ork1.OnEnter=None else: pass


That's it.

Another form of sector is the 'Ghost Sector' These areas in the map that are defined purely in script. You can use the tools in LED to define them and save the code settings, but they have no affect on the structure of the map. They are not compiled as info in the .bw file. They are defined in files with the ext .sf.
GS are used widely to assign sounds (wind, etc) to a wide area,
but they can be assigned OnEnter / OnLeave events like ordinary sectors. This is often convienient where map sectors don't quite fit in with what you want, of you need to cover a wide area that would need assigning triggers to many sectors (this can get complicated).
I'll come back to GS later.

btw. You can't assign two functions to the same sector. There is a function in darfuncs that is sometimes handy for triggers:

darfuncs.EnterSectorEvent(67400, 7000, 8750, DoSomething)

When the sector is entered (only by Player) is entered the function 'DoSomething' is called and the assignment cancelled. The advantage of using the first method is that sometimes you want other
NPcs to tigger things (if per== "Glofror") of you may want to assign another function to the sector later, once the first has been called.


More useful conditionals......

Code: Select all

def DemoFunc1(entity): per=Bladex.GetEntity(entity) # check race if per.Kind=="Ork": # Check he is still alive if per.Life>0: # Check his weapon if per.InvRight == "Ork1WP": DoSomething() elif per.Kind=="Knight_Traitor" and pers.Life>0: if per.Level<7: DoAntherThing() else: DoSomethingCompletelyDifferent() DemoFunc1("Enemy1")
In the above function an entity id is passed in the function call. His Kind is first checked. If he is an Ork his life is checked. If he is alive his weapon is checked. If all these conditions are True, the DoSomething func is called and the function ends. However, if he is not an Ork the first condition is False and Python skips to the 'elif' clause (you can have
lots of elifs). If the entity id a Knight_Traitor and he is alive, his
Level is checked and DoAnotherThing func is called and the function ends. But if the entity is neither Ork nor Knight, Python will skip these clauses and go to the else clause and DoSomethingCompletelyDifferent func is called.

Notice how clauses can be nested and several conditions can be put on one line using the 'and'. (Watch the indentation.[;)])

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

Postby prospero » Sat Jul 23, 2005 1:00 pm

Tut10. Sounds.[8D]

Things a bit quiet? Sounds and Music add alot of atmos to a map.

In mapping terms, Sounds and Music are different things. Music is not 'heard' in the map as such. It is like the music in a movie.
Sounds are localised and have a position in the map and are louder the nearer the Player is to them.

Sounds need to be created in a similar way to objects,etc. If you intend using lots of them, make a seperate file. Name it mysounds.py or sonidos.py (to keep it in its native language.[:)]) Yes, there is a source file Sounds.py [;)]. This is the code to create sounds:

Code: Select all

import Bladex snd_bang1=Bladex.CreateSound("..\\..\\sounds\\golpe-arma-escudo.wav","SoundBang1")
The filepath to the sound file in the Sounds folder is included in the
code. There are a lot of sound files and most of them have Spanish names, but you soon figure it out. (golpe = hit for example). Exec the file in cfg.py as before.

What happens after you have created the sound? Nothing [8)]. You have to play it with a function call:

snd_bang1.Play(49000, 6000, -58000,0)

This code is usually put in a function to correspond with an event.
The first three arguments are the map coords for the focus of the sound. The last 0 is the number of times the file will be played, in this case once. (put 1 and you get 2 bangs.[:p]). Some sounds are looped so that they can play continuously. For a looped sound (wind,waterfall,fire, etc) put -1.

Sometimes you want a sound to play at the Player position. You may not know where exactly the Player may be. You can 'get' the current Player position (and anything else for that matter) with this:

charpos=Bladex.GetEntity("Player1").Position
snd_bang1.Play(charpos[0],charpos[1]charpos[2],0)

You might be wondering why all the[]s have appeared. Why not just put charpos. Well, the variable charpos has three values stored. You have to unpack them. So charpos[0] acesses the first value (the X coord), etc. You could do it like this:

x,z,y=Bladex.GetEntity("Player1").Position
snd_bang1.Play(x,z,y,0)

Which is probably neater. There is usually more than one of doing things in Python.

Sounds have many Attributes. Some I am not sure exactly the purpose of. The useful ones are:

snd_bang1.Volume=0.8
snd_bang1.MinDistance=500
snd_bang1.MaxDistnce=50000

Which are self explanatary. (I am not sure whether sounds get any louder if you set the Volume greater than 1.0)

There is also Pitch, Which can be useful to vary sounds slightly.


There is a function that allows you to play sounds at random intervals.

Code: Select all

snd_crow1=Sounds.CreatePeriodicSound("..\\..\\sounds\\raven-call.wav","SoundCrow1",20,8,(40000,-2000,67000)) snd_crow1.PlayPeriodic()
It includes two extra args, 20,8. The sound will play at a random interval somewhere between 20 and 8 seconds. Set these values to your own preferences.

Music.

The backgroud music is all defined in a file 'MusicEvents.py'. This is a key file and must have that name for savegame integrity. Exec it as usual in cfg.py.

Code: Select all

import Bladex # for .wav files Bladex.AddMusicEventADPCM("Atmos24","..\\..\\sounds\\ATMOSFERA24.wav",1.0,1.0,1.0,1.0,0,-1) # for mp3s Bladex.AddMusicEventMP3(....rest is the same
There are 6 args follwing the filepath. In practice you only need pay attention to the last one. Like Sounds it controls the number of times the file is played. 0 for once, -1 continuous.

You start the music with this:

Bladex.ExeMusicEvent(Bladex.GetMusicEvent("Atmos24"))

this call is usually contained in a function.

To stop whatever music is playing:

Bladex.KillMusic()

You usually have to 'kill' whatever music is playing before starting a new track.

CombatMusic.

You can assign music to enemies that starts to play when they go into Combat mode. Use the same MusicEvents.py file but:


import DMusic

DMusic.AddCombatMusic("Ork1",..\\..\\sounds\\combate-hard.wav,400,1.0,1.0,1.0,1.0)

You can assign it to individual enemies or a whole Kind.

I haven't quite figured all the args yet, but the first is the priority. If you have lots of enemies in a group, set this value slightly lower for each one so that they don't all try to play their music at once.

If there was a piece of background music playing when you started fighting the Ork, it will not resume after he is dead. His combat music will stop and there will be silence. I made a little routine to restore the music (if any).

In DefFuncs:

Code: Select all

CurrentMusic="" # declare a variable and assign and empty string "" def ChangeMusic(music): Bladex.KillMusic() Bladex.ExeMusicEvent(music) global CurrentMusic CurrentMusic=music #then, start all music with: ChangeMusic("Atmos24")
A word on global variables. If you need to access a variable within the body of a function, you must declare it as a global or it will not be recognised. With this func you can change the music at any time and the identity of the music is stored in the CurrentMusic var.

Then if you happen to have to inerrupt the music for any reason, you can restore it with:

Code: Select all

def ResumeMusic(): global CurrentMusic Bladex.KillMusic() if CurrentMusic != "": Bladex.ExeMusicEvent(CurrentMusic) # to clear the var if necessary def StopMusic(): global CurrentMusic Bladex.KillMusic() CurrentMusic=""
This func is most likely to be called after the death of an enemy. Then, whatever music was playing when you started the fight will carry on after enemy is dead. For this you have to tap into his ImDead function. I'll come to that later.

You can either use the Sounds library, or you can make your own. To use your own stuff, just alter the filepath to your own file:

"..\\..\\Maps\\MyMap\\coolsound1.wav"
----------------------------------------------
[:D] big truck. Glad you like them, but don't spirit them away too
soon as they sometimes need de-bugging.[:I]

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

Postby big truck » Sat Jul 23, 2005 2:13 pm

Very, very good job Pro[8D].

Tuto 9 is gone with the wind[:D]

Again thanks for this BIG effort[:p]

[:)]

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

Postby big truck » Sat Jul 23, 2005 4:28 pm

<blockquote id="quote"><font size="1" face="Verdana, Arial, Helvetica" id="quote">quote:<hr height="1" noshade id="quote">----------------------------------------------
big truck. Glad you like them, but don't spirit them away too
soon as they sometimes need de-bugging.<hr height="1" noshade id="quote"></font id="quote"></blockquote id="quote">

Okay Pro[:)], I'll wait 2-3 days before uploading all these next good donuts on my site[:D]

However, don't hesitate to re-read the pages on my site to verify if an update is necessary, and send me a message if so[:)]. Or, you can access and write yourself directly on these pages if you prefer (see my last email).

Again, really good job[8D].

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

Postby prospero » Sat Jul 23, 2005 5:12 pm

Tut 11. Doors.

There are two methods of making doors. The first uses a sliding sector bulit into the map in the LED. The second uses an object which is moveable. I will refer to the latter as Gates rather than Doors. Most Gates in the game use the 'Portcullis' type object. ("Rastrillo")

Doors need carefull construction in the LED. A simple one would be a doorway split into three sectors. The middle sector will be the sliding part. The outer two form the frame. A sliding sector needs to ajoin one sector only or you get weird stuff happening. Remember this if you want to make double doors. Each half should match it's own outer sector. Don't be tempted to bridge the two halves with one big outer sector. You can arch the outer sectors, but keep the sliding part square. You will need a set of coords from some point within the slide sector.

Time for a new file. puertas.py (There is a source file Doors.py).
Exec in cfg.py as per usual.

import Doors
import Levers
import Locks
import Objects
import Sounds
import Sparks
import darfuncs
import Stars

Before making the door, define a few sounds at the top of file:

Code: Select all

snd_doorslidewood=Sounds.CreateEntitySound("..\\..\\Sounds\\puerta-madera-deslizando.wav","WoodDoorSlide") snd_doorhitwood=Sounds.CreateEntitySound("..\\..\\Sounds\\puerta-madera-golpe.wav","WoodDoorHit")
The first sound is played while the door is sliding. Second is the clunk at the end.

Now create the door:

Code: Select all

door1=Doors.CreateDoor("Door1",(3000,-5000,7000),(0,1,0),500,6000,Doors.CLOSED) door1.opentype=Doors.UNIF door1.o_med_vel=-500 door1.o_med_displ=5500 door1.closetype=Doors.AC door1.c_init_displ=5500 door1.c_med_vel=1000 # assign the sounds you defined above door1.SetWhileOpenSound(snd_doorslidewood) door1.SetWhileCloseSound(snd_doorslidewood) door1.SetEndOpenSound(snd_doorhitwood) door1.SetEndCloseSound(snd_doorhitwood)
You will see that there are a lot of parameters associated with a door.

From the top you have the individual name "Door1".
Next you have the coords of the sector in ()s
Next another () with three args 0,1,0. This sets the direction the door will slide. 0,1,0 makes it slide up from closed position. 0,0,1 would make it slide sideways along the Y axis. 1,0,0 along the X axis.
Making the 1 negative in each case will make it go in the opposite direction along the same axis. (No, you can't make them go diagonally.[:(]) The next arg '500' is the amount that will be left sticking out when the door is closed. The next is the dimension of the sector on the axis the door is set to slide along. In this case 6 LED units which translates to 6000 in the script.
The last is fairly obvious. Doors.CLOSED means the door is created closed. Put Doors.OPENED (not OPEN) and it will be created in the open position.
Below are more settings (not all of which I fully understand)
Ignore the first Doors.UNIF (Always use this.)
o_med_vel sets the speed it will open
o_med_displ .To get this value, subtract the amount you set above to have sticking out when open from the dimension of the sector you also set above. 6000-500=5500
Ignore the Doors.AC

the next two with the prefix c are the same as the ones with prefix o
but they do the same jobs when the door closes.

It may seem all very confusing at this point but stick with it.[|)]

The code that handles the doors is very powerful and can make them do all sorts of things, but for the mo use the above code as a template.

Before doing anything else, load the map and go to your door. It should be shut. Have a good look from both sides and see if you notice any weird effects. A word about textures here. It is tricky to assign textures to doors. Even if you align the texture with OGL veiver before making the slide part, you will find that the texture can move when the door code is applied. A bit of guesswork is usually needed.[;)]

If all is well you now need a means of opening it.

Usual method is a lever and the usual lever is the familiar "Zocalo3".
A lever has two parts: the plate and the stick. You need only get coords for the plate. The lever is made automatically when you apply the lever data. For accuracy, you should ideally use the Entity Browser to get a good placement, but for now we will use 'dead reckoning. On your .mp file, place you cursor on the wall roughly where you want the lever and note the XY coords. You need it about 1 meter up from the floor. For Orientation use these:

(0.5,0.5,0.5,-0.5) # to face west
(0.5,0.5,-0.5,0.5) # east
(0.0,0.0,0.5,-0.5) # north
(0.707,0.707,0.0,0.0)# south

If you want it diagonal, you really need the EBrowser. btw. if you look carefully there are some oily dribbles on the plate. Keep these going down to get it the right way up. The stick part will operate in the wrong direction otherwise.

OK create the lever with:

Code: Select all

door1lvr=Levers.PlaceLever("Door1Lever", Levers.LeverType3,(5000,-1000,-4000),(0.707,0.707,0.0,0.0),0.8) door1lvr.<font color="yellow">Mode</font id="yellow">=1 door1lvr.OnTurnOnFunc=OpenDoor1 door1lvr.OnTurnOnArgs=() door1lvr.OnTurnOffFunc=CloseDoor1 door1lvr.OnTurnOffArgs=()
---------------------------------------------------------------------
<font color="yellow">** Another mighty error here. The attribute 'Mode' in the above example should be writtem as 'mode' (lowercase). Thanks to Raptor for pointing that out. </font id="yellow">
---------------------------------------------------------------------

So you have the individual name of the lever, the type of lever, the coords of the plate and the Orientation of the plate. The last arg, 0.8 is for the scale. BUT as far as I can tell this is not used. All the levers will be scale 1.0. However, the syntax demands that a value must be put here, so leave it as it is and forget it.#

The mode affects how the lever operates. 1 will make it spring back,
set to 0 and it will stay down when operated.
Next, the open/close functions are assigned. Define these in DefFuncs:

Code: Select all

def OpenDoor1(): door1.OpenDoor() def CloseDoor1(): door1.CloseDoor()


You will notice that you can put in arguments to be passed with the function if you want. Having said that I have yet to find an example in the game where this is used. You can probably omit these two lines.(?)

OK. You should now be able to open and close your door.[:D] Don't be downhearted if it doesn't work first time. Doors are quite complex and lots of things can go wrong.[xx(]

Other Levers.

If you use other levers, you will have to put the type in the
Levers.LeverType3 bit.

LeverType3 is the "Zocalo3" type plate

LeverType1 is the "Zocalo1"
LeverType2 is the "Zocalo2"
LeverTypeFloor is the "ZocaloSuelo" # not used in game, but they do work.
LeverTypeSnake is the "ZocaloSerpiente" # not used in game and I have never tried them.

I'll do Locks when I do Gates.[:D]

Next Lesson....De-Bugging. [:0]

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

Postby prospero » Sun Jul 24, 2005 2:03 am

Tut12. De-bugging.[B)]

When you start coding, you get bugs. Python is very unforgiving and little things can cause errors. A , missed will cripple the whole process. When the map loads, Python will read all the files in the order they were executed. If it finds an error it stops there. The map will continue to load, but all the code past the error will not be exec'd. This in itself gives you a clue to the error: if you get all your objects but enemies are missing then you can deduce that the objects file is OK but the error is in the enemies file.

Console

How to enable the console is covered in Josh's Blade Mods sitehttp://www.dahlby.net/games/blade/] I'll just add a little note on the filepath. After the exe at the end TYPE A SPACE before adding -console. The space is critical.

With the console enabled you get a readout of everything that is going on in the course of a map. To get more use print statement in your code:

print "The function was executed"

This will appear in the console. If you put this at the end of a block of code, you know that the code is sound. If it doesn't appear when it should, you know that the error is before the print statement.
If you do this:

Code: Select all

def Func1(): print "Starting to exe func1" *lots of strange code here* print "func1 done"
If you get the first statement but not the second, you know that the error is somewhere inbetween. The console will sometimes tell you what is wrong and give the file and line number of the offending code.
Sometimes this is helpful, sometimes slightly cryptic.[;)] If you get a complete crash to the desktop, you loose the console and can't see what happened last that might have caused the crash.

There is a neat method whereby the console output can be saved to a .txt file.
Open up the file Lib/ConsoleOutput.py. There is a var DEBUG_FILE. It will be set to 0. Change the 0 to 1.

DEBUG_FILE = 1

---------------------------------------------------------------------
WHOOPS!

[:0] I have just realised that there is more to the ConsoleOutput.py.

This is the complete code for the file:

Code: Select all

import Console import sys DEBUG_FILE=1 class ConsoleOutput: softspace=0 def write(self,message): if message is None: Console.ConsoleOutput("None") else: if DEBUG_FILE: dbg=open("Debug.txt","at") dbg.write (message) dbg.close() Console.ConsoleOutput("None") def flush(self): pass def InitConsole(): ConsoleOut=ConsoleOutput() sys.stderr=sys.stdout=ConsoleOut
--------------------------------------------------------------------

At the same time enable the Debug mode in Lib/Reference.py. There are two vars DEBUG_INFO and PYTHON_DEBUG (lines 24/25). Set DEBUG_INFO to 1 and PYTHON_DEBUG to 2.

This will give you lots of messages in the console and also enable certain development aids. (Or 'cheats' as some incharitable people call them.[;)]) The T key will cycle though various onscreen readouts.
The first is the most useful. It gives Framerate, current Player Position and Mission Time. While in this mode The K (kill),P (freecamera) and F10 (godmode) keys are enabled. Other keys are now active but are not quite so useful. If you play main RAS maps in this
mode be careful not to hit the Numpad keys. They will teleport you about. Later, when your map has grown to enormous size, you can make a Positions.py file to take advantage of this feature. It saves having to fight your way all though the map just to test a bit near the end.

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

Postby prospero » Sun Jul 24, 2005 2:47 am

Tut13. Sun,Water,Footsteps.

You will have noticed on some maps the sun is visible and casts nice rays. If you would like this in your map it is eeeeeeeeeze.[:D]

In your DefFuncs.py file add:

Bladex.SetSun(1,-10,8,16)

I have no idea what the first arg, 1 is for so just leave it as 1 and forget it. The last 3 args should match the settings of the External light in the LED. The position of the sunflare will then be consistant with all the cast shadows. btw. In the RAS maps the sun is created in a file sol.py. For some reason the sunflare is not saved in memory like all the entities and is absent in the saved games. If you put the code in DefFuncs it will be recreated as the savegame loads. The sunflare is optional. It is purely cosmetic and does not emit light.

Water.

Water is easy enough. You just have to be careful with your coords to avoid floods. In the LED, move cursor to where you want a pool. Note X and Y readings. You need a 'seed point' which can be anywhere within where you want a wet bit. The water will flow out in the manner
of real water (watch for leaks.[8)]) The critical part is the Z reading as this sets the water level. Make sure it doesn't overflow or it can be very embarassing.[:I] The code is:

Code: Select all

pool1= Bladex.CreateEntity("Pool1","Entity Water",-7000,500,43000) pool1.Reflection=0.7 pool1.Color=0,0,0 #red,green,blue
Tweak the Color (0 to 255) and Reflection (0 to 1) to your preference.
If you have a lot of pools, you can make a seperate file, or you can put the pools in objs.py.


Footsteps.

This is a very cool feature of the game that allows you to assign different footstep sounds to various textures. Also, it affects the sound made when a surface is hit. (Arrows hit snow with nice crunch.)

Make a file 'TextureMaterials.py'. This is a Key file and must have that exact name. Shortcut here: Copy this file intact from one of the RAS maps and Paste it into your own map. If you using a texture set from a particular map, grab the one from that map. You need not edit it then.[;)] Exec it in cfg.py. Useful tip.[8)] Keep this file at the end of the execs so it is loaded last. If you have footstep sounds when the map is loaded, you then know that there are no errors in the other files.


Return to “Severance BoD: Modding Community”

Who is online

Users browsing this forum: No registered users and 5 guests

cron