ebmar

[Query] Custom Music Playing on Cutscene

Recommended Posts

Greetings, fellow Jedi! Hope y'all doing fine. :cheers:

I am here to ask for an assistance about:

  • Is there any chance for custom music to be played at an exact cutscene in game?
  • Edit: Not only cutscene to be exact; it will play along with the particular encounter and will stop soon as the encounter is done.
  • And if it's possible; would ones mind explaining on how doing it?
  • For an example; the tag of the music that would be playing is: mus_theme_legends

Many thanks for considering this; and may the Force be with you!

Edited by ebmar

Share this post


Link to post
Share on other sites

There are script commands to stop current sounds and music and play something else. One approach you could try would be defining your custom track in a UTS (see one of the vanilla modules for an example, under Blueprint, Sound in KTool). Then in your encounter you'd need to fire the script through dialogue or a trigger perhaps. Something like:

void main() {
	object oEbMusic = GetObjectByTag("UTS_TAG_HERE", 0);
	
	MusicBackgroundStop(GetArea(GetFirstPC()));
	AmbientSoundStop(GetArea(GetFirstPC())); // This is optional, depending on the area.
	DelayCommand(0.5, SoundObjectPlay(oEbMusic));
}

Then you'd need a second script at the end to restart the original music/sound. If it's a cutscene, you could fire that script through dialogue again. If it's just a straight combat encounter, perhaps through the OnDeath script of the creature (someone more experienced with such things would need to chime in about that).

void main() {
	object oEbMusic = GetObjectByTag("UTS_TAG_HERE", 0);
	
	SoundObjectStop(oEbMusic);
	DelayCommand(0.5, AmbientSoundPlay(GetArea(GetFirstPC())));  //Only if you stopped it in the first script
	DelayCommand(0.5, MusicBackgroundPlay(GetArea(GetFirstPC())));
}
  • Thanks 1

Share this post


Link to post
Share on other sites
31 minutes ago, DarthParametric said:

There are script commands to stop current sounds and music and play something else. One approach you could try would be defining your custom track in a UTS (see one of the vanilla modules for an example, under Blueprint, Sound in KTool). Then in your encounter you'd need to fire the script through dialogue or a trigger perhaps. Something like:

Spoiler


void main() {
	object oEbMusic = GetObjectByTag("UTS_TAG_HERE", 0);
	
	MusicBackgroundStop(GetArea(GetFirstPC()));
	AmbientSoundStop(GetArea(GetFirstPC())); // This is optional, depending on the area.
	DelayCommand(0.5, SoundObjectPlay(oEbMusic));
}

Then you'd need a second script at the end to restart the original music/sound. If it's a cutscene, you could fire that script through dialogue again. If it's just a straight combat encounter, perhaps through the OnDeath script of the creature (someone more experienced with such things would need to chime in about that).

Spoiler


void main() {
	object oEbMusic = GetObjectByTag("UTS_TAG_HERE", 0);
	
	SoundObjectStop(oEbMusic);
	DelayCommand(0.5, AmbientSoundPlay(GetArea(GetFirstPC())));  //Only if you stopped it in the first script
	DelayCommand(0.5, MusicBackgroundPlay(GetArea(GetFirstPC())));
}

 

Thanks for the instructive feedback! I'll look into digging more information in the game files and gain understanding about how what and how a UTS is. 🤔

Edit: Anyway, about scripting- we were not supposed to input the "object oEbMusic" to the script right? because I have seen some example that they mostly straight onto the "GetObjectByTag" part.

Edited by ebmar
Additional query added

Share this post


Link to post
Share on other sites
18 minutes ago, ebmar said:

we were not supposed to input the "object oEbMusic" to the script right?

That is declaring a variable for substitution in the main body of the script. It is perfectly valid. It's not strictly necessary in such a short script, but it's very handy in longer, more complex scripts, thus a useful habit to get into. It also tends to make things much more user-friendly when reading through a script, as it reduces the amount of nesting.

  • Like 1

Share this post


Link to post
Share on other sites
Guest Qui-Gon Glenn

^^^ Yes.

 

Also, comment your code to death. You will thank yourself later.

Share this post


Link to post
Share on other sites
4 hours ago, Qui-Gon Glenn said:

Also, comment your code to death. You will thank yourself later.

Hahah- well, just got myself opened the nwscript.nss; guess I have to "thank - you" before it's too late lol. 😵

Share this post


Link to post
Share on other sites

Taking things from where they left off-

Quote

I believe that command changes the module's music track to a different one listed in ambientmusic.2da (which is what nTrack is - an integer value for the row number). So your script would be something like:


void main()
{
	
	object oArea = GetArea(GetFirstPC());
	int nTrack = 99; // Insert ambientmusic.2da row number here
	
	MusicBackgroundChangeDay(oArea, nTrack);
}

 

I can't compile the script below with KotOR Tool;

void main()
{
	
	object oArea = GetArea(GetFirstPC());
	int nTrack = 99; 51
	
	MusicBackgroundChangeDay(oArea, nTrack);
}

It produce a "syntax error" message upon compiling. 🤔

Anyways; I already entered a new row in ambientmusic.2da and I have assigned its 2DAMEMORY with the patcher. If it is decided to engage the music with the script, will it be possible for the fired script to link with an assigned 2DAMEMORY? So the row number of ambientmusic.2da inside the fired script is automatically inserted by the patcher.

Share this post


Link to post
Share on other sites

Use the instruction manual, young Padawan.

void main() {
	
	object oArea = GetArea(GetFirstPC());
	int nTrack = #2DAMEMORY1#; // Change to appropriate TSLPatcher memory token value
	
	MusicBackgroundChangeDay(oArea, nTrack);
}

Note that you have to let TSLPatcher compile the script during the mod's installation in order for this to work. Read the instructions regarding this.

  • Like 1

Share this post


Link to post
Share on other sites
12 minutes ago, DarthParametric said:

Use the instruction manual, young Padawan.


void main() {
	
	object oArea = GetArea(GetFirstPC());
	int nTrack = #2DAMEMORY1#; // Change to appropriate TSLPatcher memory token value
	
	MusicBackgroundChangeDay(oArea, nTrack);
}

Hahah- awesome! :bow:

Anyway; the above script can be compiled now! I missed a "//" mark which I have no idea that it is significant. 🙃

Edit:

Quote

Note that you have to let TSLPatcher compile the script during the mod's installation in order for this to work. Read the instructions regarding this.

Copy that! Thank you for the insight; very much appreciated. :cheers:

Edited by ebmar

Share this post


Link to post
Share on other sites
7 minutes ago, ebmar said:

I missed a "//" mark which I have no idea that it is significant lol

Lol yeah, it's pretty significant. Double slashes indicates "ignore the rest of this line" to the compiler. I would advise you to use Notepad++ along the NSS template in order to help spot small mistakes like this. Comments should be greyed out when properly escaped with double slashes. Other things to watch out for are the number of brackets (you need matching pairs) and missing semi-colons at the end of statements. You can also trip up on stuff like float values not having the required decimal (i.e. zero as a float is 0.0 - 0 by itself is an integer).

 

  • Thanks 1

Share this post


Link to post
Share on other sites
1 hour ago, DarthParametric said:

Double slashes indicates "ignore the rest of this line" to the compiler. I would advise you to use Notepad++ along the NSS template in order to help spot small mistakes like this. Comments should be greyed out when properly escaped with double slashes. Other things to watch out for are the number of brackets (you need matching pairs) and missing semi-colons at the end of statements. You can also trip up on stuff like float values not having the required decimal (i.e. zero as a float is 0.0 - 0 by itself is an integer).

Thank you for the details!

Quote

Change to appropriate TSLPatcher memory token value

I am confused by this part. What command should I exactly enter there? Apologize that I'm not yet understand what and how a StrRef is [as referenced inside the patcher's instructions].

Many thanks for considering this. :cheers:

Share this post


Link to post
Share on other sites

2DAMEMORY1 is a memory token, storing a a row ID number. You can have as many as you want. I assume in this case you will only need the one, but it could be 2DAMEMORY2, 2DAMEMORY3, etc. depending on how many rows you needed to add. So, for example, you could have a scenario where you added say three tracks to the 2DA, and created three separate scripts, one for each track. Each script would use a different memory token, to match the specific track.

  • Thanks 1

Share this post


Link to post
Share on other sites
20 minutes ago, DarthParametric said:

2DAMEMORY1 is a memory token, storing a a row ID number. You can have as many as you want. I assume in this case you will only need the one, but it could be 2DAMEMORY2, 2DAMEMORY3, etc. depending on how many rows you needed to add. So, for example, you could have a scenario where you added say three tracks to the 2DA, and created three separate scripts, one for each track. Each script would use a different memory token, to match the specific track.

So, the soon to be compiled NSS files should be like this?

void main()
{
	
	object oArea = GetArea(GetFirstPC());
	int nTrack = #2DAMEMORY1#; // #2DAMEMORY1#
	
	MusicBackgroundChangeDay(oArea, nTrack);
}

I have test the script in-game; it looks like it fired, but without the intended music [just silence except with dialogues and SFXs]. Also the battle music begin to play as the battle started- which looks like in some way it works.

Edit: Ah sorry, I think I get it- every command after "//" would be ignored by the command. So the script should look like this?

void main()
{
	
	object oArea = GetArea(GetFirstPC());
	int nTrack = #2DAMEMORY1#;	
	MusicBackgroundChangeDay(oArea, nTrack);
}

Edit2: I'll try to switch with another command. Maybe something like "MusicBackgroundPlay" could work.

Edited by ebmar

Share this post


Link to post
Share on other sites

Wrapping the # around something is what tells TSLPatcher to make a substitution in any script you have told it to compile. So it will turn

int nTrack = #2DAMEMORY1#;

into something like

int nTrack = 49;

or whatever your new added row number is.

And yes, anything after // is considered "escaped" - that is, the compiler ignores it. Thus it is used for human-readable comments. As @Qui-Gon Glenn mentioned earlier, it is very useful to provide comments in your scripts. Both for yourself, so when you come back to it at a later date you know what you were doing, and for anyone else that may be reading your source, such as when trying to make a compatibility patch.

  • Light Side Points 1

Share this post


Link to post
Share on other sites
Guest Qui-Gon Glenn
12 hours ago, ebmar said:

Hahah- well, just got myself opened the nwscript.nss; guess I have to "thank - you" before it's too late lol. 😵

DP explained it. I guess I should have commented on how to do commenting.

"//" Indicates that the line will not be compiled. Thus, you can "comment out" broken parts of scripts or for testing purposes eliminate them temporarily. Then, when you figure it out and want that line to be compiled, remove the "//" and you are good to go.

"//* Usually indicates a comment from the scripter. It can also just be "//"

Example - I want Atton to dance until the Exile speaks with him. At that time, I want a fade to black and then fade in with Atton wearing different clothes.

Initially, I might comment out the complicated script, and just make sure the appearance change works.

Also, for the complicated script, I make a comment before the main body, explaining what I hope the script to accomplish. Good reference point.

Share this post


Link to post
Share on other sites
57 minutes ago, Qui-Gon Glenn said:

"//" Indicates that the line will not be compiled. Thus, you can "comment out" broken parts of scripts or for testing purposes eliminate them temporarily. Then, when you figure it out and want that line to be compiled, remove the "//" and you are good to go.

"//* Usually indicates a comment from the scripter. It can also just be "//"

Example - I want Atton to dance until the Exile speaks with him. At that time, I want a fade to black and then fade in with Atton wearing different clothes.

Initially, I might comment out the complicated script, and just make sure the appearance change works.

Also, for the complicated script, I make a comment before the main body, explaining what I hope the script to accomplish. Good reference point.

 

1 hour ago, DarthParametric said:

Wrapping the # around something is what tells TSLPatcher to make a substitution in any script you have told it to compile. So it will turn


int nTrack = #2DAMEMORY1#;

into something like


int nTrack = 49;

or whatever your new added row number is.

And yes, anything after // is considered "escaped" - that is, the compiler ignores it. Thus it is used for human-readable comments. As Qui-Gon Glenn mentioned earlier, it is very useful to provide comments in your scripts. Both for yourself, so when you come back to it at a later date you know what you were doing, and for anyone else that may be reading your source, such as when trying to make a compatibility patch.

Many thanks for the assistance, the insights and the knowledge which has been shared, @DarthParametric and @Qui-Gon Glenn! Very much appreciated. :respect:

I have the script working now! And quoting myself earlier:

Quote

I have test the script in-game; it looks like it fired, but without the intended music [just silence except with dialogues and SFXs].

The music did not triggered because of false rename of the file! I'm not expecting that to be honest. It seems the game only read the music if it renamed as mus_area_xx. My former file renamed as music_theme_legends; hence the game did not read the file [although inside ambientmusic.2da its entry tagged the same]. And then I revert back the command by using

MusicBackgroundChangeDay

instead of

MusicBackgroundPlay

And so now, I have to trigger back the original area music after the battle music sequence ends lol. #RIP

Edited by ebmar

Share this post


Link to post
Share on other sites
4 minutes ago, ebmar said:

So now, I have to trigger back the original area music lol. #RIP

You should be able to use basically the same script, just change it to the appropriate row ID taken from the module's GIT instead of using the memory token. 

Alternatively, you could try the following trick. A cursory glance would suggest that the Day and Night tracks are the same, so you could try the following:

void main() {
 
    int nTrack = MusicBackgroundGetNightTrack(GetArea(GetFirstPC()));
    object oArea = GetArea(GetFirstPC());
   
    MusicBackgroundChangeDay(oArea, nTrack);
}
  • Like 2

Share this post


Link to post
Share on other sites
1 hour ago, DarthParametric said:

Alternatively, you could try the following trick. A cursory glance would suggest that the Day and Night tracks are the same, so you could try the following:


void main() {
 
    int nTrack = MusicBackgroundGetNightTrack(GetArea(GetFirstPC()));
    object oArea = GetArea(GetFirstPC());
   
    MusicBackgroundChangeDay(oArea, nTrack);
}

Thanks! I take the suggestion and it works! Also, I'm using an additional delay like the following

void main()
{
    int nTrack = MusicBackgroundGetNightTrack(GetArea(GetFirstPC())); // Revert back to original area music
    int nDelay = 600; // Delay for the original music to start
    object oArea = GetArea(GetFirstPC());
   
    MusicBackgroundSetDelay(oArea, nDelay);
    MusicBackgroundChangeDay(oArea, nTrack);
}

with expectation- the custom music had its playing time extended [straight guessing lol]. I'm firing the above script using the particular NPC's .dlg [last node] before the battle sequence started; simply because I don't have any idea where and when to fire the script. But the execution seems to be lacking, because it is not effective. The original music does playing again, but it played along with the last node of the dialogue.

  • Is there any chance for the original area music to play after the battle ended?

Share this post


Link to post
Share on other sites

You might want to stop the music first, then switch it, then start it again. You should be able to use those relevant commands I posted at the start of the thread.

To play it after combat, assuming combat ends when the target dies, then like I also said earlier, attaching the script to the creature's OnDeath is probably the way to go.

  • Thanks 1

Share this post


Link to post
Share on other sites

Just one correction.

Quote

"//* Usually indicates a comment from the scripter. It can also just be "//"

I believe you meant  /* */.  Apologies if I am wrong. Unlike // this is a multi-line comment.

For example:

//This is a single line comment
//This is a multi-line comment that 
is split into a another line.<--this is run as part of the script.
/* This is a multi<--not run as part of script.
line comment */<--not run as part of script.

 

  • Thanks 1

Share this post


Link to post
Share on other sites
6 hours ago, Hunters Run said:

//This is a single line comment
//This is a multi-line comment that 
is split into a another line.<--this is run as part of the script.
/* This is a multi<--not run as part of script.
line comment */<--not run as part of script.

 

Thank you for the heads-up! That'd be very helpful for me and others who were just got their feet wet with scripting. :cheers:

Share this post


Link to post
Share on other sites

Update:

I've got the designated script to work:

void main()
{
    int nTrack = MusicBackgroundGetNightTrack(GetArea(GetFirstPC())); // This command revert the custom area music back to original
    object oArea = GetArea(GetFirstPC());
    
    DelayCommand(6.2, MusicBackgroundChangeDay(oArea, nTrack));
}

It seems more troubleshooting is needed, as after the battle sequence ended, the original area music wasn't played directly [there were seconds delayed]. But the intention for the custom music that was assigned to stop at reasonable point of the dialogue has been achieved.

I'm avoiding on using the creature's "OnDeath" simply because:

  1. I'm not yet to understand on implementing it
  2. I'm looking for a compatibility with a mod that doesn't make the particular NPC's deceased after the fight; such as escaped NPC

Most likely that I have misunderstood the function of "OnDeath" there. 🤔

Here's a quick preview of the said attempt:

I'm trying to get the music to play a little bit longer, perhaps by adding more value to 'DelayCommand' to something like '7.0'?

I have tried with '360.0' but as soon as the battle was ended, there were long silence of no music and the custom music plays back again.

Share this post


Link to post
Share on other sites

If you want to stop combat before the enemy dies, you'll probably need to add a check to their OnUserDefine script, which checks for their hit point value and surrenders when it drops below a set amount. You may want to summon someone more skilled in scripting for advice on such things.

Share this post


Link to post
Share on other sites
Guest Qui-Gon Glenn

I stand corrected. That is correct, have used it myself (copying from darth333 really) many times, but memory fails more times than not these days :P

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.