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

Drastically reduce chance of Pirate encounter in friendly ports

I think so, because in my tests the coastal raiders always turned and ran after the first volley from the fort. Can't be entirely sure it was because of this retreat check, but before they had seemed to stick around and shoot back for awhile, so it looks like it worked.

But I can't tell from the code itself, and I only tested a couple of batches of coastal raiders so far. I might be mistaken and have gotten lucky with my tests, in which case I'll try to learn more about how this is coded and add a line or two to make sure forts are counted.

One thing, they still need to get some active combat engagement even against a fort before the check runs currently, because I kept it in the broader morale check code for now to ensure it couldn't break scripted encounters or create other unforseen problems (ie, this retreat possibility only operates where the preexisting <40% HP retreat chance operates, and basicaly mirrors it in operation, so it shouldn't make any enemy run that didn't have the potential to run before). A little less than ideal, but seemed the safe approach.
 
Last edited:
@Pieter Boelen Not sure if you have a moment or not to take a look at something, totally fine if you don't.

I wrote some 10-15 trace entries and have been running tests watching things happen. Things look fine on the surrender stuff, but on the powerratio stuff, there is good news and bad news.

The good news is that it does try to count forts.

The bad news is the function fails and doesn't count anyone except the ship itself. Which explains why everyone was running when I reversed the effect, but means the strength ratio retreat check when in the correct direction will never cause anything to happen until that is fixed.

I added trace functions all around until I narrowed the problem to the line:

if(!CheckAttribute(&NearShips[sidx],tstr+"."+tstr2+".dist")) continue;

That is where it breaks off and doesn't count the ship's power. The find ships part works great (I've checked with traces, even the forts are counted it seems), but then it just doesn't count their power because that check comes back.

On the other hand, this part for measuring themself does work:

if(rel == RELATION_FRIEND)
{
chr = GetCharacter(idx);
tstr2 = 0;
power += sqrt(GetCurrentShipHP(&chr) * GetCannonCurQuantity(&chr)) / (1.0 + pow2(stf(NearShips[sidx].all.(tstr2).dist)/100.0, 1.5)); // nearest ship
Trace("power of self for" + chr + "is " + power);
}

I have excerted the full bock of code below, my first trace for "power function broke at checkattribute") shows where the problem is.

I am going to keep staring at it and trying to fix, but if you have any quick ideas, I'd love for the advice.

Does something about the way &NearShips[sidx],tstr+"."+tstr2+".dist") is written look obviously wrong to you? Or anything stand out in the below for why that attribute isn't found?



float FindPowerOfNearShips(int idx, int rel)
{
int num = FindNearShips(idx);
ref chr = GetCharacter(idx);
int sidx = sti(chr.curshipnum); // change to array 05-06-27
string tstr = "rel"+rel;
if(num <= 0 || !CheckAttribute(NearShips[sidx],tstr+".0")) return 0.0;
float power = 0.0;
string tstr2;
for(int i = 0; i < sti(NearShips[sidx].(tstr).qty); i++)
{
if(GetCannonCurQuantity(&chr)<=0) continue;
if(!CheckAttribute(&NearShips[sidx],tstr+"."+tstr2+".dist"))
Trace("power function broke at Checkattribute")
continue;

string s1 = GetCurrentShipHP(&chr);
string s2 = GetCannonCurQuantity(&chr);
string s3 = 1.0 + pow2(stf(NearShips[sidx].(tstr).(tstr2).dist)/100.0, 1.5);

tstr2 = i;
chr = GetCharacter(sti(NearShips[sidx].(tstr).(tstr2).idx));
power += sqrt(GetCurrentShipHP(&chr) * GetCannonCurQuantity(&chr)) / (1.0 + pow2(stf(NearShips[sidx].(tstr).(tstr2).dist)/100.0, 1.5));
Trace("power of one ship measured by" + chr + "is " + power);
}
if(rel == RELATION_FRIEND)
{
chr = GetCharacter(idx);
tstr2 = 0;
power += sqrt(GetCurrentShipHP(&chr) * GetCannonCurQuantity(&chr)) / (1.0 + pow2(stf(NearShips[sidx].all.(tstr2).dist)/100.0, 1.5)); // nearest ship
Trace("power of self for" + chr + "is " + power);
}
return power;
 
Ok, the problem is the distance is missing, probably not getting assigned in the nearship function correctly. I'm not so sure about the current use of distance anyway--it makes the power ratio too drastically changing when ships get a little closer or farther. I think I can fix this.
 
Last edited:
I'd need to be near my computer to look at that code properly.
It looks decidedly not simple and I'm not sure why there's a comma in that line you mention.
Seems definitely odd to me.
 
Thanks Pieter. Turns out the distance seems to not be gotten from the nearship function properly. Also, because of the place the self power check is positioned, in some situations (especially when the AI ship is alone), it will always return 0 for the power, because it cancels before it gets to the self power check. And the way distance was being used anyway was too dramatic, and the function wasn't seeming to consider cannon caliber at all.

I think I'm making good progress on fixing it, let's see whether I can get it working right. It is a really nice tool if fixed. :)
 
Last edited:
I updated some distance calculating code for the False Flag detection and also the spyglass distances.
That part does work fine now. So if you need distances, I recommend building on what I did for that.
 
Thanks @Pieter, I found the code you mentioned, and was also able to get the original code for distance working properly. The problem was the original function was orderd wrong and hadn't defined the characters before trying to get the distance.

Also, while it was trying to get forts, the formula it was using would end up defining the fort power to 0. I think I can fix that by putting a special case for forts.

I ended up using cannon price as a reasonable proxy for cannon power, before it counted a 4lbs the same as a 32lbs.

And several other things, in addition to the ones mentioned before. This has taken me hours, but I've finally got it almost corrected.:)
 
Yep, the reason it couldn't count forts was the getcurrentshiphp function is returning 0 for forts. Here it is btw:

int GetCurrentShipHP(ref _refCharacter)
{
int nShipType = GetCharacterShipType(_refCharacter);
if (nShipType <0 || nShipType >= SHIP_TYPES_QUANTITY) { return 0; } // PB: Prevent CTDs
if (CheckAttribute(_refCharacter,"ship.hp") && sti(_refCharacter.ship.hp)>0) return sti(_refCharacter.ship.hp);

return 0;
}


EDIT: And trying to directly take sti(chr.ship.hp) also returns 0 for forts.

Not sure how to get their current HP. For now, I'll just scale them as if always 10000 HP. It won't change over course of battle and go down like it should (and will for ships), so not ideal. Just picking this number for now to cause the coastal raiders to turn tail and run, which is the main purpose anyway, and shouldn't really matter.

If anyone knows how to actually take a Fort's current HP as it changes during battle, please let me know. Doesn't matter too much, but would be nice.

EDIT2: and for some reason forts return negative numbers..Very odd, but easily fixed
 
Last edited:
@Pieter Boelen Finally, success! Had to basically rewrite the entire thing, but it works well now.

Here is what it looks like in compile (with my traces on) when a Pirate ship (happens to be treasure pirate, but coastal raiders would be the same), decided a fort and the British warships in the harbor are too much for him.

The too far, skipping ship are checks I wrote to only consider enemies within long range or less. Cannon price is used as a proxy for cannon value, that is why we are measuring it.

Except for the self measurement, it is measuring enemies here. The last line
char Treasure Pirate of ship Standardis making a tactical withdrawl
is where it decides to flee.


Calculating cannon cost at800
Calculating cannon cost at800
Calculating cannon cost at800
Calculating cannon cost at800
SHIP had the following price of cannons800and HP1141and cannon qty16and contributed the following power14
Calculating cannon cost at5000
Calculating cannon cost at5000
Calculating cannon cost at5000
Calculating cannon cost at5000
FORT had the following price of cannons5000and was considered 10000 hp for powerratio purposesand cannon qty68and contributed the following power894
Calculating cannon cost at1300
Calculating cannon cost at1300
Calculating cannon cost at1300
Calculating cannon cost at1300
SHIP had the following price of cannons1300and HP1937and cannon qty22and contributed the following power55
Calculating cannon cost at1200
Calculating cannon cost at1200
Calculating cannon cost at1200
Calculating cannon cost at1200
SHIP had the following price of cannons1200and HP1084and cannon qty16and contributed the following power20
Calculating cannon cost at5000
Too far, not counting this ship
Calculating cannon cost at800
Too far, not counting this ship
Calculating cannon cost at800
Too far, not counting this ship
Calculating cannon cost at800
Too far, not counting this ship
Calculating cannon cost at1300
Too far, not counting this ship
Calculating cannon cost at1200
Calculating cannon cost at1200
self calc Character had the following price of cannons1200
power of self is 10.so new total is10.
num of near ships found for is 9, friend power is 10.enemy power is 983.
strengthrat for char Treasure Pirate of ship Standard strengthrat is 1.0173e-002
char Treasure Pirate of ship Standardis making a tactical withdrawl
 
And here is the rewritten powerratio function for reference, will take another look at it tomorrow, happy to hear if you have any feedback. For now, it seems to be working as intended. :) (Though I will move the distance limit check up a bit to avoid some caluclations for far ships)

Code:
//finds ratio of friend power vs. foe power
float FindPowerRatio(int idx)
{
    int num = FindNearShips(idx);
    ref chr = GetCharacter(idx);
    int sidx = sti(chr.curshipnum); // change to array 05-06-27
    //if(num == 0) return 0.0;
    string tstr = "rel"+RELATION_ENEMY;
    if(!CheckAttribute(NearShips[sidx],tstr+".0")) return 1.0; // TY Changing from 0 to 1. Just in case, the usage of it in the morale code should never trigger anyway unless enemies are around, but better to be safe.
    float epower = FindPowerOfNearShips(idx, RELATION_ENEMY);
    float fpower = FindPowerOfNearShips(idx, RELATION_FRIEND);
    Trace("num of near ships found for " + chr + "is " + num + ", friend power " + chr + "is " + fpower + "enemy power " + chr + "is " + epower);
    if(epower < 0.01) epower = 0.01; //TY changing to 0.01 to match the new scaling
    if(fpower < 0.01) fpower = 0.01;
    return epower / fpower;
}

float FindPowerOfNearShips(int idx, int rel)
{
    int num = FindNearShips(idx);
    ref chr = GetCharacter(idx);
    ref    rCannon = GetCannonByType(sti(chr.Ship.Cannons.Type));
    int sidx = sti(chr.curshipnum); // change to array 05-06-27
    string tstr = "rel"+rel;
    float power = 0.0;
 
    if(num <= 0 || !CheckAttribute(NearShips[sidx],tstr+".0")) {
        if(rel != RELATION_FRIEND) { // TY This check not being here might have been responsible for the self not being counted at all if no friends were around
        Trace("terminating because nothing to count");
        return power; //0.0; TY commenting out the 0.0, the thing is already set to that.
        }
    }
    string tstr2;
    for(int i = 0; i < sti(NearShips[sidx].(tstr).qty); i++)
    {
        tstr2 = i;
        chr = GetCharacter(sti(NearShips[sidx].(tstr).(tstr2).idx));
        if(GetCannonCurQuantity(&chr)<=0) continue;
        string s1 = GetCurrentShipHP(&chr); //TY Not sure why these are needed, but leaving in place in case
        string s2 = GetCannonCurQuantity(&chr);
        string s3 = GetCannonPriceForPower(&chr); // TY the reason for adding this is beyond my limited understanding, but since the original function had these strings, I'm making one for the new stuff as well
        string s4 = stf(NearShips[sidx].(tstr).(tstr2).dist);
        //string s3 = 1.0 + pow2(stf(NearShips[sidx].(tstr).(tstr2).dist)/100.0, 1.5); //TY If I could make it properly get distance, I would probably have a hard check on range, if over a certain number comes back 0 (probably a bit beyond combat range), otherwase a straight calculation of power. That would make things properly consistent with the overall tactical situation, rather than constatntly shifting perhaps dramatically from a couple of ships getting closer or farther
         //TY Let's try implementing the above plan on distance and range
        //TY Begin hard distance check
        if(stf(NearShips[sidx].(tstr).(tstr2).dist) > GetVisibilityRange(1)) { //TY long range is maximum consideration
            Trace("Too far, not counting this ship");
            continue;}
        //if (Ship_GetDistance2D(PChar, &chr) < GetVisibilityRange(1)) //TY backup way, but not ideal, trying something else first
        //TY End hard distance check
        if(chr.ship.type == SHIP_FORT_NAME)
        {
        power += ((-10000 * GetCannonCurQuantity(&chr) * GetCannonPriceForPower(&chr))/1000000); //TY Forts are really odd, can't get current hp, return negative numbers, but oh well, this works.
        trace("FORT had the following price of cannons" + GetCannonPriceForPower(&chr) + "and was considered 10000 hp for powerratio purposes" + "and cannon qty" + GetCannonCurQuantity(&chr) + "and contributed the following power" + ((-10000 * GetCannonCurQuantity(&chr) * GetCannonPriceForPower(&chr))/1000000)); 
        }
        else
        {
        power += ((GetCurrentShipHP(&chr) * GetCannonCurQuantity(&chr) * GetCannonPriceForPower(&chr))/1000000); //TY slight change--to plan mentioned in other comments--let's use cannon price as a proxy for power
        trace("SHIP had the following price of cannons" + GetCannonPriceForPower(&chr) + "and HP" + GetCurrentShipHP(&chr) + "and cannon qty" + GetCannonCurQuantity(&chr) + "and contributed the following power" + ((GetCurrentShipHP(&chr) * GetCannonCurQuantity(&chr) * GetCannonPriceForPower(&chr))/1000000)); 
        }
          //TY Explanations of formula. Let's do this differently. Squareroots don't belong here (firepower is actually better more concentrated, that was having the opposite effect), And we need caliber consideration, using weight as a proxy. Distance shouldn't be so overwhelming a factor either. Probably best not to include sails, we don't want ships running when their sails are weak really.
 
        //if(!CheckAttribute(&NearShips[sidx],tstr+"."+tstr2+".dist")){  //TY this was always coming back and canceling the check, distance wasn't being assigned. Not sure how to make it properly get distance
        //Trace("power function broke at Checkattribute"); 
        //continue;}
        //power += sqrt(GetCurrentShipHP(&chr) * GetCannonCurQuantity(&chr)) / (1.0 + pow2(stf(NearShips[sidx].(tstr).(tstr2).dist)/100.0, 1.5)); //TY this wasn't working for above reasons
     
    }
    if(rel == RELATION_FRIEND)  //TY This is where the ship calculates itself
    {
        chr = GetCharacter(idx);
        tstr2 = 0;
        if(GetCannonCurQuantity(&chr)<=0) return power; //TY no reason to do the rest if no cannons
        //power += sqrt(GetCurrentShipHP(&chr) * GetCannonCurQuantity(&chr)) / (1.0 + pow2(stf(NearShips[sidx].all.(tstr2).dist)/100.0, 1.5)); // nearest ship
        float selfpower = ((GetCurrentShipHP(&chr) * GetCannonCurQuantity(&chr) * GetCannonPriceForPower(&chr))/1000000);
        trace("self calc Character had the following price of cannons" + GetCannonPriceForPower(&chr)); //TY Same comments as above for the formulas
        power += selfpower;
        Trace("power of self is " + selfpower + "so new total is" + power);
    }
    return power;
}
//TY Adding a function to make the cannon calculations above easy
int GetCannonPriceForPower(ref rChar)
{
    int nCannonType = GetCaracterShipCannonsType(rChar);
    if(nCannonType==CANNON_TYPE_NONECANNON) return 0.0;
    ref rCannon = GetCannonByType(nCannonType);
    trace("Calculating cannon cost at" + sti(rCannon.Cost));
    return sti(rCannon.Cost);
}
 
Last edited:
@Pieter Boelen happy to report the coastal raider suiciding against forts problem is indeed fixed with the new powerratio and tactical retreat code. :) (once it is properly tested and ready for inclusion).

I ended up setting forts to be measured as if they had 500hp, which seems low, but is enough to send small enemies running without influencing the rest of their decision making too much. They will run, but might get sunk if they start right next to the docks and can't escape in time, or especially if their escape is cut off by enemy warships.

I'll raise the fort's power rating if I still see it occuring, but it seems to be enough to scare off normal coastal raiders so far, without scaring off a decently outfitted force (in case quests have the player attack forts without exempting the player allied participants from the morale check code), or unduly influencing other decisions based on power estimates.

I dropped an example compilelog demonstration in the spoilertag here: Needs Testing - Intelligent AI retreats and surrenders | PiratesAhoy!

The little pirate schooner ran immediately, but with 3 warships cutting off his retreat from the fort and his sails damaged, after a couple of volleys he eventually surrendered at 6% HP.

His main problem is he spawned right next to me at the dock, they usually get away without a problem if they start a reasonable distance away. He was a treasure pirate without a locator, the normal coastal raiders should be able to get away much easier. Even this poor fellow gets away most of the time in repeated tests.
 
Last edited:
Back
Top