BigTruck's Fan Page

BOD Python Scripting

19 - New Races

Ok. You asked for it. 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.

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.)

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. 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. At this point you can make him a skin using the material names:

KGTAF
KGTAR
KGTAF_W
KGTAR_W


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:
testknight.SetMesh("KgtSkin1")
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:

BODLoader/Mods/MyMap/MyMap

You need two new files:

BLModInit.py
BLModInfo.py


To save a lot of time, look at these two files in 'ProsperoArena1' 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 'ProsperoArena1', replace this with MyMap name. Don't change anything else; just this phrase.
			import MyMapMenu
			global ModMenu
			ModMenu=MyMapMenu.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_A model in ProsperoArena1. Edit this block of code to load your new Knight. In this case, just replace 'Amz' with 'Kgt' and 'Amazon' with 'Knight'.

* It occurs to me that sooner or later two people are eventually going to make a models with the same name.. 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.)

Next the BLModInfo.py file

Again use the ProsperoArena1 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 ProsperoArena1 with MyMap name. This map loads two new chars. Yours has only one at present so delete one of the 'Stuff' lines and edit the other one:
			MakeDirs=['../MyMap'
					  '../MyMap/Spanish' # you might need this later
					  '../MyMap/NUFiles'
					  '../MyMap/NUFiles/NUMyNewKnightStuff'
					  '../MYMap/pak']
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 NewFiles[]).

* 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.). Once installed it needs to stay there and not be uninstalled again.

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....


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)
PlayerName="Dave"
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
pass#
in front of the line:
char.Data=PlayerTypes.SuperZoe(char)
(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. UNLESS 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'.


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
			
			sys.path.append("../../Maps/MyMap/NUFiles")
			sys.path.append("../../Maps/MyMap/NUFiles/NUDaveStuff")
(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.py 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. 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:

DaveAnm_def.py

type
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.

Next step, the thrilling installment.
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:
			Bladex.LoadSampledAnimation("..\\..\\Anm\\Kgt_rlx_no.BMV","Rlx_no_Kga",1,"Knight_A")
			Bladex.AddAnmRStep("Rlx_no_Kga",0.0)
			Bladex.AddAnmLStep("Rlx_no_Kga",0.0)
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

Now just to make things awkward some blocks of code are written differently. they begin:
anm_name="Whatever"
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:
\\Kgt_jogb_1H".BMV,anm_name,1,"Knight_A")
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. 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!

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.

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.
			import Bladex
			import XtraAnimSounds
			import Basic_Funcs
			import PlayerTypes

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

					me.MeshName="KgtSkin1"

				def ResetSounds(self,EntityName):
					me=Bladex.GetEntity(EntityName)
					XtraAnimSounds.AsignarSonidosDave(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:
			if PlayerName == "Dave":
				char.Data=PlayerTypes.Dave(char)
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.

Almost anything. 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.

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).

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:

(Combat.TraitorKnightAttackData)

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:
			def ResetSounds(self,EntityName):
				me=Bladex.GetEntity(EntityName)
				XtraAnimSounds.AssignarSonidosDave(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 Combat in the ResetCombat func to give:
me.AttackList=BCopy.deepcopy(DaveAttackData)
Rename the combat chart, DaveAttackData

Also you now need to refer back to things in Combat.py, so add Combat. beefore all the words BLOCK / DODGE / ATTACKDOWN / ATTACK / MOVE

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
Combat.TempMoveInProc
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:
dave1.Data=MyMapTypes.Dave(dave1)
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.

Allies.

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.) To use this data is easy. In your NPC class, use
Ally_Def.NPCAlly
instead of
Enm_Def.NPCPerson