1. Dismiss Notice
  2. GOG.com logo

    Thanks to YOUR votes, GOG.com now sells:
    - Sea Dogs - Sea Dogs: Caribbean Tales
    - Sea Dogs: City of Abandoned Ships

    Vote now to add Pirates of the Caribbean to the list!

    Dismiss Notice
  3. Under the Crossbones Podcast

    A Pirate Podcast with Interviews
    Music, Comedy and all things Pirate!

    - Episode Guide - About - Subscribe -
    - Twitter - Facebook - iTunes - Android -
    - Youtube - Fill the Coffers -

    Dismiss Notice
  4. New Horizons logo

    Quick links for PotC: New Horizons
    - Download latest version
    - Wiki - FAQ - Report bugs here
    - ModDB profile

  5. GOF logo

    Quick links for AoP2: Gentlemen of Fortune 2
    - Downloads and info
    - Historical Immersion Supermod
    - ModDB Profile

Dismiss Notice
New to the forum?
Please take a moment to read our Welcome Message and Forum Rules.

Cleaning up Character code

Discussion in 'Build Beta and Brainstorming' started by Levis, Nov 13, 2017 at 7:31 PM.

  1. Levis

    Levis Find(Rum) = false; Staff Member Programmer Creative Support Storm Modder

    Joined:
    Oct 6, 2013
    Messages:
    6,656
    Gender:
    Male
    Occupation:
    ICT
    Location:
    University Twente (Netherlands)
    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.
     
  2. Pieter Boelen

    Pieter Boelen (Not So) Old Seadog Staff Member Administrator Storm Modder Hearts of Oak Donator

    Joined:
    Nov 11, 2004
    Messages:
    66,563
    Gender:
    Male
    Occupation:
    Maritime Research: Project Engineer (Analysis)
    Location:
    Wageningen, The Netherlands
    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

    Maybe to check if you're newly entering the location or entered through loading a save game?
     
  3. Levis

    Levis Find(Rum) = false; Staff Member Programmer Creative Support Storm Modder

    Joined:
    Oct 6, 2013
    Messages:
    6,656
    Gender:
    Male
    Occupation:
    ICT
    Location:
    University Twente (Netherlands)
    Here it skips/allows the creation of the fantoms. So it looks like it has the same purpose as the vcskip
     
  4. Pieter Boelen

    Pieter Boelen (Not So) Old Seadog Staff Member Administrator Storm Modder Hearts of Oak Donator

    Joined:
    Nov 11, 2004
    Messages:
    66,563
    Gender:
    Male
    Occupation:
    Maritime Research: Project Engineer (Analysis)
    Location:
    Wageningen, The Netherlands
    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.
     
    Levis likes this.
  5. Levis

    Levis Find(Rum) = false; Staff Member Programmer Creative Support Storm Modder

    Joined:
    Oct 6, 2013
    Messages:
    6,656
    Gender:
    Male
    Occupation:
    ICT
    Location:
    University Twente (Netherlands)
    @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).
     
  6. Grey Roger

    Grey Roger Sea Dog Staff Member Storm Modder

    Joined:
    Feb 12, 2007
    Messages:
    6,368
    That is exactly my thought. It might not look pretty to a coder but it does the job. At one time, so did levelling.
     
  7. Levis

    Levis Find(Rum) = false; Staff Member Programmer Creative Support Storm Modder

    Joined:
    Oct 6, 2013
    Messages:
    6,656
    Gender:
    Male
    Occupation:
    ICT
    Location:
    University Twente (Netherlands)
    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 .
     

Share This Page