ebmar 893 Posted November 29, 2018 (edited) Greetings, fellow Jedi! Hope y'all doing fine. 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 November 29, 2018 by ebmar Quote Share this post Link to post Share on other sites
DarthParametric 3,777 Posted November 29, 2018 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()))); } 1 Quote Share this post Link to post Share on other sites
ebmar 893 Posted November 29, 2018 (edited) 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 November 29, 2018 by ebmar Additional query added Quote Share this post Link to post Share on other sites
DarthParametric 3,777 Posted November 29, 2018 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. 1 Quote Share this post Link to post Share on other sites
Guest Qui-Gon Glenn Posted November 29, 2018 ^^^ Yes. Also, comment your code to death. You will thank yourself later. Quote Share this post Link to post Share on other sites
ebmar 893 Posted November 30, 2018 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. 😵 Quote Share this post Link to post Share on other sites
DarthParametric 3,777 Posted November 30, 2018 1 Quote Share this post Link to post Share on other sites
ebmar 893 Posted November 30, 2018 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. Quote Share this post Link to post Share on other sites
DarthParametric 3,777 Posted November 30, 2018 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. 1 Quote Share this post Link to post Share on other sites
ebmar 893 Posted November 30, 2018 (edited) 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! 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. Edited November 30, 2018 by ebmar Quote Share this post Link to post Share on other sites
DarthParametric 3,777 Posted November 30, 2018 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). 1 Quote Share this post Link to post Share on other sites
ebmar 893 Posted November 30, 2018 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. Quote Share this post Link to post Share on other sites
DarthParametric 3,777 Posted November 30, 2018 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. 1 Quote Share this post Link to post Share on other sites
ebmar 893 Posted November 30, 2018 (edited) 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 November 30, 2018 by ebmar Quote Share this post Link to post Share on other sites
DarthParametric 3,777 Posted November 30, 2018 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. 1 Quote Share this post Link to post Share on other sites
Guest Qui-Gon Glenn Posted November 30, 2018 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. Quote Share this post Link to post Share on other sites
ebmar 893 Posted November 30, 2018 (edited) 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. 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 November 30, 2018 by ebmar Quote Share this post Link to post Share on other sites
DarthParametric 3,777 Posted November 30, 2018 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); } 2 Quote Share this post Link to post Share on other sites
ebmar 893 Posted November 30, 2018 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? Quote Share this post Link to post Share on other sites
DarthParametric 3,777 Posted November 30, 2018 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. 1 Quote Share this post Link to post Share on other sites
Hunters Run 57 Posted November 30, 2018 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. 1 Quote Share this post Link to post Share on other sites
ebmar 893 Posted December 1, 2018 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. Quote Share this post Link to post Share on other sites
ebmar 893 Posted December 1, 2018 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: I'm not yet to understand on implementing it 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: Spoiler Legends_Theme_v1_Preview.mp4 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. Quote Share this post Link to post Share on other sites
DarthParametric 3,777 Posted December 1, 2018 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. Quote Share this post Link to post Share on other sites
Guest Qui-Gon Glenn Posted December 2, 2018 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 Quote Share this post Link to post Share on other sites