• 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 Peaceful sailing music plays in battle after spawning from worldmap

Grey Roger

Sea Dog
Staff member
Administrator
Storm Modder
You're sailing around on worldmap, then go to 3D sailing straight into a battle - perhaps someone attacked you and you couldn't refuse, perhaps you chased an enemy ship, or perhaps you joined in a battle in progress. When you go to 3D mode, although you're in combat, it's one of the peaceful sailing music tracks which plays.

Here's why. In "PROGRAM\sound\sound.c", function 'SetSchemeForSea()':
Code:
           if (sti(pchar.Ship.POS.Mode) == SHIP_WAR) // PB: !bMapEnter doesn't have the same effect
               SetMusic("music_sea_battle");
           else
           {
               if(Whr_IsNight())       SetMusicAlarm("music_night_sailing");
               else
               {
                   if(Whr_IsFog())       SetMusicAlarm("music_fog_sailing");
                       else               SetMusicAlarm("music_day_sailing");
               }
           }
I put a 'traceandlog' line in there to see how "pchar.Ship.POS.Mode" is set, and also had console do a 'dumpattributes(PChar)'. The 'traceandlog' line produced this:
Code:
pchar.ship.pos.mode = 0, SHIP_WAR = 1, playing peaceful sailing music
And 'dumpattributes' said this:
Code:
  pos =
    x = -0.0952811
    z = 0.185853
    y = 0.837455
    mode = 1
This is a problem I've seen before in a different context. Attribute "PChar.ship.pos" and its sub-attributes aren't set right away after spawning to sea. So the code here is getting the wrong value of "PChar.ship.pos.mode").
 
@Grey Roger: Do I understand correctly that the code is technically correct, but is executed too early?
It might be possible to solve that then by adding a PostEvent delay.
Those are not technically too complicated, so if you would like to learn how to use those, I'll be happy to explain it.
They can be a pretty powerful tool, also for quests. :yes
 
I would be happy to learn about PostEvent, yes. I'm not sure I could use it to fix this issue, but it may have uses at some point in the future, and learning new tricks never does any harm.

And yes, the issue here is that the code is executed too early - it's trying to read the attribute "PChar.ship.pos.mode" before the attribute has been set. I ran into exactly the same problem when I tried to calculate the distance to a fort right after spawning - the function which calculates range uses "PChar.ship.pos.x/y/z", which also haven't been set right away, so it got the range wrong.
 
I would be happy to learn about PostEvent, yes.
Could you upload NK.c? I think there are some good (and hopefully simple) uses in there that may work as example.

I'm not sure I could use it to fix this issue, but it may have uses at some point in the future, and learning new tricks never does any harm.
My thought would be to add a 1(?) second delay before starting the music, by which time the attribute should have been set.
If not, the delay could be increased.
 
Here's "NK.c" from the 7th January installer. I don't have anything newer in the zip archive so this is probably the latest version.
 

Attachments

  • NK.c
    197.7 KB · Views: 171
I don't have anything newer in the zip archive so this is probably the latest version.
No worries; pretty much any version would do.
I don't remember any recent changes to it anyway. I'm probably the only person to edit that file and I haven't done so in ages. ;)

This seems to be a very simple example:
Code:
 if(CheckAttribute(pchar, "KrakenAttack")) PostEvent("EnableKraken", 5*60*1000);
}

#event_handler("EnableKraken", "KrakenEnabled");
void KrakenEnabled()
{
 ref pchar = GetMainCharacter();
 DeleteAttribute(pchar, "KrakenAttack");
 if(KrakenAttackEnabled()) LogIt("Captain, the Kraken is ready for another attack!"); // Just in case you swap ships
}
First PostEvent("EnableKraken", 5*60*1000); is executed.
This triggers a delay on "EnableKraken" in milliseconds, so 5*60*1000 equals a delay of 5 minutes.

Then this line handles what to do when the delay expires:
Code:
#event_handler("EnableKraken", "KrakenEnabled");
This means "if the delay expires on KrakenAttack (called earlier), then execute function KrakenEnabled".

And then, of course, it relays to the KrakenEnabled function:
Code:
void KrakenEnabled()
{
 ref pchar = GetMainCharacter();
 DeleteAttribute(pchar, "KrakenAttack");
 if(KrakenAttackEnabled()) LogIt("Captain, the Kraken is ready for another attack!"); // Just in case you swap ships
}
The purpose is to allow only one Kraken Attack to be called at the same time and to have a "cooldown period" before the next one.
This prevents it from being too much like an "instant kill" cheat.

The "#event_handler" doesn't necessarily need to be above the function declaration that it calls (it could be anywhere else in the code),
but we usually do that so it is clear how it works and all related code is grouped together.

This is the very basic use and should be enough to solve your music issue (I hope!).

-------------------------------

A more advanced example can be found a bit earlier:
Code:
 PostEvent("KrakenAttackFinished", delay, "i", rCharacter);
}

#event_handler("KrakenAttackFinished", "FinishKrakenAttack");
void FinishKrakenAttack()
{
 ref pchar = GetMainCharacter();
 aref rCharacter = GetEventData();
Here the "i" (for integer, I think) and 'rCharacter' are added.
This allows 'GetEventData()' to read the value of 'rCharacter' with which the delay was called.

Because a Kraken Attack can be called on different ships, passing the target character along with the event allows this:
Code:
LogIt("Captain, the Kraken has finished its attack on the " + rCharacter.ship.name + "!");
Otherwise the 'FinishKrakenAttack()' function would have no clue who was the target of the attack that just finished.


I hope this explains things for you. :doff
 
Two different 'PostEvent' commands have two different lists of parameters. One just has the event name and a delay; the other has event name, delay, string "i" and a reference.

'PostEvent("EnableKraken", 5*60*1000)' is easy enough to understand - delay is in milliseconds, so a 5 minute delay is 5 x seconds/minute x milliseconds/second. If I were doing one of those, I'd then need to declare a whole new function somewhere.

'PostEvent("KrakenAttackFinished", delay, "i", rCharacter)' is less easy to understand. "delay" is again the delay in milliseconds. "i" can't be "integer", or if it is, what's it referring to? Not "rCharacter" - that's an aref, not an integer. What's the difference between aref and ref anyway?
 
If I were doing one of those, I'd then need to declare a whole new function somewhere.
Correct, but that is easy enough. Doesn't even require a new game to take effect.

string "i" and a reference.
It is even possible to add more than one variable to pass on. The "i" marks the type.
If you pass two of type "i", then you can use "ii", variable1, variable2) .

"i" can't be "integer", or if it is, what's it referring to?
I honestly can't quite remember; that confused me just now as well. But I do know it works.
If I recall, the "Manuals & History" PDF file in the mod's "Documentation" subfolder contains a list of those.
You could have a look through that and see if there is a description to be found about Events.

What I do remember is that this is weird and confusing. There was also "l" and "s", I think.
"l" might be a number then and "i" a reference of sorts?

What's the difference between aref and ref anyway?
I never had that clear either, but @Levis explained it to me here:
New Horizons Wiki
 
According to that, 'ref' is a reference to an object and 'aref' is a reference to an attribute.

So 'PostEvent("KrakenAttackFinished", delay, "i", rCharacter)' triggers event "KrakenAttackFinished", whose event handler triggers function 'FinishKrakenAttack()', which tries to read aref 'rCharacter' from event data.

The 'PostEvent' is in function 'KrakenAttack' and supplies parameter also called 'rCharacter', which was declared as an aref parameter to 'void KrakenAttack(aref rCharacter, int iSwimQuantity)'.

Function 'KrakenAttack' is called from within "BattleInterface.c":
Code:
KrakenAttack(Characters[targetNum], GetCharacterShipHP(Characters[targetNum])/100 );
And array "Characters" is an array of objects declared in "globals.c". So by the definition in the wiki I'd expect anything referring to its elements to be ref, not aref.

Now you see why I am having trouble figuring out the difference between ref and aref. o_O
 
Now you see why I am having trouble figuring out the difference between ref and aref. o_O
I know the feeling, because it still confuses me as well.
As far as I can tell, they're used pretty much interchangeably in the code. :facepalm
The way I figure it: As long as it works, I don't care so much about the specifics.

I have a vague memory that 'aref' is quite common in combination with 'GetEventData()'.
You could try replacing it with 'ref' and see what happens.

Sometimes the game engine does an automatic type conversion if you use the "wrong" type for whatever reason.
Just like this actually works fine:
Code:
int number = 1;
string text = "Number is " + number;
LogIt(text);
In most programming languages that I am familiar with, you would need to use an 'int2string' function on 'number' if it is to be used in a string context.
This makes the PotC code relatively forgiving and somewhat easier to work with for beginning coders.

On the other hand, when it SHOULD do a data conversion but doesn't, it can become massively confusing.
It took me forever to figure out a bug caused by mixing 'int' and 'float' in the same formula. :modding
 
I have no intention of messing with general game code that currently works!

Automatic conversion of numbers to strings is something I've seen before, especially if it's just going to something like a "print" or "trace". Going the other way usually requires a function, though.

It's worth noting that nobody, myself included, ever noticed this before. The only reason I noticed it now is that I've been paying more attention to music as a result of getting my end theme to work. Also, when the game picks a piece of music to play during peaceful sailing, it cycles through all the options which were set in "music_standard.c", but I've set another one in "StartStoryline.c" so there's another track which can play if you're sailing peacefully in daytime while playing "Ardent". It just so happened that the game had got to the end of the cycle, which means my new track was the one which played. I'd been listening to the music to make sure it really was picking a different track each time I went to sailing mode, and it wasn't supposed to pick that one during a battle!
 
I have no intention of messing with general game code that currently works!
I just meant as an experiment to see if it makes a difference.
If it does make a difference, you'll probably notice soon enough because the Kraken functionality would no longer work properly.

It's worth noting that nobody, myself included, ever noticed this before. The only reason I noticed it now is that I've been paying more attention to music as a result of getting my end theme to work. Also, when the game picks a piece of music to play during peaceful sailing, it cycles through all the options which were set in "music_standard.c", but I've set another one in "StartStoryline.c" so there's another track which can play if you're sailing peacefully in daytime while playing "Ardent". It just so happened that the game had got to the end of the cycle, which means my new track was the one which played. I'd been listening to the music to make sure it really was picking a different track each time I went to sailing mode, and it wasn't supposed to pick that one during a battle!
Indeed it's not a big bug and nobody bothered reporting it before.
But it is still wrong (I vaguely recall noticing it before), so if a fix can be attempted with only a few lines of code, it might be worth to at least try. :doff
 
Do you reckon this would work?
Code:
void SetSchemeForSea()
{
   PostEvent("SeaSchemeSet", 500);   // GR: delay setting music so that PChar.ship.pos attributes have time to be set
}

#event_handler("SeaSchemeSet", "Delayed_SetSchemeForSea");
void Delayed_SetSchemeForSea()
{
<entire contents of original 'SetSchemeForSea'>
}
The entire original function 'SetSchemeForSea' is renamed 'Delayed_SetSchemeForSea' and called by the event handler. New 'SetSchemeForSea' just sets off the event, delayed by 0.5 second. My guess is that it will either crash horribly, actually work, or not have any effect because 0.5 second isn't enough...
 
@Grey Roger: I think in theory that should do exactly what it is intended to do. :onya

You may want to add some LogIt/Trace statements to check it actually executes as you'd expect.
And indeed the value for the delay may require tweaking; but that is something you'll find out soon enough. :doff
 
Ever get the idea the system hates you? Now that I want it to play the wrong music, it refuses to do so. Still using the version of "sound.c" without the 'PostEvent' fix, I went to worldmap, sailed around looking for something to fight, spawned right on top of it, and got battle music. "compile.log" showed that it had started with normal sailing music and then switched to battle music, and a few 'trace' lines in "sound.c" showed the same - 'SetSchemeForSea()' started playing a "music_day_sailing" theme, then 'Sound_OnSeaAlarm' detected a battle in progress and played a "music_sea_battle" theme. I tried several times reloading from different savegames in port, going to worldmap, then exiting worldmap straight into a fight, and it always did the same. :modding

In another thread, someone had a similar problem when playing "Hoist the Colours" and going into battle with the Interceptor, so I've asked for a savegame which will hopefully play the wrong music, then I can test the fix. Meanwhile, I've started "Hoist the Colours" myself, partly in the hope of getting the same problem at the same point, and partly because it's about time I tried that storyline.
 
Back
Top