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

Fix in Progress Captive captains are just clones

Grey Roger

Sea Dog
Staff member
Administrator
Storm Modder
In "Tales of a Sea Hawk", it is possible to take Silehard - and, for that matter, Isenbrandt Jurcksen - prisoner when you sail to Cozumel. When you return to Port Royale to visit the new governor, he says "Well, His Majesty will be sorry not to have Silehard in the Tower. But this was just as final." I've always taken that as being an unofficial way of telling you to dispose of Silehard yourself so that there isn't the potential embarrassment of a governor being in court for corruption. But in fact there is supposed to be alternative dialog if Silehard is your prisoner. And it's not being triggered. It turns out that character "Robert Christopher Silehard" does not have the "prisoned" attribute, which is what the dialog checks.

A bit of console jiggery-pokery:
Code:
if (isprisoner(characterFromID("Robert Christopher Silehard"))) traceandlog("Silehard is a prisoner");
else traceandlog("Silehard is NOT a prisoner");
if (IsPassenger(characterFromID("Robert Christopher Silehard"))) traceandlog("Silehard is a passenger");
else traceandlog("Silehard is NOT a passenger");

traceandlog("Passengers:");
   for(i=0; i < GetPassengersQuantity(pchar); i++)
   {
       n = GetPassenger(pchar, i);
       if (n < 0)           continue;                   // Skip invalid characters
       ch = GetCharacter(n);                                                                   // Reference to the character
       if(!CheckAttribute(ch,"index"))   continue;                   // Skip invalid characters
       traceandlog("ID: " + ch.id + " - Name: " + GetMySimpleName(ch));
   }
And this is the result:
Code:
Silehard is NOT a prisoner
Silehard is NOT a passenger
Passengers:
ID: Danielle - Name: Nathaniel Hawk
ID: Researcher - Name: Clement Barnabas Aurentius
ID: Enc_Officer_2 - Name: Symon Hoppe
ID: Enc_Officer_3 - Name: Peter Verplancken
ID: Fred Bob - Name: Fred Bob
ID: Enc_Officer_4 - Name: GhibeDender Schuiling
ID: Enc_Officer_1 - Name: Hilary Trinder
ID: Rys Bloom - Name: Rys Bloom
ID: Edgar Attwood - Name: Edgar Attwood
ID: Enc_Officer_12 - Name: Bebe Caballero
ID: Enc_Officer_13 - Name: Argentina Chissano
ID: Enc_CabinCaptain_4 - Name: Fiebras Nattier
ID: Enc_CabinCaptain_18 - Name: Lewellyn Belt
ID: Enc_Officer_7 - Name: Bastian Froelich
ID: Enc_Officer_8 - Name: Simone Aubarat
ID: Virginie d'Espivant - Name: Virginie d'Espivant
ID: Enc_CabinCaptain_13 - Name: Gaspar Gomiz
ID: Enc_Officer_14 - Name: Julia Parlabean
ID: Enc_Officer_6 - Name: Alison Martyr
ID: Sabine Matton - Name: Sabine Matton
ID: Enc_Officer_15 - Name: Isenbrandt Jurcksen
ID: Enc_CabinCaptain_14 - Name: Robert Christopher Silehard
Notable entries in that list are "Enc_CabinCaptain_18", alias Lewellyn Belt, one of Silehard's captains during the Bridgetown counter-attack; "Enc_Officer_15", alias Isenbrandt Jurcksen; and "Enc_CabinCaptain_14", alias Robert Christopher Silehard. And that's why character "Robert Christopher Silehard" doesn't show up as a prisoner, or for that matter, even as a passenger.
 
Whenever you take a captain prisoner or hire one, indeed a clone is made.
This is needed so the code still believes the captain "died", allowing quests and other game functionality to proceed as if he did.

How do prisoners work again? I remember they used to only show up in the Passengers interface, with buttons to handle them.
But that was changed so they show up in the cargo hold instead for you to talk with.
Do they still show in the Passengers interface at all now? Do Silehard and Jurcksen show anywhere in the interfaces after the battle?

If Silehard is recognized as neither a passenger or a prisoner, then what IS Silehard marked as?
Your console check proves that he IS some sort of "passenger", even though the functions don't seem to realise.
Could you show the function definitions for 'IsPassenger' and 'IsPrisoner'?
 
Prisoners show up in the "Passengers" interface but there is no button to ransom them. You do indeed go to the hold to talk to them. That's how, in previous games, I was able to follow the new governor's apparent sneaky command to execute Silehard.

The result of the console code shows that Silehard isn't marked as anything. Another console version with a simple 'dumpattributes(CharacterFromID("Robert Christopher Silehard"));' confirmed that. The prisoner in your hold is not "Robert Christopher Silehard", it's "Enc_CabinCaptain_14". Likewise, I don't have character "Isenbrandt Jurcksen", I have "Enc_Officer_15", who probably changed from "Enc_CabinCaptain" to "Enc_Officer" because I hired him.

Here's "IsPassenger":
Code:
bool IsPassenger(ref _refCharacter)
{
   // Old check Depreciated by PB
//   if(!CheckAttribute(_refCharacter,"index"))        return false;                               // MAXIMUS
//   if (CheckAttribute(_refCharacter, "passenger"))   return sti(_refCharacter.passenger);

   int cn;
   ref chr;
   ref pchar = GetMainCharacter();
   for(int i=0; i < GetPassengersQuantity(pchar); i++)
   {
       cn = GetPassenger(pchar, i);
       if (cn < 0)                                                   continue;                   // Skip invalid characters
       chr = GetCharacter(cn);                                                                   // Reference to the character
       if(!CheckAttribute(chr,"index"))                           continue;                   // Skip invalid characters
       if(CheckAttribute(_refCharacter, "index") && CheckAttribute(chr, "index"))
       {
           if(sti(_refCharacter.index) == sti(chr.index))           return true;               // This character is in the captain's passenger list
       }
   }

   return false;
}

Now looking at function "TIH_PrisonerTakenProcess", defined in "Dialog_func.c", which is what does the cloning. What I'm now thinking is to add a new attribute, perhaps call it "OldID", which stores the prisoner's original ID. And then have "IsPassenger" check if each character has the "OldID" attribute; if so, does it match the ID of "_refCharacter"; if so, return true, you've just found _refCharacter's clone.
 
The result of the console code shows that Silehard isn't marked as anything.
Ah, of course! Different character ID. :facepalm

What I'm now thinking is to add a new attribute, perhaps call it "OldID", which stores the prisoner's original ID. And then have "IsPassenger" check if each character has the "OldID" attribute; if so, does it match the ID of "_refCharacter"; if so, return true, you've just found _refCharacter's clone.
Sounds interesting, but potentially complicated.

Alternatively, maybe you could use your loop through passengers to check for a character with name "Robert Christopher Silehard" in that single spot where it is needed for the quest dialog?
 
Sounds interesting, but potentially complicated.
It shouldn't be that complicated. The whole idea is not to alter the existing operation of "TIH_PrisonerTakenProcess" except to add a new attribute, so it will still create the clone, and the original character will still be marked as dead.
Alternatively, maybe you could use your loop through passengers to check for a character with name "Robert Christopher Silehard" in that single spot where it is needed for the quest dialog?
That doesn't help with any other quest captains you may capture in other storylines.
 
That doesn't help with any other quest captains you may capture in other storylines.
True. Unless you make it a function that checks on 'SimpleName'.
But effectively then you end up with something very similar to what you were already proposing anyway. :cheeky

It shouldn't be that complicated. The whole idea is not to alter the existing operation of "TIH_PrisonerTakenProcess" except to add a new attribute, so it will still create the clone, and the original character will still be marked as dead.
Fair point.
I do wonder is if it could cause problems further down the line, because the same character IDs are being cycled through.
But if there is a 'ClearCharacter' statement first (I think; it is a function that completely erases an earlier character before his ID is reused), it should be fine.

And even if not, a newly-added attribute doesn't harm anything, except potentially in the functionality that was added to use it.
But the specific quest case where it is checked for that character should really only happen once.

Long story short: You're probably safe to do exactly what you have in mind. :onya
 
I do wonder is if it could cause problems further down the line, because the same character IDs are being cycled through.
Generic ID's are reused. Specific ID's such as quest characters aren't. This is the relevant part from "TIH_PrisonerTakenProcess":
Code:
       if(!HasSubStr(RefChar.id,"Enc_CabinCaptain"))
       {
           CopyAttributes(&Prisoner, &RefChar);// copy all attr from the reference character (the hiring officer) to the new officer char

           Prisoner.index = PrisonerIdx;// reset these after the copyattr
           Prisoner.id    = PrisonerID; // reset these after the copyattr
           Prisoner.oldID = RefChar.id; // Note the reference character's ID. Checks such as 'IsPassenger' can pass if a clone's "oldID" matches the ID being checked
       }
The "oldID" attribute is only set if the original character isn't already a generic "Enc_CabinCaptain". Then "IsPassenger" checks it:
Code:
    for(int i=0; i < GetPassengersQuantity(pchar); i++)
   {
       cn = GetPassenger(pchar, i);
       if (cn < 0)                                   continue;   // Skip invalid characters
       chr = GetCharacter(cn);                                                                   // Reference to the character
       if(!CheckAttribute(chr,"index"))                       continue;   // Skip invalid characters
       if(CheckAttribute(_refCharacter, "index") && CheckAttribute(chr, "index"))
       {
           if(sti(_refCharacter.index) == sti(chr.index))               return true;   // This character is in the captain's passenger list
           if(CheckAttribute(chr,"oldID") && chr.oldID == _refCharacter.id)   return true;   // This character was taken prisoner and this passenger is the character's prisoner clone
       }
   }
Next: make "RemovePassenger" look for and remove a clone. Otherwise the game is going to detect that Silehard is a passenger and try to remove the original, leaving you still stuck with the clone. :facepalm
 
SO quick question, why would a copy need to be made if it isn't a generic character? can't you just use the normal character?
 
SO quick question, why would a copy need to be made if it isn't a generic character? can't you just use the normal character?
Whenever you take a captain prisoner or hire one, indeed a clone is made.
This is needed so the code still believes the captain "died", allowing quests and other game functionality to proceed as if he did.
;)

This applies equally to quest characters. For example, the code to detect the end of the penultimate battle at Cozumel, against Silehard and his pirate pals, is:
Code:
           Pchar.quest.Story_SinkPiratesAtKhaelRoa.win_condition.l1 = "NPC_Death";
           Pchar.quest.Story_SinkPiratesAtKhaelRoa.win_condition.l1.character = "Isenbrandt Jurcksen";
           Pchar.quest.Story_SinkPiratesAtKhaelRoa.win_condition.l2 = "NPC_Death";
           Pchar.quest.Story_SinkPiratesAtKhaelRoa.win_condition.l2.character = "Brian The Slayer";
           Pchar.quest.Story_SinkPiratesAtKhaelRoa.win_condition.l3 = "NPC_Death";
           Pchar.quest.Story_SinkPiratesAtKhaelRoa.win_condition.l3.character = "Thomas Norton";
           Pchar.quest.Story_SinkPiratesAtKhaelRoa.win_condition.l4 = "NPC_Death";
           Pchar.quest.Story_SinkPiratesAtKhaelRoa.win_condition.l4.character = "Robert Christopher Silehard";
That won't work unless the real Silehard and pirates are dead, so if any of them surrenders, he still dies and you get his clone instead.
 
;)

This applies equally to quest characters. For example, the code to detect the end of the penultimate battle at Cozumel, against Silehard and his pirate pals, is:
Code:
           Pchar.quest.Story_SinkPiratesAtKhaelRoa.win_condition.l1 = "NPC_Death";
           Pchar.quest.Story_SinkPiratesAtKhaelRoa.win_condition.l1.character = "Isenbrandt Jurcksen";
           Pchar.quest.Story_SinkPiratesAtKhaelRoa.win_condition.l2 = "NPC_Death";
           Pchar.quest.Story_SinkPiratesAtKhaelRoa.win_condition.l2.character = "Brian The Slayer";
           Pchar.quest.Story_SinkPiratesAtKhaelRoa.win_condition.l3 = "NPC_Death";
           Pchar.quest.Story_SinkPiratesAtKhaelRoa.win_condition.l3.character = "Thomas Norton";
           Pchar.quest.Story_SinkPiratesAtKhaelRoa.win_condition.l4 = "NPC_Death";
           Pchar.quest.Story_SinkPiratesAtKhaelRoa.win_condition.l4.character = "Robert Christopher Silehard";
That won't work unless the real Silehard and pirates are dead, so if any of them surrenders, he still dies and you get his clone instead.
Ah right forgot about that.
So is this fixed now?
 
No. I ran into trouble trying to make "RemovePassenger" and any associated functions look for clones, and since then I've been trying to compile the next update archive without including anything that might upset game stability. Once that's out of the way, and barring other projects requiring my attention, I may try again.
 
SO quick question, why would a copy need to be made if it isn't a generic character? can't you just use the normal character?
At the end of a boarding action, the captain MUST die or the process does not continue.
So the captured captain has to be a copy.
 
I've just thought of a stupidly simple way that should fix this.

"Cabinfight_dialog.c" has some specific stuff for Silehard, and while this file is running, you're still talking to the real Silehard. He isn't cloned until the dialog closes with you taking him prisoner. So, at case "take_as_prisoner", all I should need to do is this:
Code:
           if (NPChar.id == "Robert Christopher Silehard") NPChar.prisoned = true;
That sets the "prisoned" attribute onto real "Robert Christopher Silehard". So, when you talk to the replacement governor of Port Royale, who at this point is still using "Robert Christopher Silehard_dialog.c", this bit...
Code:
               if(CheckAttribute(Characters[GetCharacterIndex("Robert Christopher Silehard")], "prisoned"))
               {
                   Link.l1 = DLG_TEXT[359];
                   Link.l1.go = "Silehard_prison";
               }
... should activate.

And in "Prisoned_dialog.c"...
Code:
if(GetMySimpleName(NPChar) == "Robert Christopher Silehard") DeleteAttribute(CharacterFromID("Robert Christopher Silehard"),"prisoned");
... should prevent you from claiming Silehard is your prisoner if you've thrown him to the sharks. Copies of that are needed at cases "kill_prisoner", "Exit_hanged" and "Exit_sharks" so that the attribute is cleared regardless of how you kill him.
 
This brings me neatly full circle to my first ever post a few years back


Fixed - Animist passenger on-board after completing quest

Gosh was it only three years ago.

However hopefully getting the governor's dialogue to work with Silehard a prisoner should put paid to any later jumping ship by Silehard but perhaps the attribute should be wiped at that point (jumping ship) too, just in case the player doesn't go straight to the new English governor. I haven't checked what the game does for escapees - hopefully it wipes the character anyway so the attribute would go too but worth a check?

It did raise in my thoughts about your honourable treatment for surrendered Captains revamp and if those who surrender honourably should ever jump ship anyway after presumably giving their parole - perhaps only if they are neutral or evil - if one equates "honour" with "good" - although I'm not sure that is a correct assumption, certainly in Silehard's case he certainly would jump if he got the opportunity.
 
However hopefully getting the governor's dialogue to work with Silehard a prisoner should put paid to any later jumping ship by Silehard but perhaps the attribute should be wiped at that point (jumping ship) too, just in case the player doesn't go straight to the new English governor. I haven't checked what the game does for escapees - hopefully it wipes the character anyway so the attribute would go too but worth a check?
Yes, I need to clear the attribute in several cases - if you execute him, ransom him, or he jumps overboard. Note that while the system may very well wipe all attributes from a character who escapes or is killed, that doesn't help here - it will wipe the clone, not Silehard himself.

I've always wondered about that jumping ship, though. It makes sense if you're in port. It doesn't make sense if you're out at sea. Good luck swimming to shore and avoiding the sharks!

It did raise in my thoughts about your honourable treatment for surrendered Captains revamp and if those who surrender honourably should ever jump ship anyway after presumably giving their parole - perhaps only if they are neutral or evil - if one equates "honour" with "good" - although I'm not sure that is a correct assumption, certainly in Silehard's case he certainly would jump if he got the opportunity.
Silehard isn't honourable or good. ;)

Hornblower, on the other hand, is. Several times he tells the prison commandant that it's his duty to escape. Only when hs has been granted a special privilege - walking with the "Duchess" on the beach, or taking a boat to rescue sailors from a wrecked ship - does he promise not to use that privilege as a means to escape. (And even after he's promised not to escape while walking on the beach, he reminds the commandant that he still intends to escape at some other time.) So it's perfectly reasonable for an honourable prisoner to jump ship.
 
It did raise in my thoughts about your honourable treatment for surrendered Captains revamp and if those who surrender honourably should ever jump ship anyway after presumably giving their parole - perhaps only if they are neutral or evil - if one equates "honour" with "good" - although I'm not sure that is a correct assumption
That's a fair point, actually! And that could also be an additional incentive for treating your prisoners properly.
Maybe if you treat a "good" captain and his crew with honour (AND are "good" yourself?), he won't try to escape.
But if he's bad, you are bad or you treated him and/or his crew badly, he will try to escape.

Yes, I need to clear the attribute in several cases - if you execute him, ransom him, or he jumps overboard. Note that while the system may very well wipe all attributes from a character who escapes or is killed, that doesn't help here - it will wipe the clone, not Silehard himself.
See if you can find when and where 'ClearCharacter' is called. That is an EVIL function of mine that completely erases all traces of an existing character.

I've always wondered about that jumping ship, though. It makes sense if you're in port. It doesn't make sense if you're out at sea. Good luck swimming to shore and avoiding the sharks!
Yep; very true! :rofl

I suppose that feature was modded in mainly to add some risk to the prisoner functionality.
Would be fun though if that risk weren't 100% random, but the player would have some way of controlling it.
Would be especially fun if it would only occur close(ish) to land; but that would probably be quite tricky to implement and likely not worthwhile.
 
That's a fair point, actually! And that could also be an additional incentive for treating your prisoners properly.
Maybe if you treat a "good" captain and his crew with honour (AND are "good" yourself?), he won't try to escape.
But if he's bad, you are bad or you treated him and/or his crew badly, he will try to escape.
Hornblower is good, the commandant is good, but that still doesn't stop Hornblower from declaring his intent to escape.

See if you can find when and where 'ClearCharacter' is called. That is an EVIL function of mine that completely erases all traces of an existing character.
It appears to be called when a new fantom character is to be created. That character may use a slot previously filled by another, so you want to make sure the new character doesn't inherit any odd attribute from real Silehard at several places in "Prisoned_dialog.c" and once in "DailyCrewUpdate.c", right where you kill or release him or he jumps overboard.
 
Hornblower is good, the commandant is good, but that still doesn't stop Hornblower from declaring his intent to escape.
True. I was thinking about a mutual promise that he won't escape IF you hold up your end of the bargain too.
What bargain? Dunno... That you'll release/ransom him in port within the next month or so?

This gets complicated very quick, doesn't it? Probably better to forget it then... :facepalm
 
Besides, discussion about what to do with prisoners in general would probably go better into this thread:
Ideas for the future! ;)

As for quest characters such as Silehard, I noticed another thing. Anyone marked with the "questchar" attribute can't be hired or ransomed for the full amount even in a port of his own nation. That's due to this line in "Prisoned_dialog.c":
Code:
if(CheckAttribute(NPChar,"questchar") && NPChar.questchar==true) { bAllowHireJoin = false; bAllowRelease = false; }
You can still ransom them for the reduced amount as if you weren't in a friendly port, probably just as well as otherwise you'd either have to kill them (and take the reputation loss) or be stuck with them until they finally jump into the sea. In "Ardent", I removed the "questchar" attribute from a few characters just before you can take them prisoner precisely so that you can either hire or ransom them. By doing this before the sea battle starts, the clones also don't have "questchar" any more and can be ransomed or hired as well.

What that means for Silehard is that you certainly won't be hiring him and probably won't be ransoming him for the minimal amount. Especially not if this works and you can get 400,000 for him, which is what's supposed to happen if you bring him in alive and hand him over to the new governor.
 
As for quest characters such as Silehard, I noticed another thing. Anyone marked with the "questchar" attribute can't be hired or ransomed for the full amount even in a port of his own nation.
That doesn't make much sense to me. Avoiding them to be hired makes sense, I suppose. But why shouldn't you be able to ransom them? Especially when one method does work, but the other doesn't?

Also: Does 'ClearCharacter' ever get called on prisoners that jumped ship?
 
Back
Top