So I had some time looking at code but couldn't test it. So I decided to figure out something which was bugging me for a while already.
So let me tell you the steps which a character goes trough before it enters the game.
First the character array is populated like this:
This forloop will create the character array and it calls the InitChracter(ch) which looks like this:
So this function sets some variables already. Because no character is loaded yet all these checks will be true so all these values will be set 2250 times (the total character amount).
After the array is populated it will start with doing all the stuff for the main character which is okay I guess.
After that it will fill the character array like this:
These lines will load the init files for the different locations and call the AddGameCharacter for each of these characters.
This function looks like this:
Notice here the "InitCharacter(chr);" is called again. So even tough this was called before it's now called again. This is only one of the things which is done double. You'll also notice some Soldier specific things in this function and some more checks to see if everything is set.
After all characters are loaded the following functions are called:
The SetAllShipData and SetAllFellows both loop trough all the characters again and set some respective functions for all of them, personally I think this could be easily added somewhere else.
The DoCharactersPostInit runs trough all characters again and calls this function for all of them:
You'll see here it will set some animation data and the facemaker data and also gives everyone a sword and pistol if they don't have one already (and equip it).
Now the init of the character is done
All this stuff happens on the first game loading. Now the character is treated again once it will first load into a location.
We start with this function
For some reason there is a acLoadFlag check here which I haven't figured out what it would do exactly. From what I can see it makes sure something else happens in these functions if they are called after the loading of a location. For some reason one of the loops uses MAX_CHARACTERS while the other uses 32.
The first loops loads in the normal characters while the second one loads in fantoms. these are random characters which can be created by things like the vice city mod.
It calls this function for every character which is logged in:
It first checks some things if you are the main character and if not it will call this function:
it will first check for some situations where the character is allowed to login or not (login means it can enter the location).
Then it will see if the maximum of characters is already achieved, if not it will fail. For some reason the maxHP is also set if we are not in a boarding situation...
After that some of the AI stuff is set up. And then it goes into a calculation where the hp is calculated.
It calls for the restorecharacter function, this one is used from what I see to restore a character if it was killed before.
If the login was succesfull it will call the createcharacter function
The first part of this function will check some animation data again and have the character equip some stuff again.
After that is a part from me for the setup which can be completely removed I think because this is called again lateron, I have no idea why this is still in here
.
After that are some weird functions about the fight level which I don't completly know what they do.
Finally during the login this function is called:
This should give the character some weapons if they don't have one already (which I believe has been done twice by now).
After all this is done it will go into the postinit phase which is called like this:
A few more ai things are being called here and here is the main leveling function called.
Now let's see what more happens in leveling.
If this is the first time the character is loaded it will call:
InitCharacterSkills
This function will look at all the skills and determine if they fit the rules for leveling and set the right importances etc. It will then see which level the character has to be and add the right amount of experience to this character so the experience for the skills is filled and perks are chosen. But before it does all this it will first check the character to see if everything is set up right:
And to finish it off it will call these function:
So the HP is set right (for the level) and the right equipment is equiped
So you see a lot of stuff is being done double, and the code is one big mess now. I've mostly posted this here so nobody else has to look trough this spagethi again.
I hope we could make this a bit more easy to read and remove a lot of duplicated code so it become faster.
A lot of the checks are now being done during the init and once again during the login. I think these can all go to the login so the init becomes faster. but we can also do the the other way around so loading between area's will become faster.
So let me tell you the steps which a character goes trough before it enters the game.
First the character array is populated like this:
Code:
void CreateCharacters()
{
//int tmpNameFileID = LanguageOpenFile("characters_names.txt");
ref ch;
int n;
string curskill;
for(n=0; n<TOTAL_CHARACTERS; n++)
{
makeref(ch,Characters[n]);
//DeleteAttribute(ch,"act");
DeleteAttribute(ch,"");
//Logic data
ch.id = "0";
ch.index = n;
InitCharacter(ch); // KK
}
Code:
void InitCharacter(ref ch)
{
if (!CheckAttribute(ch, "name")) {
ch.name = "";
ch.old.name = "";
}
if (!CheckAttribute(ch, "lastname")) {
ch.lastname = "";
ch.old.lastname = "";
}
if (!CheckAttribute(ch, "sex")) ch.sex = "man";
//Model data
if (!CheckAttribute(ch, "model")) ch.model = "none";
if (!CheckAttribute(ch, "model.entity")) ch.model.entity = "NPCharacter";
if (!CheckAttribute(ch, "model.animation")) ch.model.animation = "";
//Address
if (!CheckAttribute(ch, "location")) ch.location = "none";
if (!CheckAttribute(ch, "location.group")) ch.location.group = "";
if (!CheckAttribute(ch, "location.locator")) ch.location.locator = "";
if (!CheckAttribute(ch, "location.from_sea")) ch.location.from_sea = "";
//Ship data
if (!CheckAttribute(ch, "Ship.Type")) ch.Ship.Type = SHIP_NOTUSED_TYPE_NAME;//SHIP_LUGGER; // PS
//Quest data
if (!CheckAttribute(ch, "Dialog.CurrentNode")) ch.Dialog.CurrentNode = "First time";
if (!CheckAttribute(ch, "Dialog.TempNode")) ch.Dialog.TempNode = ch.Dialog.CurrentNode;
if (!CheckAttribute(ch, "quest")) ch.quest = "True";
if (!CheckAttribute(ch, "quest.meeting")) ch.quest.meeting = "0";
//Misc data
if (!CheckAttribute(ch, "nation")) ch.nation = ENGLAND;
if (!CheckAttribute(ch, "freeskill")) ch.skill.freeskill = 0;
if (!CheckAttribute(ch, "rank")) ch.rank = 1;
if (!CheckAttribute(ch, "reputation")) ch.reputation = REPUTATION_NEUTRAL;
// battle hp
if (!CheckAttribute(ch, "headModel")) ch.headModel = "capitan";
if (!CheckAttribute(ch, "location.stime") || !CheckAttribute(ch, "location.etime")) {
float stime = 6.0;
float etime = 21.98333;
if (CheckAttribute(ch, "location.stime")) stime = stf(ch.location.stime);
if (CheckAttribute(ch, "location.etime")) etime = stf(ch.location.etime);
LAi_SetLoginTime(ch, stime, etime);
}
if (!CheckAttribute(ch, "chr_ai.hp") || !CheckAttribute(ch, "chr_ai.hp_max")) {
float hp = LAI_DEFAULT_HP;
float hp_max = LAI_DEFAULT_HP_MAX;
if (CheckAttribute(ch, "chr_ai.hp")) hp = stf(ch.chr_ai.hp);
if (CheckAttribute(ch, "chr_ai.hp_max")) hp_max = stf(ch.chr_ai.hp_max);
LAi_SetHP(ch, hp, hp_max);
}
if (!CheckAttribute(ch, "AbordageMode")) ch.AbordageMode = 1; // boal 05.09.03 officer need to go to abordage
if (!CheckAttribute(ch, "isSoldier")) ch.isSoldier = false;
}
After the array is populated it will start with doing all the stuff for the main character which is okay I guess.
After that it will fill the character array like this:
Code:
n = CreateGameCharacters(n, "SideQuest"); ReloadProgressUpdate(); // PB: Initialize first so that all other files can override this
n = CreateGameCharacters(n, "Officers"); ReloadProgressUpdate(); // PB: Initialize first so that all other files can override this
n = CreateGameCharacters(n, "FalaiseDeFleur"); ReloadProgressUpdate();
This function looks like this:
Code:
void AddGameCharacter(ref n, ref ch)
{
int chridx, iNation;
string model;
ref chr;
if (!CheckAttribute(ch, "id")) return;
chridx = GetCharacterIndex(ch.id);
if (chridx >= 0) {
makeref(chr, Characters[chridx]);
} else {
makeref(chr, Characters[n]);
chridx = n;
n++;
}
DeleteAttribute(chr, "");
CopyAttributes(chr, ch);
chr.index = chridx;
InitCharacter(chr);
DeleteAttribute(ch, "");
if (!CheckAttribute(chr,"quest.officerprice")) chr.quest.officerprice = 0;//PB
if (isSoldier(chr) != UNKNOWN_NATION) {
chr.model.uniform = chr.model;
iNation = sti(chr.nation);
model = "m" + sti(chr.model);
if (iNation >= 0 && iNation < NATIONS_QUANTITY) model = Nations[iNation].fantomModel.(model);
if (iNation == PERSONAL_NATION) model = characters[GetMainCharacterIndex()].Soldier.(model);
SetModel(chr, model, chr.sex, chr.sex, 0.0, false);
GiveSoldierWeapon(chr, iNation); // ARF: Update Soldier Weapons
// Baste: Strong Soldiers -->
// chr.rank = 11+GetDifficulty()*3+rand(6*GetDifficulty()); // Landlubber: between 14 and 20 - Swashbuckler: between 23 and 47
// Baste: Strong Soldiers <--*/
}
if (chr.name == "" && chr.lastname == "") {
SetRandomNameToCharacter(chr);
chr.old.name = "";
chr.old.lastname = "";
}
//We want some more variance in characters so give them all a random level.
if(sti(chr.rank) < 2) chr.rank = rand(12 + GetDifficulty()*3);
if(sti(GetStorylineVar(FindCurrentStoryline(), "RESET_HP")) < 1) ResetHP(chr); // PB: To initialize HP for character level + HP bonus
}
Notice here the "InitCharacter(chr);" is called again. So even tough this was called before it's now called again. This is only one of the things which is done double. You'll also notice some Soldier specific things in this function and some more checks to see if everything is set.
After all characters are loaded the following functions are called:
Code:
SetAllShipData(); // PB: If we make sure this is done FIRST, then it shouldn't need to occur in mid-game anymore
SetAllFellows(); // PB: Just in case
//Post init
// do in PostInit()
DoCharactersPostInit();
The SetAllShipData and SetAllFellows both loop trough all the characters again and set some respective functions for all of them, personally I think this could be easily added somewhere else.
The DoCharactersPostInit runs trough all characters again and calls this function for all of them:
Code:
bool Character_PostInit(int n)
{
if(n >= CHARACTERS_QUANTITY) return false;
ref rCharacter = GetCharacter(n);
// NK 05-03-31 check to see if we're overwriting an animation -->
if(!CheckAttribute(rCharacter,"model.animation") || rCharacter.model.animation == "")
{
// changed by MAXIMUS [for AOP models] -->
if(rCharacter.sex == "woman")
{
if(StraifCharacter(rCharacter)) rCharacter.model.animation = "new_woman";
else rCharacter.model.animation = "towngirl";
// KK -->
rCharacter.model.height = WOMAN_HEIGHT;
rCharacter.capacity.max = WOMAN_CAPACITY;
// <-- KK
}
else
{
if(StraifCharacter(rCharacter)) rCharacter.model.animation = "new_man";
else rCharacter.model.animation = "man";
// KK -->
rCharacter.model.height = MAN_HEIGHT;
rCharacter.capacity.max = MAN_CAPACITY;
// <-- KK
}
// changed by MAXIMUS [for AOP models] <--
}
// NK <--
rCharacter.FaceGroup = 0;
// set fellows
if (CheckAttribute(rCharacter,"fellows")) { SetBaseFellows(rCharacter); }
// set base ship data
// SetBaseShipData(rCharacter); // PB: This is already taken care of by the CheckCharacterSetup function now
facemaker(rCharacter); // NK
//rCharacter.headModel = "h_" + rCharacter.model; // NK called by above, unneeded (4-30)
if(CheckAttribute(rCharacter,"blade"))
{
if(CheckAttribute(rCharacter,"blade.itemID"))
{
rCharacter.equip.blade = rCharacter.blade.itemID;
}
else
{
rCharacter.equip.blade = "blade4";
}
DeleteAttribute(rCharacter,"blade");
}
if(CheckAttribute(rCharacter,"gun"))
{
if(CheckAttribute(rCharacter,"gun.itemID"))
{
rCharacter.equip.gun = rCharacter.gun.itemID;
}
else
{
rCharacter.equip.gun = "pistol1";
}
DeleteAttribute(rCharacter,"gun");
}
if(CheckAttribute(rCharacter,"spyglass.itemID"))
{
rCharacter.equip.spyglass = rCharacter.spyglass.itemID;
DeleteAttribute(rCharacter,"spyglass");
}
// NK equip all items 05-03-31 -->
EquipCharacterByItem(rCharacter, FindCharacterItemByGroup(rCharacter,BLADE_ITEM_TYPE));
EquipCharacterByItem(rCharacter, FindCharacterItemByGroup(rCharacter,GUN_ITEM_TYPE));
EquipCharacterByItem(rCharacter, FindCharacterItemByGroup(rCharacter,ARMOR_ITEM_TYPE));
// NK <--
return true;
}
You'll see here it will set some animation data and the facemaker data and also gives everyone a sword and pistol if they don't have one already (and equip it).
Now the init of the character is done
All this stuff happens on the first game loading. Now the character is treated again once it will first load into a location.
We start with this function
Code:
void LoginCharactersInLocation(ref loc)
{
int i;
string locID = loc.id;
LocAi_Init(loc);
for (i = 0; i < MAX_CHARACTERS; i++)
{
if (loc.type == "Dungeon" && IsOfficer(&Characters[i]) && CheckAttribute(&Characters[i], "AbordageMode") && makeint(Characters[i].AbordageMode) == 0) {
continue;
} // NK based on BOAL 05-05-01 dungeons count as boarding, Swindler added CheckAttribute() to avoid runtime errors
if (loc.type == "Deck" && IsOfficer(&Characters[i]) && CheckAttribute(&Characters[i], "AbordageMode") && makeint(Characters[i].AbordageMode) == 0) {
continue;
} // KK
LoginCharacter(&Characters[i], locID);
ReloadProgressUpdate(); //Levis
}
if(actLoadFlag)
{
for(i = 0; i < 32; i++)
{
LoginCharacter(&Characters[LOC_FANTOM_CHARACTERS + i], locID);
}
}
LocAi_PostInit(loc);
if(!actLoadFlag) Build_again(loc); // ccc building kit, restores buildings
}
The first loops loads in the normal characters while the second one loads in fantoms. these are random characters which can be created by things like the vice city mod.
It calls this function for every character which is logged in:
Code:
void LoginCharacter(aref chr, string locID)
{
if(CheckAttribute(chr,"condition") && chr.condition=="reload") { DeleteAttribute(chr,"condition"); }//MAXIMUS: for skipping dialog with character
if(IsMainCharacter(chr))
{
// ResetTimeToNormal();//MAXIMUS: removes time-acceleration and sets normal time
if(!CheckAttribute(chr,"systemStatus")) { chr.systemStatus = "up"; systemStatus = chr.systemStatus; }//MAXIMUS: for several things
if(CheckAttribute(chr,"TrainingFight")) DeleteAttribute(chr,"TrainingFight");//MAXIMUS: if player leaved ShipDeck before his training was finished - this will help :)
if(CheckAttribute(chr,"location.first"))//MAXIMUS: checking fastreload on tutorial deck
{
if(HasSubStr(locID,"tutorial") || HasSubStr(locID,"deck") || HasSubStr(locID,"cabin"))
{
DisableFastTravel(true);
if(HasSubStr(locID,"ShipDeck")) Whr_UpdateWeather(false);
else WhrDeleteRainEnvironment();
}
else
{
DeleteAttribute(chr,"location.first");
DisableFastTravel(false);
Whr_UpdateWeather(false);
if(CheckAttribute(chr,"newGameStart") && chr.newGameStart==true) DeleteAttribute(chr,"newGameStart");
}
}
if(!CheckAttribute(&locations[FindLocation(locID)],"fastreload") || locations[FindLocation(locID)].fastreload!="ship") ClearDeck();//MAXIMUS: I'd tried to find a better place, but wasn't be able
}
if(LAi_CharacterLogin(chr, locID))
{
bool isNoCreated = true;
if(CreateCharacter(chr))
{
isNoCreated = false;
if(TeleportCharacterToLocator(chr, chr.location.group, chr.location.locator)==0)
{
if(IsMainCharacter(chr))
{
// ccc Greater Oxbay mod. To enable entry to non-reload-group locators
/* if(chr.location.locator=="randitem2") chr.location.group = "randitem";
else{if(chr.location.locator=="Goto2") chr.location.group = "goto";} // for Greenford suburb
else{if(chr.location.locator=="Goto4") chr.location.group = "goto";} // for shipyard ladder*/
// JRH: Rewritten code to be generic -->
chr.location.group = "goto"; //default locatorgroup
if(HasSubStr(chr.location.locator, "randitem")) chr.location.group = "randitem";
if(HasSubStr(chr.location.locator, "monster")) chr.location.group = "monsters"; //for all dungeons
// JRH: Rewritten code to be generic <--
TeleportCharacterToLocator(chr, chr.location.group, chr.location.locator);
// ccc end
traceif("Main character <" + chr.id + "> error teleportation by location: " + chr.location + " on locator: " + chr.location.group + "::" + chr.location.locator);
}else{
isNoCreated = true;
DeleteCharacter(chr);
traceif("Delete character <" + chr.id + "> , error teleportation by location: " + chr.location + " on locator: " + chr.location.group + "::" + chr.location.locator);
}
}
SetUpCharacterWeapons(chr); //Levis moved this stuff here so it's only checked for loged in characters
SetUpCharacterNationName(chr); //Levis moved this stuff here so it's only checked for loged in characters
}else{
traceif("Can't create character: " + chr.id);
}
if(isNoCreated)
{
LAi_CharacterLogoff(chr);
return;
}
}
// <-- KK
}
It first checks some things if you are the main character and if not it will call this function:
Code:
bool LAi_CharacterLogin(aref chr, string locID)
{
string func;
//Проверим адрес логина
if(CheckAttribute(chr, "location") == false)
{
Trace("Character <" + chr.id + "> have not field [location]");
return false;
}
if(CheckAttribute(chr,"storedAttributes") && CharacterRebirth(chr)==false) { DeleteCharacter(chr); return false; }
bool isLogin = false;
// 04-12-05 this is making things worse bool always = false; // NK 04-11-10 override
if(chr.location == locID)
{
if(sti(chr.index) == GetMainCharacterIndex()) isLogin = true;
if(CheckAttribute(chr, "location.stime") != false)
{
if(CheckAttribute(chr, "location.etime") != false)
{
//Проверям время логина
if(LAi_login_CheckTime(stf(chr.location.stime), stf(chr.location.etime))) isLogin = true;
}else isLogin = true;
}else isLogin = true;
}
//Проверяем возможность нахождения в церкви
if(CheckAttribute(chr, "location.church") != false)
{
if(chr.location.church == locID)
{
//Проверям время нахождения в церкви
if(CheckAttribute(chr, "location.church.stime") != false)
{
if(CheckAttribute(chr, "location.church.etime") != false)
{
//Проверям время нахождения в церкви
if(LAi_login_CheckTime(stf(chr.location.church.stime), stf(chr.location.church.etime))) isLogin = true;
}
}
}
}
//Залогинем последователей
if(!LAi_IsBoarding)
{
// 04-12-05 this is making things worse if(CheckAttribute(chr,"alwaysload")) { if(sti(chr.alwaysload)) { isLogin = true; always = true; } } // NK 04-11-10 add alwaysload override
if(CheckAttribute(chr, "location.follower") != false)
{
ref mcref = GetMainCharacter();
chr.location = mcref.location;
chr.location.group = mcref.location.group;
chr.location.locator = mcref.location.locator;
isLogin = true;
if(LAi_restoreStates)
{
LAi_SetCurHPMax(chr);
}
}
}
//Проверим на захваченность локации
if(LAi_IsCapturedLocation)
{
if(GetMainCharacterIndex() != sti(chr.index)) // 04-12-05 this is making things worse && !always) // NK 04-11-10 allow override
{
for(int i_ofc = 1; i_ofc < OFFICER_MAX; i_ofc++)
{
int idx = GetOfficersIndex(GetMainCharacter(), i_ofc);
if(idx == sti(chr.index)) break;
}
}
}
if(!isLogin) return false;
//Если персонажей больше максимального числа, незагружаем больше
if(LAi_numloginedcharacters >= 32)
{
Trace("LAi_CharacterLogin -> many logined characters in location (>32)");
return false;
}
//Устанавливаем необходимые поля, если надо
if(CheckAttribute(chr, "chr_ai.type") == false)
{
chr.chr_ai.type = LAI_DEFAULT_TYPE;
}
if(CheckAttribute(chr, "chr_ai.tmpl") == false)
{
chr.chr_ai.tmpl = LAI_DEFAULT_TEMPLATE;
}
if(CheckAttribute(chr, "chr_ai.group") == false)
{
chr.chr_ai.group = LAI_DEFAULT_GROUP;
}
if(CheckAttribute(chr, "chr_ai.alarmreact") == false)
{
chr.chr_ai.alarmreact = LAI_DEFAULT_ALARMREACT;
}
if(CheckAttribute(chr, "chr_ai.grpalarmr") == false)
{
chr.chr_ai.grpalarmr = LAI_DEFAULT_GRPALARMR;
}
if(CheckAttribute(chr, "chr_ai.hp") == false)
{
chr.chr_ai.hp = LAI_DEFAULT_HP;
}
if(CheckAttribute(chr, "chr_ai.hp_max") == false)
{
chr.chr_ai.hp_max = LAI_DEFAULT_HP_MAX;
}
if(CheckAttribute(chr, "chr_ai.charge") == false)
{
chr.chr_ai.charge = LAI_DEFAULT_CHARGE;
}
//Проверяем хитпойнты
float hp = stf(chr.chr_ai.hp);
float hpmax = stf(chr.chr_ai.hp_max);
if(hpmax < 1) hpmax = 1;
chr.chr_ai.hp_max = hpmax;
if(hp > hpmax) hp = hpmax;
if(hp < 0) hp = 0;
chr.chr_ai.hp = hp;
if(!actLoadFlag)
{
if(hp < 1)
{
if(CheckAttribute(chr, "location.norebirth") != false)
{
if(sti(chr.location.norebirth) != 0) return false;
}
//Надо возраждать персонажа
chr.chr_ai.hp = hpmax;
hp = hpmax;
SetRandomNameToCharacter(chr);
func = chr.chr_ai.type;
chr.chr_ai.type = "";
chr.chr_ai.tmpl = "";
if(func != "")
{
func = "LAi_type_" + func + "_Init";
call func(chr);
}
}
}
if(hp < 1) return false;
//Проверяем персонажа
if(LAi_CheckCharacter(chr, "LAi_CharacterLogin") == false) return false;
//Выставляем скилл для сражения
//LAi_AdjustFencingSkill(chr); //Levis: Not needed anymore
RestoreCharacter(chr);//MAXIMUS: returns stored attributes
//Логиним персонажа
func = chr.chr_ai.type;
bool res = true;
if(func != "")
{
func = "LAi_type_" + func + "_CharacterLogin";
res = call func(chr);
}
if(res == false) return false;
chr.chr_ai.login = true;
LAi_AddLoginedCharacter(chr);
return true;
}
it will first check for some situations where the character is allowed to login or not (login means it can enter the location).
Then it will see if the maximum of characters is already achieved, if not it will fail. For some reason the maxHP is also set if we are not in a boarding situation...
After that some of the AI stuff is set up. And then it goes into a calculation where the hp is calculated.
It calls for the restorecharacter function, this one is used from what I see to restore a character if it was killed before.
If the login was succesfull it will call the createcharacter function
Code:
bool CreateCharacter(ref character)
{
// KK -->
if (IsUsedAlliesModel(character) == true && CheckAttribute(character, "IsFantom") == true && sti(character.IsFantom) == true && LAi_IsBoardingProcess() == false && ownDeckStarted() == false) {
//MAXIMUS: for excluding player's allies twins (with some reservations for constant characters[quest-characters or enemy-captains, for example])
return false;
}
// <-- KK
// NK bugfix 05-06-28 for chars who miss postinit.
if(!CheckAttribute(character, "model.animation") || character.model.animation == "" || !CheckAttribute(character, "model.height"))
{
/*if(character.sex == "man")
{
character.model.animation = "man";
character.model.height = 1.8;
}else{
character.model.animation = "woman"; // was towngirl. No real difference, IIRC.
character.model.height = 1.75;
}*/
//Assume no postinit done.
Character_PostInit(sti(character.index));
}
if(!CreateEntity(&character, character.model.entity)) return false;
// KK -->
if(!SendMessage(character, "lss", MSG_CHARACTER_SETMODEL, character.model, character.model.animation))
{
trace("CreateCharacter -> character " + character.id + " can invalide model("+ character.model +") or animation(" + character.model.animation + ")");
DeleteCharacter(character);
return false;
}
// <-- KK
ExecuteCharacterEquip(character);
if (!CheckAttribute(character,"completeinit") && !AllowCharacterPostInit())
{
//These are characters which are created after loading is done. To increase performance we are going to add them to the post init too if the queue isn't too large
if(GetAmountInPostInitQueue() < 5) //No idea why I picked 5, but sounds good to me.
{
SetCharacterPostInit(true);
InitCharacterSkills(character); //Levis
SetCharacterPostInit(false);
if(!PostInitActive) StartPostInitChars();
}
else
{
InitCharacterSkills(character); //Levis
}
}
//Set fight level
float fgtlevel = 0.0;
if(isBoardingLoading == false)
{
fgtlevel = CalcCharacterSkill(character, SKILL_FENCING);
}else{
fgtlevel = GetShipSkill(character, SKILL_FENCING);
}
if(fgtlevel < 0.0) fgtlevel = 0.0;
if(fgtlevel > MAX_CHARACTER_SKILL) fgtlevel = MAX_CHARACTER_SKILL;
fgtlevel = fgtlevel/MAX_CHARACTER_SKILL;
SendMessage(character, "lf", MSG_CHARACTER_SETFTGLEVEL, fgtlevel);
//Set character sex
SendMessage(character, "lsl", MSG_CHARACTER_EX_MSG, "SetSex", character.sex == "man");
//
// changed by MAXIMUS -->
if(bAbordageStarted) AddCharacterLocatorGroup(character, "rld");
else AddCharacterLocatorGroup(character, "goto");
// changed by MAXIMUS <--
BeginChangeCharacterActions(character);
SetDefaultFight(character);
EndChangeCharacterActions(character);
return true;
}
The first part of this function will check some animation data again and have the character equip some stuff again.
After that is a part from me for the setup which can be completely removed I think because this is called again lateron, I have no idea why this is still in here

After that are some weird functions about the fight level which I don't completly know what they do.
Finally during the login this function is called:
Code:
void SetUpCharacterWeapons(ref chr)
{
if (CheckAttribute(chr,"itemtrade")) return; // PB: Merchants shouldn't equip their own stock
if (isSoldier(chr) != UNKNOWN_NATION) GiveSoldierWeapon(chr, makeint(GetAttribute(chr, "nation"))); // PB: Re-Update Soldier Weapons
// KK -->
if (CheckAttribute(chr, "old.blade")) {
if (GetCharacterItem(chr, chr.old.blade) == 0) GiveItem2Character(chr, chr.old.blade);
DeleteAttribute(chr, "old.blade");
}
if (CheckAttribute(chr, "old.gun")) {
if (GetCharacterItem(chr, chr.old.gun) == 0) GiveItem2Character(chr, chr.old.gun);
DeleteAttribute(chr, "old.gun");
}
string equipitm = GetCharacterEquipByGroup(chr, BLADE_ITEM_TYPE);
string equipfound = FindCharacterItemByGroup(chr, BLADE_ITEM_TYPE);
// ccc special weapons, rearm disarmed chr
if (equipitm == "" && equipfound != "") {
EquipCharacterByItem(chr, equipfound);
} else {
if (equipitm != "") EquipCharacterByItem(chr, equipitm);
}
// ccc end
equipitm = GetCharacterEquipByGroup(chr, GUN_ITEM_TYPE);
equipfound = FindCharacterItemByGroup(chr, GUN_ITEM_TYPE);
if (equipitm == "" && equipfound != "") {
EquipCharacterByItem(chr, equipfound);
//JRH ammo mod -->
if (ENABLE_AMMOMOD && !IsOfficer(chr)) // LDH change, officer check added by Uzver
{
TakenItems(chr, "gunpowder", -6);
TakenItems(chr, "pistolbullets", -6);
TakenItems(chr, "gunpowder", 1 + rand(2));
TakenItems(chr, "pistolbullets", 1 + rand(2));
}
//JRH ammo mod <--
} else {
if (equipitm != "") EquipCharacterByItem(chr, equipitm);
}
}
After all this is done it will go into the postinit phase which is called like this:
Code:
void LAi_PostLoginInit(aref chr)
{
if(!IsEntity(chr)) return;
//Добавляем в группу
LAi_group_MoveCharacter(chr, chr.chr_ai.group);
//Инициализируем шаблон
string func = chr.chr_ai.tmpl;
if(func != "")
{
func = "LAi_tmpl_" + func + "_InitTemplate";
bool res = call func(&chr);
if(res == false)
{
chr.chr_ai.tmpl = LAI_DEFAULT_TEMPLATE;
}
}
//Инициализируем тип
func = chr.chr_ai.type;
if(func != "")
{
func = "LAi_type_" + func + "_Init";
call func(&chr);
}
//Update the contriblist and skill multipliers and do auto level up for NPC's
InitAutoSkillsSystem(chr, true); //Levis, the check for autoskill will happen later.
if(CheckAttribute(chr,"ContribList")) DeleteAttribute(chr,"ContribList")); //Levis refresh contriblist on login
DeleteAttribute(&PostInitQueue,"restorelist"); //Levis: Because everything is restored by the autoskill system already now
}
Now let's see what more happens in leveling.
If this is the first time the character is loaded it will call:
InitCharacterSkills
This function will look at all the skills and determine if they fit the rules for leveling and set the right importances etc. It will then see which level the character has to be and add the right amount of experience to this character so the experience for the skills is filled and perks are chosen. But before it does all this it will first check the character to see if everything is set up right:
Code:
void CheckCharacterSetup(ref chref)
{
if (!CheckAttribute(chref,"id")) {chref.id = "without_id"; }
if (!CheckAttribute(chref,"Experience")) {chref.Experience = 1; }
if (!CheckAttribute(chref,"perks.FreePoints")){chref.perks.FreePoints = 1; }
if (!CheckAttribute(chref,"skill.freeskill")){chref.skill.freeskill = 0; }
if (!CheckAttribute(chref,"quest.officertype") || GetAttribute(chref,"quest.officertype") == "combat")
{
if(GetAttribute(chref,"isOfficer") == 1)
{
chref.quest.officertype = GetRandomOfficerType();
}
else
{
if(GetAttribute(chref,"issoldier") == 1)
{
chref.quest.officertype = OFFIC_TYPE_GUARD;
}
else
{
if(GetAttribute(chref,"isMerchant") == 1)
{
chref.quest.officertype = OFFIC_TYPE_SHOPKEEPER;
}
else
{
if(GetAttribute(chref,"isWarrior") == 1)
{
chref.quest.officertype = GetRandomEnemyType();
}
else
{
if (CheckAttribute(chref, "model")) chref.quest.officertype = FindRandomModelOfficerType(chref.model); // PB: Assign officer type based on model
else chref.quest.officertype = OFFIC_TYPE_CIVILIAN; // Default if model is missing
}
}
}
}
}
if (CharacterHasShip(chref)) chref.quest.officerType = GetCaptainType(chref);
if (!CheckAttribute(chref,"rank")) {chref.rank = 1; }
if (!CheckAttribute(chref,"quest.officerprice") && !IsMainCharacter(chref)) chref.quest.officerprice = 0;
if (!CheckAttribute(chref,"Money")) {
chref.Money = GetRandCharMoney(chref, makeint(Rand(8)+2)); //Levis: Reduced amount of money characters carry
// } else { // PB: Commented out, because zero money may be intentional!
// if (sti(chref.Money) == 0) chref.Money = GetRandCharMoney(chref, makeint(Rand(8)+2));
}
//Checks for leaving officers
if (!CheckAttribute(chref,"loyality")) {chref.loyality = 5+rand(15); }
if (!CheckAttribute(chref,"alignment")) {if(rand(1)==1){chref.alignment = "good";}else{chref.alignment = "bad";}}
if (!CheckAttribute(chref,"homelocation"))
{
int loc = FindLocation(chref.location);
bool set = false;
if(loc > -1)
{
if(GetAttribute(Locations[loc],"type") == "tavern")
{
chref.homelocation = "";
chref.homelocation.group = "";
chref.homelocation.locator = "";
chref.homestate = "";
set = true;
}
}
if(!set)
{
chref.homelocation = chref.location; // PB: was alignment???
chref.homelocation.group = chref.location.group;
chref.homelocation.locator = chref.location.locator;
if(CheckAttribute(chref,"chr_ai.type")) chref.homestate = chref.chr_ai.type;
else chref.homestate = "citizen";
}
}
SetBaseShipData(chref); // PB: Not sure if this serves any purpose here, but it gets aborted if already done
}
And to finish it off it will call these function:
Code:
//Set the HP right
ResetHP(chr); // PB: was ResetMaxHP
//Equip things if not done yet
ExecuteCharacterEquip(chr);
So the HP is set right (for the level) and the right equipment is equiped
So you see a lot of stuff is being done double, and the code is one big mess now. I've mostly posted this here so nobody else has to look trough this spagethi again.
I hope we could make this a bit more easy to read and remove a lot of duplicated code so it become faster.
A lot of the checks are now being done during the init and once again during the login. I think these can all go to the login so the init becomes faster. but we can also do the the other way around so loading between area's will become faster.