• New Horizons on Maelstrom
    Maelstrom New Horizons


    Visit our website www.piratehorizons.com to quickly find download links for the newest versions of our New Horizons mods Beyond New Horizons and Maelstrom New Horizons!

Cleaning up Character code

Levis

Find(Rum) = false;
Staff member
Administrator
Creative Support
Programmer
Storm Modder
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:
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
    }
This forloop will create the character array and it calls the InitChracter(ch) which looks like this:
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;
}
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:
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();
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:
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
}
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:
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 :p.
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);
    }
}
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:
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
}
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:
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.
 
The way I figure it, a bit longer wait at game start is a small price to pay for better performance during the game itself.
At least it is only once, right? :cheeky

For some reason there is a acLoadFlag check here which I haven't figured out what it would do exactly.
Maybe to check if you're newly entering the location or entered through loading a save game?
 
Maybe to check if you're newly entering the location or entered through loading a save game?

Here it skips/allows the creation of the fantoms. So it looks like it has the same purpose as the vcskip
 
Here it skips/allows the creation of the fantoms. So it looks like it has the same purpose as the vcskip
If you're loading a save game, the fantoms are already there and should not be created again.
I'm relatively sure that is its purpose.
 
@Grey Roger do you think it's worth looking into this? We all know it's a mess but it works now. If we start looking into this it might result in some unexpected bugs later on (which would be easier to fix becaue the code is more understandable).
 
That is exactly my thought. It might not look pretty to a coder but it does the job. At one time, so did levelling.
 
That is exactly my thought. It might not look pretty to a coder but it does the job. At one time, so did levelling.
I feel a small jest here :walkplank.
When I first took the leveling project I just moved everything to one function and removed all duplicate code. when that was done everything still worked the same. But then we decided to go to a progressive experience system (where it used to be a linear experience system). That's what caused a lot of the problems. When that was sorted we decided all characters should follow the same rules, so that caused some more of the problems. But I'd say in the end it works better now right?

As a first step for this I would suggest moving everything which is done during init onto the:
InitCharacter

Function and make sure this is only called when the character is loaded (so not during the population of the array).

Secondly I would sugest moving all checks during the login phase to
CreateCharacter
where maybe 1 or more seperate functions are created for the different checks (like a check for the animation data etc).
And have the check in leveling be removed because that wouldn't be needed anymore now.

If that works it would become easier to read already and no code would really be changed. it would only be moved so it's all together.

Then I'd sugest test with that for a while to see if anything strange pops up. And only after it's really clear nothings strange pops up I would slowly start removing stuff from the InitCharacter function which is also set in the CreateCharacter function untill at least every thing is only set once. Test with that for a long time again.
And only then maybe start considering removing this which look to be unnessecary .
 
Back
Top