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