• 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!

WIP Use Generic Captain and Ship Generation Functions

Actually, ran a search and in LAi boarding file it looks like difficulty level already does influence enemy boarders HP pretty strongly, so we might want to be somewhat careful in changing Captains...

Probably the current way of handling the difficulty works fine. :)

Here's the boarder HP stuff by difficulty level:

// KK -->
// LDH - playerHP should be left alone. EnemyHP is scaled by difficulty and morale differences.
if (!IsTown) {
boarding_enemy_hp += boarding_enemy_hp * ((stf(echr.ship.crew.morale) - stf(mchr.ship.crew.morale))/200.0); // LDH - change from absolute morale diff to percentage diff, 0.5 to 1.5 - 19Jan09
boarding_enemy_hp = boarding_enemy_hp * (1.0 + (GetDifficulty()-1.0)/5.0)); // 1.0, 1.2, 1.4, 1.6
} else {
boarding_enemy_hp = boarding_enemy_hp + ((makefloat(GetTownCrewMorale(echr.town)) - stf(mchr.ship.crew.morale))/4.0); // fix parenthesis as above
boarding_enemy_hp = boarding_enemy_hp * (1.0 + (GetDifficulty()-1.0)/5.0)); // 1.0, 1.2, 1.4, 1.6
}
 
I think the HP is actually overwritten by the leveling system again but I would need to look into that...
 
Looking into this more it seems that worldmap encounters also end up in the same fantom function to generate the captain. but afterwards this captain is modified more by this function:
RestoreCharacter(ref chCorpse)

Code:
void RestoreCharacter(ref chCorpse)
{
    if(!CheckAttribute(chCorpse,"corpse")) return;
    else DeleteAttribute(chCorpse,"corpse");
    if(CharacterIsDead(chCorpse)) { LAi_CharacterLogoff(chCorpse); return; }

    int tmpNameFileID = LanguageOpenFile("characters_names.txt");
    bool bNoName;

    if(CheckAttribute(chCorpse,"old.name"))
    {
        if(chCorpse.old.name!="")
        {
            if(LanguageConvertString(tmpNameFileID,chCorpse.old.name)!="") { chCorpse.name = LanguageConvertString(tmpNameFileID,chCorpse.old.name); bNoName = false; }
            else { chCorpse.name = chCorpse.old.name; bNoName = false; }
        }
        else { chCorpse.name = chCorpse.old.name; bNoName = false; }
    }
    else { bNoName = true; }

    if(CheckAttribute(chCorpse,"old.lastname"))
    {
        if(chCorpse.old.lastname!="")
        {
            if(LanguageConvertString(tmpNameFileID,chCorpse.old.lastname)!="") { chCorpse.lastname = LanguageConvertString(tmpNameFileID,chCorpse.old.lastname); bNoName = false; }
            else { chCorpse.lastname = chCorpse.old.lastname; }
        }
        else { chCorpse.lastname = chCorpse.old.lastname; bNoName = false; }
    }
    else { bNoName = true; }

    if(CheckAttribute(chCorpse,"old.firstname"))
    {
        if(chCorpse.old.firstname!="")
        {
            if(LanguageConvertString(tmpNameFileID,chCorpse.old.firstname)!="") { chCorpse.firstname = LanguageConvertString(tmpNameFileID,chCorpse.old.firstname); }
            else { chCorpse.firstname = chCorpse.old.firstname; }
        }
        else { chCorpse.firstname = chCorpse.old.firstname; }
    }

    if(CheckAttribute(chCorpse,"old.middlename"))
    {
        if(chCorpse.old.middlename!="")
        {
            //if(LanguageConvertString(tmpNameFileID,chCorpse.old.middlename)!="") { chCorpse.middlename = LanguageConvertString(tmpNameFileID,chCorpse.old.middlename); }
            //else { chCorpse.middlename = chCorpse.old.middlename; }
            chCorpse.middlename = TranslateString("", chCorpse.old.middlename); // KK
        }
        else { chCorpse.middlename = chCorpse.old.middlename; }
    }
    LanguageCloseFile(tmpNameFileID);

    if(CheckAttribute(chCorpse,"old.title"))
    {
        if(chCorpse.old.title!="")
        {
            if(TranslateString("",chCorpse.old.title)!="") { chCorpse.title = TranslateString("",chCorpse.old.title); }
            else { chCorpse.title = chCorpse.old.title; }
        }
        else { chCorpse.title = chCorpse.old.title; }
    }

    if(bNoName) { SetRandomNameToCharacter(chCorpse); }
    if(CheckAttribute(chCorpse,"deathx")) { DeleteAttribute(chCorpse,"deathx"); }
    if(CheckAttribute(chCorpse,"deathy")) { DeleteAttribute(chCorpse,"deathy"); }
    if(CheckAttribute(chCorpse,"deathz")) { DeleteAttribute(chCorpse,"deathz"); }
    if(CheckAttribute(chCorpse,"deathay")) { DeleteAttribute(chCorpse,"deathay"); }
    if(!CheckAttribute(chCorpse,"storedAttributes")) { LAi_CharacterLogoff(chCorpse); return; }
    if(bAllies(chCorpse)) { DeleteAttribute(chCorpse,"storedAttributes"); LAi_CharacterLogoff(chCorpse); return; }
    if(LAi_IsBoardingProcess()) { DeleteAttribute(chCorpse,"storedAttributes"); LAi_CharacterLogoff(chCorpse); return; }

    if(CheckAttribute(chCorpse,"old.dialog.filename")) { chCorpse.dialog.filename = chCorpse.old.dialog.filename; }
    if(CheckAttribute(chCorpse,"old.dialog.currentnode")) { chCorpse.dialog.currentnode = chCorpse.old.dialog.currentnode; }
    if(CheckAttribute(chCorpse,"old.dialog.tempnode")) { chCorpse.dialog.tempnode = chCorpse.old.dialog.tempnode; }
    if(CheckAttribute(chCorpse,"old.chr_ai.type.dialog")) { chCorpse.chr_ai.type.dialog = chCorpse.old.chr_ai.type.dialog; }
    if(CheckAttribute(chCorpse,"old.chr_ai.dmgbldmin")) { chCorpse.chr_ai.dmgbldmin = chCorpse.old.chr_ai.dmgbldmin; }
    if(CheckAttribute(chCorpse,"old.chr_ai.dmgbldmax")) { chCorpse.chr_ai.dmgbldmax = chCorpse.old.chr_ai.dmgbldmax; }
    if(CheckAttribute(chCorpse,"old.chr_ai.piercing")) { chCorpse.chr_ai.piercing = chCorpse.old.chr_ai.piercing; }
    if(CheckAttribute(chCorpse,"old.chr_ai.block")) { chCorpse.chr_ai.block = chCorpse.old.chr_ai.block; }
    if(CheckAttribute(chCorpse,"old.chr_ai.charge_max")) { chCorpse.chr_ai.charge_max = chCorpse.old.chr_ai.charge_max; }
    if(CheckAttribute(chCorpse,"old.chr_ai.charge_dlt")) { chCorpse.chr_ai.charge_dlt = chCorpse.old.chr_ai.charge_dlt; }
    if(CheckAttribute(chCorpse,"old.chr_ai.dmggunmin")) { chCorpse.chr_ai.dmggunmin = chCorpse.old.chr_ai.dmggunmin; }
    if(CheckAttribute(chCorpse,"old.chr_ai.dmggunmax")) { chCorpse.chr_ai.dmggunmax = chCorpse.old.chr_ai.dmggunmax; }
    if(CheckAttribute(chCorpse,"old.chr_ai.accuracy")) { chCorpse.chr_ai.accuracy = chCorpse.old.chr_ai.accuracy; }
    if(CheckAttribute(chCorpse,"old.items"))//MAXIMUS: I'm not sure that it's needed
    {
        aref ifrom; makearef(ifrom, chCorpse.old.items);
        DeleteAttribute(chCorpse,"items");
        chCorpse.items = "";
        aref ito; makearef(ito, chCorpse.items);
        CopyAttributes(&ito, &ifrom);
    }
// KK -->
    if (CheckAttribute(chCorpse, "old.chr_ai.group")) {
        if (chCorpse.old.chr_ai.group != LAI_GROUP_CORPSES) {
            chCorpse.chr_ai.group = chCorpse.old.chr_ai.group;
        } else {
            DeleteAttribute(chCorpse, "old.chr_ai.group");
            LAi_group_MoveCharacter(chCorpse, LAI_DEFAULT_GROUP);
        }
    }
    if (CheckAttribute(chCorpse, "old.chr_ai.type")) { LAi_group_MoveCharacter(chCorpse, chCorpse.old.chr_ai.type); }
    //if (CheckAttribute(chCorpse, "old.chr_ai.hp")) { chCorpse.chr_ai.hp = chCorpse.old.chr_ai.hp; }//Removed by levis cause it isn't used anymore.
    //if (CheckAttribute(chCorpse, "old.chr_ai.hp_max")) { chCorpse.chr_ai.hp_max = chCorpse.old.chr_ai.hp_max; }//Removed by levis cause it isn't used anymore.
    if (CheckAttribute(chCorpse, "passenger")) { DeleteAttribute(chCorpse, "passenger"); } // KK
    if (CheckAttribute(chCorpse, "prisoned")) { DeleteAttribute(chCorpse, "prisoned"); }
    if (CheckAttribute(chCorpse, "actions")) { DeleteAttribute(chCorpse, "actions"); }
    if (CheckAttribute(chCorpse, "killer")) { DeleteAttribute(chCorpse, "killer"); }
    if (CheckAttribute(chCorpse, "status")) { DeleteAttribute(chCorpse, "status"); }
    if (CheckAttribute(chCorpse, "position")) { DeleteAttribute(chCorpse, "position"); }
    if (CheckAttribute(chCorpse, "fight")) { DeleteAttribute(chCorpse, "fight"); }
    if (CheckAttribute(chCorpse, "surrendered")) { DeleteAttribute(chCorpse, "surrendered"); }
// <-- KK
    if (CheckAttribute(chCorpse, "corpse")) { DeleteAttribute(chCorpse, "corpse"); }

    for(int c = 0; c < 10; c++)
    {
        string curSkillName = GetSkillName(c);
        if(CheckAttribute(chCorpse,"old.skill."+curSkillName+".mod")) { chCorpse.skill.(curSkillName).mod = chCorpse.old.skill.(curSkillName).mod; }
        else { if(CheckAttribute(chCorpse,"skill."+curSkillName+".mod")) { DeleteAttribute(chCorpse,"skill."+curSkillName+".mod"); } }
    }

    DeleteAttribute(chCorpse,"storedAttributes");
    LAi_CharacterLogoff(chCorpse);
}

Maybe we could try to remove this piece of code and see what happens. From what I understand this is to make sure the captain is alive and not other stuff is still left behind. but I think this now is already managed by some of the fantom changed @Pieter Boelen and I did in the past.
 
I think the HP is actually overwritten by the leveling system again but I would need to look into that...
Probably, yes.

Maybe we could try to remove this piece of code and see what happens. From what I understand this is to make sure the captain is alive and not other stuff is still left behind. but I think this now is already managed by some of the fantom changed @Pieter Boelen and I did in the past.
Indeed maybe ClearCharacter being used can help to do away with that stuff.
Hopefully....
 
Probably, yes.


Indeed maybe ClearCharacter being used can help to do away with that stuff.
Hopefully....
ClearCharacter should be called sooner already. This is just legacy probably. I think we can just remove this whole call and then the level of the captain should be alright for all types of ship encounters. altough I think it still does it right now also.
 
ClearCharacter should be called sooner already.
That's what I mean. Before RestoreCharacter is called to "reuse" the old character slot, that slot should now be already cleared first.
 
@Levis I am happy to test it out. So I just delete that block of code?

Oh, so the hp changes get overwritten, and swashbuckler doesn't face harder boarding enemies than landlubber? That would be a shame!

I do think it is better to do the difficulty effect on boarder hp rather than captain level, because otherwise, there won't be as much of difference in the abilities/perks of tier 4 ship captains vs tier 1 :)
 
Was looking into the AIFantom.c, trying to learn how things work.:)

Found this:

rFantom.quest.officertype = GetCaptainType(rFantom);
rFantom.rank = GetCaptainRank(rFantom);
mult *= CaptainMultFromOfficerType(rFantom.quest.officertype);
ref Shiptype = GetShipByType(iShipType);
int iMCShipClass = makeint(GetCharacterShipClass(GetMainCharacter()));
if (sti(Shiptype.Class) < iMCShipClass) {
mult *= 1.15;
} else {
if (makeint(Shiptype.Class) > iMCShipClass) mult *= 0.85;

It looks like it is altering ship class of encounters at Sea in response to player ship size, or am I misunderstanding? Just trying to figure everything out. :)

And a second question:

Also, if I added a line like this: rank *= (0.9 + ((GetDifficulty() - 1) / 10))

int rank = (8-shipclass)*7;
rank = rank*0.8 + 0.3*rand(rank);
rank += GetOfficTypeRankBonus(SCaptain.quest.officertype);
rank *= (0.9 + ((GetDifficulty() - 1) / 10))

Would that give a 20% increase in enemy captain rank on swashbuckler, a 10% decrease on the lowest difficulty? Given what I am seeing in perks right now, I am considering trying this out, but not sure if I wrote that correctly ;)
 
Last edited:
It does look at player ship tier there for some reason.
But it only affects 'mult'. Where is that used later?
You might want to upload the entire file.

Also, since there are many different generation functions, it is important to know where you found that code.
E.g. in which function.
 
I will try to look into this during the weekend and at least make sure captain generation is moved to 1 single function so it happens the same way always. then we can see how to tweak it etc.
 
It does look at player ship tier there for some reason.
But it only affects 'mult'. Where is that used later?
You might want to upload the entire file.

Also, since there are many different generation functions, it is important to know where you found that code.
E.g. in which function.

Pieter, as requested, file attached, and here is the code. I tried reading it but it is beyond what I can understand. ;)

Levis, that sounds great! :) So great to see all the amazing stuff you are doing to improve the mod.

void Fantom_AddFantomCharacter(string sGroupName, int iShipType, string sFantomType, int iEncounterType, int iNation) // NK 04-09-05 add nation argument
{
ref rFantom = GetFantomCharacter(iNumFantoms);
ClearCharacter(rFantom); // PB: Clear ALL attributes from previous character
rFantom.nation = iNation; // NK

rFantom.SeaAI.Group.Name = sGroupName;
rFantom.Ship.Type = GetShipID(iShipType); // PS
//trace("added fantom " + rFantom.index + " with ship " + rFantom.ship.type);
rFantom.Ship.idx = iShipType; // PS
rFantom.Ship.Mode = sFantomType;
rFantom.ship.cannons.Charge.Type = GOOD_BALLS;
//NK -->
// PRS3 -->
aref arship; makearef(arship, rFantom.ship);
//KB - Tuning ships - changed call to SetRandomStatsToShip
SetRandomStatsToShip(sti(rFantom.index), iShipType, iNation);
//KB - orig SetRandomStatsToShip(arship, iShipType, iNation);
//KB
// PRS3 <--
if(sti(rFantom.nation) == PIRATE) { sFantomType = "pirate"; }
else { if(sFantomType == "pirate") { sFantomType = "war"; } }
rFantom.shiptype = GetShipID(iShipType); // PS
rFantom.FantomType = sFantomType;
float mult = 1.0;
if(DEBUG_EXPERIENCE>0) TraceAndLog("Fantom_AddFantomCharacter: Set officer type for " + GetMySimpleName(rFantom));
/*switch(sFantomType)
{
case "trade":
mult *= 1.0;
rFantom.quest.officertype = OFFIC_TYPE_CAPMERCHANT;
break;

case "war":
mult *= 1.25;
rFantom.quest.officertype = OFFIC_TYPE_CAPNAVY;
break;

case "pirate":
mult *= 1.25;
rFantom.quest.officertype = OFFIC_TYPE_CAPPIRATE;
break;

case "error":
mult *= 1.0;
rFantom.quest.officertype = OFFIC_TYPE_CAPPIRATE;
break;
}*/
//Levis moved this to already existing functions
rFantom.quest.officertype = GetCaptainType(rFantom);
rFantom.rank = GetCaptainRank(rFantom);
mult *= CaptainMultFromOfficerType(rFantom.quest.officertype);
ref Shiptype = GetShipByType(iShipType);
int iMCShipClass = makeint(GetCharacterShipClass(GetMainCharacter()));
if (sti(Shiptype.Class) < iMCShipClass) {
mult *= 1.15;
} else {
if (makeint(Shiptype.Class) > iMCShipClass) mult *= 0.85;
}
rFantom.Points = mult * stf(GetLocalShipAttrib(&arship,&Shiptype,"Weight")) / 5000; // PRS3
//Log_SetStringToLog("Type: " + rFantom.FantomType + "; Mult: " + rFantom.Points);
// NK <--
InitAutoSkillsSystem(rFantom,false); //Levis: Reset this character
iNumFantoms++;
}

void Fantom_SetRandomMoney(ref rFantom, string sFantomType)
{
//NK, based on Stone-D -->
// clear money from character
rFantom.ShipMoney = 0;

// ship class
ref rShip = GetShipByType(GetCharacterShipType(rFantom));
aref arship; makearef(arship, rFantom.ship); // PRS3 on down
float cap = stf(GetLocalShipAttrib(arship,rShip,"Capacity")); //get ship's capacity.
float weight = makefloat(sti(GetLocalShipAttrib(arship,rShip,"Weight"))) * 10000 / 508; //get ship's weight. Changed 04-09-19 to be more accurate, was simply *19.7
int rank = sti(rFantom.rank);
//make rankscalar
float ranksc = makefloat(rank);
if(ranksc<=5.0) ranksc = ranksc/5.0
else ranksc = sqrt(ranksc) - 1.236;
ranksc = ranksc*10;
int rscale = makeint(ranksc);
// add money
// 04-09-22 add SHIPMONEY_MULT
switch (rFantom.FantomType)
{
case "trade":
rFantom.ShipMoney = makeint(SHIPMONEY_MULT * (cap * makefloat(30 + rand(20) + rand(20)) * (rscale/2.0 + rand(rscale))/40.0 + (rand(10)+5.0)/30.0));
return;
break;
case "war":
rFantom.ShipMoney = makeint(SHIPMONEY_MULT * (weight * makefloat(40 + rand(10) + rand(10)) * (rscale/2.0 + rand(rscale))/20.0 + (rand(10)+5.0)/30.0));
return;
break;
case "pirate":
rFantom.ShipMoney = makeint(SHIPMONEY_MULT * (weight * (5.0 + (rand(5)+1.0) * (rand(5)+1.0)) * (rscale/2.0 + rand(rscale))/20.0 + (rand(10)+5.0)/15.0));
return;
break;
}
//Trace("Fantom index = " + rFantom.index + ", group id = " + rFantom.SeaAI.Group.Name + ", have invalid encounter type = " + sFantomType);
// NK <--
}
 

Attachments

  • AIFantom.c
    29.8 KB · Views: 208
Pieter, as requested, file attached, and here is the code. I tried reading it but it is beyond what I can understand. ;)
Thanks! That explains a lot.
The full relevant section is:
Code:
int iMCShipClass = makeint(GetCharacterShipClass(GetMainCharacter()));
if (sti(Shiptype.Class) < iMCShipClass) {
mult *= 1.15;
} else {
if (makeint(Shiptype.Class) > iMCShipClass) mult *= 0.85;
}
rFantom.Points = mult * stf(GetLocalShipAttrib(&arship,&Shiptype,"Weight")) / 5000; // PRS3
It does indeed look at the player ship tier, but it uses that only to determine the "points" attribute.
What this does is to give you more relation points with a nation for sinking/capturing a large ship with a small ship yourself, than you do for the opposite.
 
Ah, that makes sense! :)

Anytime the code gets complex I get confused, but it is surprising (and a testement to how well the comments are written by past modders, and how well organized some things are) that there is a ton I can do and understand in the less complex parts of the code.
 
Many former modders started out as yourself, trying to do small things. Myself included!
So many of them know how difficult it can be to understand things and have tried to at least make the code and comments somewhat self-explanatory.
Glad to hear that pays off! :cheers
 
From what I can see this is what needs to be done to create the captain right.
First the encounter has to be determined and the shipclass.
At one point the Force_GetShipType() is called.
In here the class and typ of the ship etc is set. Also the nation is set.
So we now have the ship and the nation of the captain we want to create. After this we want to have a function to create a captain.
Now this is either done by Fantom_AddFantomCharacter() which is called for encounters. Or in the case of coastel raiders this is done manually in the function it self.

So I sugest we make a function like LAi_CreateCaptain(int ShipType, string FantomType, int Nation)
In this function the following things need to happen:
- First we clear the character completly to be sure no data has remained.
- Now we want to determine the level of the captain.
- We also want to determine the type of the captain
- We want to set the nation of the character
- apperently the character needs a crmonth and crday for coastalraiders
- We need to set the captain in the right location (currentlocation), the group and locator can stay empty for now.
- A randommodel needs to be set for the captain
- Randomname has to be set for the captain
- Now the InitCharacterSkills needs to be called for the character so all skills are setup right.
- We should make a random name for the ship.
- In the encounter code and other function they use manual ways to set the ship to the character. I believe we should just use GiveShip2Character. I'm only not sure how to deal with the cannons. Using this function should make sure crew and supplies are arranged.
- We should apply some random stats to the ship too with SetRandomStatsToShip

I think this is all which needs to be done to generate a captain if I look at the existing function.
After this the captain has to be added to the right group (I want to remove this fromt he create captain function). This can be done with the Group_AddCharacter function I believe.
If we want we could also add a function here to add officers to the captain. but thats for later.

Does anyone sees a problem with this?
@Pieter Boelen or @Grey Roger do you know how to assign cannons to the ship? I see they use -1 everywhere in the GiveShip2Character function. Would this automaticly set the cannons right?
 
Last edited:
"GiveShip2Character" can take the cannon type as an argument. -1 uses the default type as defined in "Ships_init.c", but you can override it with an equivalent or smaller type. (Replace carronades with long guns or vice versa, give merchants smaller guns, etc.)

On that note, at the moment merchants normally have smaller guns and fewer guns than the maximum allowed for the ship, especially "versatile" ships. That doesn't give them any benefit in the game but simulates a merchant ship which has had a gun deck removed to make more room for cargo. Merchants should also have lower skill captains and lower morale crews than naval or pirate ships, representing the fact that they're not professional fighting men.
 
"GiveShip2Character" can take the cannon type as an argument. -1 uses the default type as defined in "Ships_init.c", but you can override it with an equivalent or smaller type. (Replace carronades with long guns or vice versa, give merchants smaller guns, etc.)

On that note, at the moment merchants normally have smaller guns and fewer guns than the maximum allowed for the ship, especially "versatile" ships. That doesn't give them any benefit in the game but simulates a merchant ship which has had a gun deck removed to make more room for cargo. Merchants should also have lower skill captains and lower morale crews than naval or pirate ships, representing the fact that they're not professional fighting men.
I've found a function which I believe will lower the cannons for merchants. I will look into that.
The level of the captain is modified depending on which you are indeed. This should then also affect the crew etc.
 
I think this is all which needs to be done to generate a captain if I look at the existing function.
You should look at the top of PROGRAM\QUESTS\quests_common.c; that first function to generate quest ships calls some functionality that I wrote which does most of those things already.
The only issue there is that it is for quest ships only, where the captain rank is determined before the ship tier.

apperently the character needs a crmonth and crday for coastalraiders
That can be kept inside the Coast Raider function itself, no?

- In the encounter code and other function they use manual ways to set the ship to the character. I believe we should just use GiveShip2Character. I'm only not sure how to deal with the cannons. Using this function should make sure crew and supplies are arranged.
I very much agree with relaying the ship generation to use GiveShip2Character; that would be very nice! :onya

On that note, at the moment merchants normally have smaller guns and fewer guns than the maximum allowed for the ship, especially "versatile" ships.
Really??? I'm pretty sure all ships should always be generated with their maximum number of cannons present! :shock
 
@Pieter Boelen you mind if I'm also replacing the initQuestCaptain function (or at least remove a portion from it to use the new function I'm making now) so everything happens at the same spot and we have more control over it?
 
Back
Top