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

Confirmed Bug Sea Relations: When commander of group surrenders the whole group surrenders

Yep, weird right?

It made testing the bug hard, because I had to find a new fleet everytime.

The save alongside that allied fleet just would not generate the bug, tried 3 times with raising the pirate flag and consoling the flagship to surrender. Instead, the other ships would stay properly hostile and keep firing at me.
 
Just tested a 4th time, it is completely reliable, loading a save right before going hostile to the other fleet by raising a pirate flag prevents the bug from occuring, it doesn't just fix it by refreshing.

BTW, in case anyone wants it, here is a console code for forcing the nearest ship to surrender:
float enemydistance = DIST_NOSHIP;
int targetidx = FindClosestShipofRel(sti(pchar.index), enemydistance, RELATION_ENEMY);
Ship_Surrender(targetidx);
 
@Pieter Boelen @Levis @Grey Roger

I think I've got the bug fixed. I'm putting it into my AI changes for further testing, but here's the full report. A somewhat abbreviated summary later down.

First, the full explanation:

The bug comes from running this line at the end of the surrender code:
RefreshBattleInterface(true);

The first part of the fix is changing to:
RefreshBattleInterface(false);


Which means when the refreshbattleinterface is run, this part is skipped
if (CheckRelations)
{
CheckAllShips("forts", true); // PB: Set initial relations for ships
CheckAllShips("ships", true); // PB: Set initial relations for forts

UpdateRelations();
}

The checkallships function is in screwface, and I think the difference in having a previously loaded game protecting against the bug might be this check in the checkallships functions doesn't seem to come back true if previously loaded (at least, my trace placed there didn't fire during surrender, so for some reason it didn't get to it)
if (CheckAttribute(PChar, "seaAI.update."+(type) )) {

Which would skip the updating of relations and prevent the bug if you had previously loaded.

I put a trace in there, and that section doesn't run right after surrender if a game had previously been loaded right before (ie, when the bug does not occur), but it does run right after surrender if not (ie, when the bug occurs).

However it isn't specifically that section, as that trace WAS coming back as occuring just normally every once a while sailing around.

The key seems to be that when refreshbattleinterface calls it and you haven't recently loaded, that part gets run, and then since the refreshbattleinterface sets the bool to true, you then also get this subsequent part that only runs if the above is met:
if (initialize)
{
CheckInitialFlagRelations(chr, visibility_range, ship_range);
}


One thing, at first I thought to only skip the true part if a flagship, by setting a variable and then doing something like this:
if(Flagshipcapture == 1) {RefreshBattleInterface(false);} // KK //TY Fixing flagship surrender bug
else {RefreshBattleInterface(true);}

But that won't work right always, because if a non flagship later gets captured, then it will refreshbattleinterface true and trigger the bug.


So, to summarize:

SOURCE OF THE BUG: After a flagship surrender, running refreshbattleinterface runs CheckAllShips with the initilize setting marked to true, which runs CheckInitialFlagRelations(chr, visibility_range, ship_range);. If you have previously loaded a save right before starting battle but next to friendly ships, it seems that cuts this off and doesn't let it get to that check. If you haven't (which in almost all cases you haven't) then it does run that function and the bug occurs.

Loading a saved game afterwards seems to clear that bug, as mentioned, but probably for a very different reason than it blocks the bug.

So the bug is that anytime CheckInitialFlagRelations(chr, visibility_range, ship_range); runs for all ships after a flagship surrender, the bug occurs. If that doesn't get run, things go on normally.


THE FIX: change the surrender code to run RefreshBattleInterface(false); instead. Cannot be limited to just flagship capture, because if it runs later when a normal ship surrenders, it will trigger the bug from the previously captured flagship



INITIAL TESTING: I ran some tests, and so far this solution seems to work perfectly fine. Captured several flagships, his underlings kept firing at me, could capture them as well, battles played out nicely. Tested about 10 times under various battle situations.


ONLY PROBLEM: While the surrendered ship goes fully passive as it should, if 2 nations are present, then enemy ships may still continue firing on it, and it will block fast travel if close to the player.

EDIT: solutions found, a combination of retargeting and full refresh when the last enemy surrenders, see below posts
However, this is only until the player boards at least 1 ship--as soon as he does that, everything goes to as it should be when the sea is reloaded (the bug does NOT trigger, just allied ships stop shooting at the surrendered one). So whatever is done to reinitilize the sea after a ship is boarded, is sufficient to set AI orders correctly so they stop targeting surrendered ships, but it doesn't cause the bug.

I think this is a pretty minor issue in comparison, but there are two possible solutions:
1) Run the check initial relations but only for the surrendered character ship using CheckInitialFlagRelations(), so only reset his relations to everyone. I tried this, but couldn't get it to work, I think I just don't understand relations well enough.
2) Write a special function to make surrendered ships go neutral to everyone (except the player character maybe? blocking fast travel doesn't seem a problem to me, the player can just sail a bit away the surrendered ship, and presumably he will want to board it. Would there be bad consequences for player rep if they board a ship that is set to neutral? Or does all that happen beforehand and it would be fine?)

I'll see if I can do the remaining parts, but I may run out of time first (It took me about 8 hours to finally find the source of this and fix it, so I'm pretty tired, especially after 20 hours this weekend working on the AI generally). This solution has been fully tested with over 10 trials, so far no issues cropping up other than the minor one noted above.

EDIT: and those final parts fixed now as wel, see below.
 
Last edited:
EDIT: Ah, the surrendered ships are already neutral to everyone. Figured out the remaining issue, see below.

Well no luck on getting the AI to stop firing on surrendered ships.Maybe someone can look at the function I wrote and figure out what went wrong. Maybe I am using the wrong inputs for the SetCharacterRelationBoth(sti(chr.index), sti(chr2.index), RELATION_NEUTRAL); or something basic like that.

Code:
/*void WeGiveUp(int idx)  //TY Surrendered ship now needs a special function to go neutral to everyone, to fix flagship surrender bug. Not going neutral to player though, not sure of the implications
{
    if(idx<0) return;
    int pchr = GetMainCharacter();
    int num = FindNearShips(idx);
    ref chr = GetCharacter(idx);
    ref chr2 = GetCharacter(idx);
    int sidx = sti(chr.curshipnum);
    string tstr = "rel"+RELATION_ENEMY;
    string tstrr = "rel"+RELATION_FRIEND;

    if(num <= 0) {return;} //TY Don't reset if only the player or noone around, for now, not sure of implications
    string tstr2;
    if(CheckAttribute(NearShips[sidx],tstr+".0"))
    {
        for(int i = 0; i < sti(NearShips[sidx].(tstr).qty); i++)
        {
        tstr2 = i;
        chr2 = GetCharacter(sti(NearShips[sidx].(tstr).(tstr2).idx));
  
        if (chr2 != pchr && chr2 != chr)
            {
            SetCharacterRelationBoth(sti(chr.index), sti(chr2.index), RELATION_NEUTRAL);
            }
        }
    }
    string tstr3;
    if(CheckAttribute(NearShips[sidx],tstrr+".0"))
    {
        for(int j = 0; j < sti(NearShips[sidx].(tstrr).qty); j++)
        {
        tstr3 = j;
        chr2 = GetCharacter(sti(NearShips[sidx].(tstrr).(tstr3).idx));
        if (chr2 != pchr && chr2 != chr)
            {
            SetCharacterRelationBoth(sti(chr.index), sti(chr2.index), RELATION_NEUTRAL);
            }
        }
    }
}*/

Anyway, at least no more abrupt ends to all battle when a flagship surrenders. The remaining problem is only an issue with allied battles, and the AI doesn't seem to care much about the surrendered ships anyway, it seems to focus on other ships and seems to get bored and move on if everything surrenders. And if the player boards a single surrendered ship, everything updates correctly.

So for 99% of circumstances, the bug is fixed and no more abrupt ends to battles or needing to load. Just some slight remaining oddness in allied battles of allies taking some final shots before you board and accept surrender. MUCH less weird than everyone stopping firing of course. ;)

Maybe someone better at coding can help me look at that final part, I'm about to be out of time, going away for several days to celebrate the end of work project with my family, so gone after tomorrow until the following week.:)

In the meantime, I'll put the fix in my new AI improvement release I'm about to upload, and we'll see how it goes.
 
Last edited:
Ok, so the surrendered ships are already neutral to everyone with the fix, the only issue is getting the AI ships to retarget.

I did a little change to AISHIP targeting logic, and it seems to have at least have the AI retarget to a live vessel if they were targeting a surrendered one. However, if the entire enemy fleet is surendered, and they are in attack mode, they seem to keep firing.


This should fix everything except when all of the following are true: A) there is an allied fleet in attack mode B) all of the enemy fleet has surendered and there are no more possible targets, and C) the player has for some reason elected not to board any surrendered vessels. (Boarding a vessel corrects that and does not trigger bug).

This seems both minor and very solvable, but I need to call it a night for now. :)


I'll put the fix into the new version of my AI improvements and upload now.
 
Last edited:
Ah, figured it out just now. I'll just have a check to run the refresh function set to true for initialize instead of false if the surrendering ship was a member of a 1 person fleet (surrendered ships are transferred to a new group as they surrender, so entually the enemy fleet will only have 1 person). A variation of the commander change dependency I mentioned earlier, but one that shouldn't cause the same problem.

Then flagship surrenders won't cause the bug, and if enemies remain, the retargeting will keep the surrendered safe, and if the final enemy surrenders, a full refresh with true for initialize will happen.

That should fix everything. Will upload the final bit tomorrow. :)


EDIT: Hmmm..The only case that would remain is if you engage two separate hostile fleets at the same time, like a pirate jumping into the middle of two national fleets fighting, and you then force the flagships of both fleets to surrender, and then force the last member of one of the fleets to surrender. But that has got to be pretty rare.

I think we can still call it 99% fixed.:p
 
Last edited:
But when a ship surrenders, the Battle Interface MUST be run with 'true'.
If you call it with 'false', then you might as well not bother calling it at all.

The whole point is that the relations are updated correctly between all ships and forts so they all acknowledge the surrender.
If you bypass it, then of course relations are never updated and the bug doesn't trigger either.
But you'd get a whole new bug instead because of bypassing code that was being called on purpose. :facepalm

I'm rather familiar with the functions you've been investigating here, because I wrote them as part of my massive fix in the past to get Nations Relations to actually work the way they're meant to.

I won't claim it is perfect and it could very well be there are some scenarios that may not be handled quite correctly.
But I will claim that the way it currently is, is not by accident.
A lot of thought, consideration and testing went into it. ;)

Actually, if you call the function you mention with 'false', it can be called with 'true' later anyway through other code.
For example if you decide to hoist another flag, it would do it too.
I can't remember now if it also gets called when loading a Save At Sea.

Does this mean the surrender now doesn't trigger the bug, but the bug does get triggered if you hoist another flag afterwards?
 
The only purpose I can see of updating nation relations at the point of surrender is to keep other ships from firing on the surrendered ship, or to allow fast travel without boarding the ship or sailing away.

The first goal can be achieved using the targeting logic to redirect the ships to fire at the closest enemy if current target is surrendered, and running the full refresh with true if the last enemy in the group surrenders (as detailed above).

The second goal is minor, and also achieved by running the full refresh after the last enemy surrenders.

EDIT: hoisting another flag would trigger the bug only if you are hositing another flag after a flagship surrenders, and all it would do is put you back in the same situstion as before with the bug, not make anything worse, and not make it happen if it wouldn't have happened before. in thst case the fix still would have helped, it would have delayed the bug until you did that. Also wtwo hostile fleets at the same time with both flagships surrendered case I mentioned.

But we fix 99% of the occurences of the bug, and we certainly don't create any new occurences.


Can't see any problems with this, with retageting logic mentioned, the full refresh should only be needed when the last member of the group surrenders, no?
 
Last edited:
@Pieter Boelen Ah I understand your last question--no, you misunderstand I most certainly have NOT changed anything in how the relations system functions anywhere.

All I have done is change the call of that function at the end of surrender code, in that one time. And I will only have it hold off on the full refresh when there are remaining group members.

Nothing else will be affected, hence shouldn't create any problems anywhere else. Will fix the bug in almost all cases (except for rare things like hoisting a flag after flagship surrender, in which case it will have at least delayed the bug), and will not cause the bug to occur any place it wouldn't have occured before.

To summarize: With the fix, flagship surrenders won't cause the bug, and if enemies remain, the retargeting will keep the AI focussed on ships that haven't surrendered, and if the final enemy surrenders, a full refresh with true for initialize will happen exactly as before. I can't see any purpose of initializing nation relations at each earlier surrender that isn't fully handled by that, the only difference is we don't cause the bug.
 
Last edited:
Why would update relations cause the ships to become neutral though?
 
@Levis no clue, but I have tested my fix over 10 times, using the console to force surrenders of flagships of various fleets, and holding off on the refresh fixes the problem.

Interestingly, the ships do know who their friends and enemies are perfectly correctly without the refresh. I can see from my traces, as ships surrender, they are already neutral, and not enemy, relations to the former combatents. That is why the retargeting works to have them stop firing at surrendered ships. And surrendered ships know full well how to behave themselves, everything works fine.

So the solution is:

1) retargeting logic to tell them to pick a closest enemy if they are targeting a surrendered vessel. The ships relations are updated to neutral anyway, so telling them to target closest enemy if they were targeting a surrendered vessel works as long as there is at least 1 enemy remaining anywhere.

2) doing a full refresh of relations when the last enemy ship in a group surrenders. (This part I still need to add for the version I will upload tomorrow)


One interesting thing--if the player boards a ship at any point, relations are initilized correctly afterwards without triggering the bug. Boarding doesn't fix the bug if it already happened, but it does set everything correctly without triggering the bug. Which means as long as we don't instantly run the full refresh before the player has a chance to board, the bug doesn't occur.


Basicaly, the engine limitation was real, reinitilizing relations at sea can cause this bug if a flagship switch happened.

But the fixable bug is that we don't need to do a full refresh after every surrender, we just need to retarget the AI away from firing at surrendered ships (since the surrendered ships are changed to relation_neutral even without the refresh). Then, when either the last ship surrenders, OR the player boards any ship to accept a surrender, the relations are fully refreshed without triggering the bug.
 
Last edited:
This bug predates the existence of that whole 'CheckInitialFlagRelations' code, so therefore the root cause cannot be in there.
You might be able to hide the bug by skipping that code from being called, but that is not a real fix.

If you don't want the relations to update, then you don't need to call RefreshBattleInterface(false); because you might as well not call anything at all.
The reason why RefreshBattleInterface is being called there is because the relations should be updated, since they just changed.

I had hoped my complete rewrite on this might have fixed this bug. Unfortunately it didn't and I never bothered to really investigate why.
One advantage of the rewrite is that now at least after loading a Save At Sea, relations are set correctly. That wasn't the case before.
So that was at least some slight improvement made there.

But as far as I can tell, for a proper fix, further investigations need to be made to track down the real root cause.
That is not going to be easy.
 
You might be able to hide the bug by skipping that code from being called, but that is not a real fix.

In some technical sense maybe, but if the bug being "hidden" means it doesn't occur in 99% of cases, I'd call that 99% fixed.

A hidden bug thst never bothers players might as well not exist. :)

If you don't want the relations to update, then you don't need to call RefreshBattleInterface(false); because you might as well not call anything at all.

Got it, I'll only call it when it is the last group member surrendering.

So that was at least some slight improvement made there..

Actually Pieter, your changes then might be what makes this work.

My method only works because the ships do actually know who is neutral and who is an enemy relation even without the refresh, at least when targeting decisions and such call for the relations. And because any boarding action refreshes all relations correctly without triggering the bug.

But as far as I can tell, for a proper fix, further investigations need to be made to track down the real root cause.

Well, it is a bug that has been plaguing the game for 2+ years, and I have a way to eliminate 99% of the cases in which it occurs. I'd say that is some serious progress, and basically resolves the issue. But if someone wants to dive into the code to find the root cause, they can certainly do so, but seeing as how thst hasn't happened in 2+ years I don't think we should hold our breath waiting. ;)

We are basically moving this from "engine limitation, hopeless" to "won't occur in almost all cases", which is pretty good I'd say. Basically a player would have to pick a fight with two hostile fleets at the same time and force both flagships to surrender and then all of one side to surrender, all without boarding a single ship. Or deciding to hoist a pirate flag right next to a bunch of surrendered ships without boarding one of them. I'm going to go out on a limb and say most players will never, ever see this bug again. ;)

Whether this method is technically called a "fix" or a "workaround" for the bug, the problem will finally be fixed for players, which is all that matters, and worthy of some celebration I think. :)

Well, goodnight everyone, I'll get the final parts of the fix up for further testing tomorrow.
 
Last edited:
@Pieter Boelen I took a look at the functions which are called and this is one of them:
Code:
bool CheckAllShips(string type, bool initialize)
{
    int i, shipidx, num;
    aref shipattr;
    string shipstr;
    ref chr;
    float ship_range = 0.0;
    float visibility_range = 0.0;
    ref PChar = GetMainCharacter();

    if (!initialize) // Do this here for performance
    {
        if (GetCurrentFlag() == PERSONAL_NATION)    return false; // Never a false flag
        if (iForceDetectionFalseFlag == -1)            return false; // Override: Never detect
    }

    bool Recognized = false;
    if (CheckAttribute(PChar, "seaAI.update."+(type) )) {
        makearef(shipattr, PChar.seaAI.update.(type) );
        num = GetAttributesNum(shipattr);
        for (i = 0; i < num; i++) {
            shipstr = "l" + i;
            shipidx = sti(shipattr.(shipstr).idx);
            if (shipidx < 0) continue;
            chr = GetCharacter(shipidx);
            ship_range       = shipattr.(shipstr).distance;
            visibility_range = GetCharVisibilityRange(chr, 2); // KK: Ship nation is visible inside MEDIUM range
            if(Whr_IsNight() && HasCharacterShipAttribute(PChar, "night_stealth")) visibility_range /= 2;    // PB: Black Pearl stealth at night
           
            if (initialize)
            {
                CheckInitialFlagRelations(chr, visibility_range, ship_range);
            }
            else
            {
                if (ship_range < visibility_range)
                {
                    bool bCheckInitial = false;
                    if (!CheckAttribute(chr, "PlayerShip"))                                    bCheckInitial = true;    // GR: If you can be seen and aren't already logged, you are now
                    if (CheckAttribute(chr, "PlayerShip") && !HasThisShip(chr.PlayerShip))    bCheckInitial = true;    // GR: If you are already logged and the log is out of date, update it
                    if (bCheckInitial) CheckInitialFlagRelations(chr, visibility_range, ship_range);
                }
                Recognized = CheckForMainCharacterfalseflag(chr, visibility_range, ship_range);
            }
        }
    }
    return Recognized;
}
If i understand well it should call this function but use the false value for initialize right? Because the values are already initialized.
So what about doing what @Tingyun suggest but after that call these two functions but with the initialize set to false? then the falseflag detection should also work fine and if stuff does needs to be changed or set again it will do it.
 
@Levis EDIT: oh I see, but I think checkallships runs constsntly in the background with initilize set to false, see below post. I don't think running it here an extra time at surrender would do anything at all, neither harmful nor helpful.
 
Last edited:
I mean after calling RefreshBattleInterface(false); you also call:
Code:
CheckAllShips("forts", false); // PB: Set initial relations for ships
CheckAllShips("ships", false); // PB: Set initial relations for forts

UpdateRelations();
does it still work then?
 
@Levis oh my mistake, I see what you mean. :)

The answer is it would be fine, but it won't acomplish anything, because checkallships with initilize at false runs every few moments no matter what you do.

Checkallships is running constsntly in the background, if you sail around it is running everytime a few seconds passes. Doesn't mess anything up, but doesn't acomplish anything related to this bug. I had traces set up to test it it. It runs every minute or so it seems, with initilize set to false.


So the only thing the initilize true reset on surrender is needed for is to stop ships from firing at the surrendered ship, but since the surrenderd ship will no longer have relation_enemy designation anyway (I tested this thoroughly, it doesn't need the refresh), it is simple to just do thst with targeting logic. There was already a check thst almost worked for thst in place, I just needed to alter it slightly. Then as long as you full refresh after the last ship in the group surrenders, or the player boards any surrendered ship, the AI groups update their orders when the last enemy goes away and everything works fine.

Or, at least it was all working great in the 10+ tests I ran, finding fleets and fleet battles between two nations and forcing them to surrender under various circumstances with console commands. We'll see through more testing, but I think this should resolve the problem without any substantial issues.

Well, off to sleep for now. I'll get the final step, the full refresh on last member of group surrendering, added to the file and reuploaded tomorrow.

Goodnight everyone! :)
 
Last edited:
I believe the AI system isn't that good so I can imagine even if the relations are changed and updated well the ships might still fire if the tasks aren't updated either...
I hope I'll be able to make a new zip soon(ish). we could include this then (behind a toggle so it's easy to revert it if needed) to get some more test data.
 
Yep, the task updates actually aren't hard, it just looks like

If(guy you are targeting has attribute surrendered) target closest enemy instead.

There was a check for that written already actually, but it wasn't fully working, but started working when I made a slight change. Then a full refresh when the last enemy surrenders gets them to resume their group task (once I add that tomorrow), when targetclosest enemy would start coming back empty and so wouldn't override the targeting of surrendered enemies anymore. The full refresh also lets the player go to worldmap instantly (formerly hostile enemies block worldmap until boarded or you sail away otherwise), if player for some reason doesn't want to board surrendered enemies, which is also only really relevant for the last enemy in the group, as you won't be poppong into worldmap if enemies are still around anyway.

The earlier refreshes while enemies were still remaining were nice, but it isn't hard to achieve what they acomplished through other means, like retargeting code. Especially since any boarding does refresh everything correctly without triggering the bug, and the player will be boarding these surrendered enemies.
 
Last edited:
I cannot approve of this, sorry. Hiding a bug is the worst kind of bug "fixing" there is.
Basically you end up adding extra code to deliberately make the code NOT do what it is meant to do.
With fixing bugs, I firmly they should be fixed properly or not at all.

Faking is worse because it convolutes the code and basically ensures nobody is ever going to do it properly.
And with a bit of bad luck, it'll cause weirdness elsewhere instead and before you know it, you're chasing your own tail and going nuts.
This is just like all those one million other things that were wrong in the game before, but nobody noticed because "they were hidden".
I've spent 10 years dealing with that, so I know what it is like and you really don't want that.

On the actual subject, I think if you skip calling that function, then if the flagship surrenders, it'll continue showing as RED on the minimap.
While of course it should turn GREY instead to indicate its surrender.

As long as I still make the official EXE files, this change will NOT go into any official releases. Not until it is fixed for real.
 
Back
Top