• 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

Check For Pirate Exception is part of the Sea Relations so that, for example, the French fort at La Tortue does not fire on Pirates (because Tortuga was a target famous pirate haven despite officially being French).

Check All Ships is also part of the Sea Relations and sets hostility correctly between all the groups.
It is also involved in the False Flag Detection.

This is stuff I added myself a while ago when I completely rewrote those Sea Relations because they didn't quite work like they should before.
 
@Tingyun I can see more or less how it works but I don't see what I could change. Maybe in the "CheckAllShips" function we could add a line that would check if there is a fort and also a pirate, if that is true then the pirate would run away. But I don't know how to do that.
 
You need to start with the function all the way at the bottom of that file, which should be called every minute and is the one that assigns AI tasks.

Check All Ships probably won't be the right place as it is intended specifically to set relations and therefore only gets called if those change.
For performance reasons I did my best to not call that code more often than it needs to be.
 
I think I found it but it looks like it affects both pirates and warships in the same sentence.

void Improve_SeaAi(String groupe)
{
ref crgchar = Group_GetGroupCommander(groupe);
if(CheckAttribute(crgchar,"surrendered") || CharacterIsDead(crgchar)){return;}
float x, z;
int enemydist = 0;
int nextenemy = 0;
string sLocationGroup, sLocationLocator;
ref rIsland;
string sTst;
ref rGroup = Group_GetGroupByID(groupe);
ref PChar = GetMainCharacter();
//LogIt("Leadership : " + crgchar.skill.leadership + " ");

if(CheckAttribute(crgchar,"fantomtype"))
{
if(crgchar.fantomtype=="trade")
{
nextenemy = Find_nearest_inf_ship(crgchar);
if(nextenemy != -1) // && Characters[nextenemy].ship.type != SHIP_FORT_NAME )
{
if(Check_courageous_merchant(crgchar, &Characters[nextenemy]) || GetCharacterShipClass(crgchar) < 3)
{
//TraceAndLog("I'm " + GetMyShipNameShow(crgchar) + " a brave merchant and I'm defending!");
Group_UnlockTask(groupe);
Group_SetTaskAttack(groupe, GetGroupIDFromCharacter(GetCharacter(nextenemy)), false); // PB: Relations should already be hostile
Group_LockTask(groupe);
}
else
{
//TraceAndLog("I'm " + GetMyShipNameShow(crgchar) + " a merchant and I have a too close enemy, so I'm running away!");
Group_UnlockTask(groupe);
Group_SetTaskRunaway(groupe);
Group_LockTask(groupe);
if(Group_GetTask(groupe)==AITASK_MOVE)
{
rGroup.memo.x = rGroup.Task.Target.Pos.x;
rGroup.memo.z = rGroup.Task.Target.Pos.z;
}
else
{
if(Group_GetTask(groupe)!=AITASK_ATTACK)
{
rGroup.memo.x = crgchar.ship.Pos.x;
rGroup.memo.z = crgchar.ship.Pos.z;
}
}
}
}
else
{
if(Group_GetTask(groupe)==AITASK_RUNAWAY || Group_GetTask(groupe)==AITASK_ATTACK)
{
if(CheckAttribute(rGroup,"memo"))
{
//TraceAndLog("I'm " + GetMyShipNameShow(crgchar) + " a merchant! There's no more danger, so I can return to my initial route!");
x = stf(rGroup.memo.x);
z = stf(rGroup.memo.z);
Group_UnlockTask(groupe);
Group_SetTaskMove(groupe, x, z);
Group_LockTask(groupe);
DeleteAttribute(rGroup,"memo");
}
//else{TraceAndLog("I'm " + GetMyShipNameShow(crgchar) + " an anchoring merchant!");}
}
else
{
if(Group_GetTask(groupe)==AITASK_MOVE)
{
//TraceAndLog("I'm " + GetMyShipNameShow(crgchar) + " a merchant sailing to a destination!");
Group_UnlockTask(groupe);
Group_SetTaskMove(groupe, stf(rGroup.Task.Target.Pos.x), stf(rGroup.Task.Target.Pos.z));
Group_LockTask(groupe);
}
//else{TraceAndLog("I'm " + GetMyShipNameShow(crgchar) + " an anchoring merchant!");}
}
}
}

if(crgchar.fantomtype=="pirate" || crgchar.fantomtype=="war")
{
//nextenemy = FindClosestShipofRel(crgchar.index, &enemydist, RELATION_ENEMY);
nextenemy = Find_nearest_inf_ship(crgchar);
if(CheckAttribute(crgchar,"runaway"))
{
//TraceAndLog("I'm " + GetMyShipNameShow(crgchar) + " a " + crgchar.fantomtype + " and I'm running away!");
Group_UnlockTask(groupe);
Group_SetTaskRunaway(groupe);
Group_LockTask(groupe);
}
else
{
if(nextenemy != -1)
{
//TraceAndLog("I'm " + GetMyShipNameShow(crgchar) + " a " + crgchar.fantomtype + " and I'm attacking " + Characters[nextenemy].id + "!");
Group_UnlockTask(groupe);
Group_SetTaskAttack(groupe, GetGroupIDFromCharacter(GetCharacter(nextenemy)), false); // PB: Relations should already be hostile
Group_LockTask(groupe);
}
else
{
// find new Quest_ship locator to patrol
if(PChar.location!="")
{
sLocationGroup = "Quest_ships";
sLocationLocator = GetRandomQuestShipLocator(PChar.location); // PB

rIsland = GetIslandByID(PChar.location);
sTst = sLocationGroup + "." + sLocationLocator + ".x";

if (CheckAttribute(rIsland, sTst) || CheckAttribute(rGroup,"memo"))
{
if(CheckAttribute(rGroup,"memo"))
{
if(GetDistance2D(stf(crgchar.ship.Pos.x), stf(crgchar.ship.Pos.z), stf(rGroup.memo.x), stf(rGroup.memo.z)) <= 500)
{
//TraceAndLog("I'm " + GetMyShipNameShow(crgchar) + " a " + crgchar.fantomtype + " and I'm patroling to " + sLocationLocator + "!");
x = stf(rIsland.(sLocationGroup).(sLocationLocator).x);
z = stf(rIsland.(sLocationGroup).(sLocationLocator).z);
rGroup.memo.x = x;
rGroup.memo.z = z;
}
else
{
x = stf(rGroup.memo.x);
z = stf(rGroup.memo.z);
//TraceAndLog("I'm " + GetMyShipNameShow(crgchar) + " a " + crgchar.fantomtype + " and I'm " + GetDistance2D(stf(crgchar.ship.Pos.x), stf(crgchar.ship.Pos.z), stf(rGroup.memo.x), stf(rGroup.memo.z)) + " yards from my next patrol point!");
}
}
else
{
//TraceAndLog("I'm " + GetMyShipNameShow(crgchar) + " a " + crgchar.fantomtype + " and I'm patroling to " + sLocationLocator + "!");
x = stf(rIsland.(sLocationGroup).(sLocationLocator).x);
z = stf(rIsland.(sLocationGroup).(sLocationLocator).z);
rGroup.memo.x = x;
rGroup.memo.z = z;
}
}
else
{
if(rand(1)==0){x = 10000.0 * rand(10);}else{x = -10000.0 * rand(10);}
if(rand(1)==0){z = 10000.0 * rand(10);}else{z = -10000.0 * rand(10);}
if(CheckAttribute(rGroup,"memo")){DeleteAttribute(rGroup,"memo");}
//TraceAndLog("I'm " + GetMyShipNameShow(crgchar) + " a " + crgchar.fantomtype + " without target or I've lost my target!");
}
}
else
{
if(rand(1)==0){x = 10000.0 * rand(10);}else{x = -10000.0 * rand(10);}
if(rand(1)==0){z = 10000.0 * rand(10);}else{z = -10000.0 * rand(10);}
if(CheckAttribute(rGroup,"memo")){DeleteAttribute(rGroup,"memo");}
//TraceAndLog("I'm " + GetMyShipNameShow(crgchar) + " a " + crgchar.fantomtype + " without target or I've lost my target!");
}
Group_UnlockTask(groupe);
Group_SetTaskMove(groupe, x, z);
Group_LockTask(groupe);
}
}
}
}
else
{
if(CheckAttribute(crgchar,"runaway"))
{
//TraceAndLog("I'm " + GetMyShipNameShow(crgchar) + " and I'm running away!");
Group_UnlockTask(groupe);
Group_SetTaskRunaway(groupe);
Group_LockTask(groupe);
}
else
{
nextenemy = Find_nearest_inf_ship(crgchar);
if(nextenemy != -1)
{
//TraceAndLog("I'm " + GetMyShipNameShow(crgchar) + " and I'm attacking " + Characters[nextenemy].id + "!");
Group_UnlockTask(groupe);
Group_SetTaskAttack(groupe, GetGroupIDFromCharacter(GetCharacter(nextenemy)), false); // PB: Relations should already be hostile
Group_LockTask(groupe);
}
else
{
//TraceAndLog("I'm " + GetMyShipNameShow(crgchar) + " and I'm doing nothing...");
}
}
}
}
 
That is definitely the correct section of code. :onya

If necessary, it should be easy enough to treat Pirates a bit differently there.
Just a matter of an extra 'if'.
 
So it should be something like:

if(crgchar.fantomtype=="pirate" && "something that represents a fort goes here"){
Group_SetTaskRunaway(groupe); //(or something like that)
}

All that inside this "if":

if(crgchar.fantomtype=="pirate" || crgchar.fantomtype=="war")

Or maybe this is nowhere near what it should look like.
 
Sounds about right to me! :onya

You'd need to loop through all ships/forts in the area and check if they're hostile and within range.
At the moment I cannot remember the best way to do that and can't check either for at least another week.

Maybe with the Tips & Tricks though, you could try to find out yourself?
Check All Ships does involve a loop over all ships and forts, so that should point towards a clue.
 
Thanks for working on this Eskhol! The code for this is a bit beyond what I am comfortable trying to work with yet, so I'm going to focus on easier modding tasks while learning, and hope that you are able to do interesting things on this particular problem. :)
 
Thanks for the kind words Tingyun! :) I'll try to do something but I don't have much time this week, hopefully I'll be able to do something for the next weekend.
 
At least I'll be able to be more supportive as there will no longer be an entire ocean separating me from my game files. ;)
 
@Eskhol

Just checking in and seeing how you've been. :) If you do manage to pull this off, and produce a testable version later, I'd love to include it in my experimental version so we can test it further.

The current suicide-by-fort Pirates are a little ridiculous, and way too easy of a source of prize ships when forts do the work.

If you've gotten too busy to do it, please let me know everything you've tried so far and what your current version is, and I'll take over work on it sooner or later. Totally understandable if life got on the way! :)
 
Good point, we should definitely do that.

@Grey Roger would you be able to provide some guidance? You are MUCH better at coding than I am, and I fear this one might be a bit beyond me, so maybe we could look at this together if Eskhol ends up being too busy? If you were able to set up something that might work as a first attempt, I could spend the time changing it around and playtesting to get it to work right. Just a rough first attempt would be of enormous help.

I think where we left off was:

Eskhol said:
"So it should be something like:

if(crgchar.fantomtype=="pirate" && "something that represents a fort goes here"){
Group_SetTaskRunaway(groupe); //(or something like that)
}

All that inside this "if":

if(crgchar.fantomtype=="pirate" || crgchar.fantomtype=="war")"

Then Pieter said:

"Sounds about right to me! :onya

You'd need to loop through all ships/forts in the area and check if they're hostile and within range.
At the moment I cannot remember the best way to do that and can't check either for at least another week.

Maybe with the Tips & Tricks though, you could try to find out yourself?
Check All Ships does involve a loop over all ships and forts, so that should point towards a clue."

Maybe Eskhol got farther than that, but from what he said I think he very understandably got busy with real life.
 
Good point, we should definitely do that.

@Grey Roger would you be able to provide some guidance? You are MUCH better at coding than I am
Doubtful, which is why I stick to developing my storyline and the occasional minor tweak. That way, if I foul up, it's not going to ruin the whole game. xD
 
Or we can keep this on the wish-list for a while longer.
The current state isn't ideal, but it doesn't do any major harm either. :shrug
 
Don't bother about this to much now please :).
it will be fixes in B15
 
@Levis Was working with the surrender code, and stumbled across a way to fix this bug as well. So no need to wait for B15, it seems like it is an easy fix.:D

I have the AI do a powerratio check, and run if overwhelmingly outmatched and have good sails. As a consequence, they run if they spawn near a fort (or at least in my tests, the coastal raiders all quickly turned and ran from the fort, they should only stay and fight if they are very powerful).

The powerratio check there was previously only used for modifying surrender chances, and it had been accidentally reversed by whoever wrote it (so the AI was less likely to surrender when outmatched). I fixed that error and then wrote an additional retreat check using it, so now the AI makes intelligent withdrawal decisions if heavily outmatched.

Needs Testing - Intelligent AI retreats and surrenders | PiratesAhoy!
 
Last edited:
@Pieter Boelen For reference for our earlier discussions on this and Sea AI, here are the functions that control this (the findnearships they use seems to include forts, and tests in game have coastal raiders running from forts, at least in my early tests):

Really shocking this wonderful FindPowerRatio function below, with all its potential for intelligent AI decision making, was only ever used once previously (and with an error that reversed the intended effect), to slightly modify surrender chance. What a waste!

So for future reference, we have this wonderfully useful ready-made route to more intelligent AI decisions. :)


EDIT: deleting question I was able to figure out the answer to, see below later comment


Code:
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 0.0;
   float epower = FindPowerOfNearShips(idx, RELATION_ENEMY);
   float fpower = FindPowerOfNearShips(idx, RELATION_FRIEND);
   if(epower < 0.5) epower = 0.5; //TY changing to 0.5 to match the limit
   if(fpower < 0.5) fpower = 0.5;
   return epower / fpower;
}

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")) 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));
   }
   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
   }
   return power;
}
 
Last edited:
@Pieter Boelen please ignore my earlier question, I am now fairly confident the below part of the code counts the AI character in question's own ship.

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
}

Which matches my testing so far.

To make sure the mechanic itself is working, I alternately tried setting the AI to retreat when power ratio is >1 or <1, and then sailed around in a small ship, which alternately caused enormous fleets to flee at the mere sight of my tiny ship (with the reversed >) or attack relentlessly (with the correct direction), which makes me confident it is counting things properly and the direction is right.

I then set it to 2.0, and picked some fights with enemies I only slightly overpowered or who overpowered me, and they behaved properly and didn't flee.

Will need further testing of course, but looks to be working well so far. :)


By the way, I found where this wonderful function for power ratio comes from-- Nathan Kell wrote it in 2005, and seemed happy about the potential for it. May I present: | Page 2 | PiratesAhoy!
 
Last edited:
Back
Top