Author Topic: Pausing In-game Music?  (Read 4587 times)

Offline Sirbomber

  • Hero Member
  • *****
  • Posts: 3237
Pausing In-game Music?
« on: February 18, 2009, 08:33:02 PM »
Is there a way to pause the in-game music temporarily, and then reactivate it after a few seconds?
"As usual, colonist opinion is split between those who think the plague is a good idea, and those who are dying from it." - Outpost Evening Star

Outpost 2 Coding 101 Tutorials

Offline Hidiot

  • Hero Member
  • *****
  • Posts: 1018
Pausing In-game Music?
« Reply #1 on: February 19, 2009, 05:43:51 AM »
Other than turning it off manually from the game options, none I know of. And it works more like a "Stop".
"Nothing from nowhere, I'm no one at all"

Offline Sirbomber

  • Hero Member
  • *****
  • Posts: 3237
Pausing In-game Music?
« Reply #2 on: February 19, 2009, 09:47:13 AM »
Another question, not related to the topic but related to my current project.
I want to know if there is a way to check for a specific unit's death without doing something like:
Code: [Select]
void AIProc()
{
    if (someUnit.IsLive() == 0)
    {
        ...
    }
}
Is there some trigger in Functions.h I've overlooked?

The original question about pausing music still needs to be answered, too.

Also, is there a way to add a delay between doing things without using time triggers?
While I can do it with time triggers, something like this would be much easier:
Code: [Select]
SCRIPT_API void PlayerDies()
{
    TethysGame::AddMessage(-1, -1, "Three...", -1, sndBeep2);
    (wait a few ticks)
    TethysGame::AddMessage(-1, -1, "Two...", -1, sndBeep2);
    (wait a few ticks)
    TethysGame::AddMessage(-1, -1, "One...", -1, sndBeep2);
    (wait a few ticks)
    TethysGame::AddMessage(-1, -1, "Zero!", -1, sndBeep5);
}
« Last Edit: February 19, 2009, 11:30:58 AM by Sirbomber »
"As usual, colonist opinion is split between those who think the plague is a good idea, and those who are dying from it." - Outpost Evening Star

Outpost 2 Coding 101 Tutorials

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4954
Pausing In-game Music?
« Reply #3 on: February 20, 2009, 12:57:31 AM »
Check MusicManager.h from the ForcedExports project.

Code: [Select]
class MusicManager
{
...
  void Pause();      // 0x450BB0
  void Unpause();      // 0x450C20
...
};


// Globals
extern MusicManager musicManager; // 0x565390

Probably the simplest way to use this information in your own projects without importing things from elsewhere would be something like this:

Code: [Select]
// Untested code
void __declspec(naked) Pause()
{
  __asm
  {
    MOV ECX, 0x565390  // Address of MusicManager object
    MOV EAX, 0x450BB0  // Address of Pause function
    JMP EAX
  }
}


That "(wait a few ticks)" idea is *beep* doomed to fail. That trigger callback must return before the game can continue processing and actually advance the game time. If you wanted to dump all the code in one function, then consider using AIProc. The other obvious choice is to have a sequence of TimeTrigger callbacks, maybe reuse the TimeTrigger. Or, you might also keep a global counter (in the struct saved by GetSaveRegions), and print the subsequent message each time it's called. (I would normally suggest a static local variable for this, but that's a bad idea with Outpost 2 level DLL programming).

Code: [Select]
Export void Callback()
{
  scriptGlobal.counter++;
  switch (scriptGlobal.counter)
  {
    case 1:
      // etc.
      break;
    case 2:
      // etc.
      break;
  }
}

Make sure the time trigger is repeatable, so it will keep calling the callback function.


I'm not seeing any exported way to detect a unit's death, which is actually quite annoying. I found that to be one of the serious limitations of the API. If you don't update cached unit references every so often, it's possible they can get stale, and even eventually end up point to a new unit that got placed in the old unit record slot after the old one dies off and a new one is created.

I believe there was an internally trigger, that could potentially be hooked, but I don't have the details, and I suspect you're not looking for something that complicated at the moment.


Edit: Added some comments to the assembly code.
« Last Edit: February 22, 2009, 02:32:26 AM by Hooman »

Offline Sirbomber

  • Hero Member
  • *****
  • Posts: 3237
Pausing In-game Music?
« Reply #4 on: February 20, 2009, 09:26:08 AM »
I'll need some more info about the music thing; I get the feeling just copying/pasting that code into my project won't work too well (how do I distinguish between pausing/unpausing, and how do I tell OP2 when to do whichever function?).

The waiting thing isn't such a big deal I guess.  I can just use lots of time triggers.

As for detecting unit deaths, I'll just have to figure something out.
"As usual, colonist opinion is split between those who think the plague is a good idea, and those who are dying from it." - Outpost Evening Star

Outpost 2 Coding 101 Tutorials

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4954
Pausing In-game Music?
« Reply #5 on: February 21, 2009, 02:28:53 PM »
Only only gave an example of calling one function. If you look at the addresses I posted after the Pause/Unpause, I'm sure you can copy/paste and get the other function by extension.
 

Offline Sirbomber

  • Hero Member
  • *****
  • Posts: 3237
Pausing In-game Music?
« Reply #6 on: February 21, 2009, 09:26:29 PM »
Oh, Hooman, you think too highly of me...
We'll see how it goes, but no promises.

Edit: Using your code gets:
Quote
'Pause' : local function definitions are illegal

Edit 2: Figured out a solution to the unit problem... I think.
« Last Edit: February 23, 2009, 02:51:12 PM by Sirbomber »
"As usual, colonist opinion is split between those who think the plague is a good idea, and those who are dying from it." - Outpost Evening Star

Outpost 2 Coding 101 Tutorials

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4954
Pausing In-game Music?
« Reply #7 on: February 25, 2009, 03:35:58 PM »
Code: [Select]
int f()
{
     int g()
     {
     }
}

Quote
error C2601: 'g' : local function definitions are illegal


Place it outside of other functions. You can't nest functions in C++.
« Last Edit: February 25, 2009, 03:36:26 PM by Hooman »

Offline Sirbomber

  • Hero Member
  • *****
  • Posts: 3237
Pausing In-game Music?
« Reply #8 on: February 25, 2009, 07:08:53 PM »
Excellent!  It works!
Well, sorta...
There is a delay between when the music should stop/resume and when it actually does.  For example:

Code: [Select]
// Detects unit death
SCRIPT_API void CargoTruckDead()
{

    Pause();

    (lots code to determine whether to pass to the next player, kill the current player, or restart the turn)

    GD.userGP.DoDeath();
}

However, the music doesn't pause until a mark or two after the Guard Post is killed.  Why?

In another example, the music doesn't appear to pause; however as removing the unpause code makes it work I'm guessing that the game pauses the music and then immediately unpauses it.  Am I doing something wrong, or is this what happens when we mess with OP2 too much?

Edit: Also, it sometimes randomly doesn't feel like unpausing.  Other times, it works fine (other than that delay I was talking about).
« Last Edit: February 25, 2009, 07:10:51 PM by Sirbomber »
"As usual, colonist opinion is split between those who think the plague is a good idea, and those who are dying from it." - Outpost Evening Star

Outpost 2 Coding 101 Tutorials

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4954
Pausing In-game Music?
« Reply #9 on: February 25, 2009, 11:13:26 PM »
Probably due to buffering. It sends data to the sound system in somewhat large chunks. Are you serious about the 1 or 2 marks though? I find marks last quite a while on my computer, so that's an unusually long time.


If you want more control, I have code somewhere for hooking the music playback code. I had implemented a .aud decoder, and was playing back external music, which keeping it still fully under the game controls. Although, I think that's a bit overkill, and probably much too difficult of a solution.


... or, duh, it could be because the trigger you're using to call that function with the Pause/Unpause has a long delay in it. Like how when you kill an enemy, and there is a brief pause before victory. Or when you self destruct all your units, and there is a brief pause before defeat. That would be trigger latency time. Maybe try controlling it from AIProc?
 

Offline Sirbomber

  • Hero Member
  • *****
  • Posts: 3237
Pausing In-game Music?
« Reply #10 on: February 26, 2009, 07:18:44 AM »
Putting it in AIProc makes OP2 hang forever, unless you meant put the call to the pause/unpause functions in there (which still had a noticeable delay).
"As usual, colonist opinion is split between those who think the plague is a good idea, and those who are dying from it." - Outpost Evening Star

Outpost 2 Coding 101 Tutorials

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4954
Pausing In-game Music?
« Reply #11 on: February 27, 2009, 12:01:51 AM »
I just tested it, and there was no delay. It must be something to do with how your calling it. Perhaps post some to show how it's being called, and give a bit of context.
 

Offline Sirbomber

  • Hero Member
  • *****
  • Posts: 3237
Pausing In-game Music?
« Reply #12 on: February 27, 2009, 06:26:15 AM »
I've tried it two ways so far:

1) Calling it from a trigger, like so:
Code: [Select]
// If the current player doesn't set off the bomb, go to the next player.
SCRIPT_API void NextPlayer()
{

    GD.curPlayer++;
    if (GD.curPlayer == GD.plAI)
    {
        GD.curPlayer = 0;
    }

    GD.stopGame = 0;
    Unpause();
    StartNewTurn();
}   // end NextPlayer

2) Setting a boolean value to true:
Code: [Select]
// Find out the current phase.
SCRIPT_API void DoPhaseCheck()
{
    GD.pauseMusic = 0;       **I unpause the music here, but I pause it elsewhere.
    if (GD.curPhase == 0)
    {
        DoPhase1();
    }   // end phase 1 check
    else if (GD.curPhase == 1)
    {
        DoPhase2();
    }   // end phase 2 check
    else
    {
        DoPhase3();
    }   // end phase 3 check
    GD.stopGame = 0;
    StartNewTurn();
}   // end phase check

Then, in AIProc:
Code: [Select]
if (GD.pauseMusic == 1)
    {
         __asm
            {
                MOV ECX, 0x565390  // Address of MusicManager object
                MOV EAX, 0x450BB0  // Address of Pause function
                JMP EAX
            }
    }
    else
    {
         __asm
            {
                MOV ECX, 0x565390  // Address of MusicManager object
                MOV EAX, 0x450C20  // Address of Unpause function
                JMP EAX
            }
    }

How did you do it?

Edit: I put it up on the SVN, so look there if you need more context.
« Last Edit: February 27, 2009, 11:58:18 AM by Sirbomber »
"As usual, colonist opinion is split between those who think the plague is a good idea, and those who are dying from it." - Outpost Evening Star

Outpost 2 Coding 101 Tutorials

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4954
Pausing In-game Music?
« Reply #13 on: February 27, 2009, 11:10:01 PM »
I only tested the pausing. There was no delay at all.

Code: [Select]
// Holder for global script variables (for Saved game files)
struct ScriptGlobal
{
Unit scout;
};
ScriptGlobal scriptGlobal;


// /*Un*/tested code :p
void __declspec(naked) Pause()
{
 __asm
 {
   MOV ECX, 0x565390  // Address of MusicManager object
   MOV EAX, 0x450BB0  // Address of Pause function
   JMP EAX
 }
}


Export int InitProc()
{
TethysGame::CreateUnit(scriptGlobal.scout, mapScout, LOCATION(35, 5), Player0, mapNone, 0);

return true; // Level loaded successfully
}

Export void AIProc()
{
if ((scriptGlobal.scout.unitID != 0) && (!scriptGlobal.scout.IsLive()))
{
  TethysGame::AddMessage(-1, -1, "Scout destroyed!", -1, 0);
  Pause();
}
}


I'll take a peek at the code in the SVN. I'm particularly interested in what triggers you used and how they were setup.
 

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4954
Pausing In-game Music?
« Reply #14 on: February 28, 2009, 03:16:56 AM »
Ok, there's some good news, and some bad news.

The good news is that I got it working. The bad news is that you won't recognize the code you checked into the SVN anymore.  :whistle:


Btw, there are two potential problems that I forsee, which I did not attempt to solve. One is that if you quit the level while the music is paused, it will stay paused, even when you start a new level. This also means that saved games could be an issue, but I suppose since it's supposed to be a multiplayer game, this isn't a very relevant point. The other is that Pause/Unpause are counting functions, so if you call Unpause() twice, you must call Pause() twice for it to actually pause. That means you have to pay attention to the calls, and make sure there is no code path that accidentally calls pause/unpause twice. I put in a cheap hack to not call Unpause at the start of the first round. You'll have to verify the locations of the calls, as I didn't spend too much time figuring out your round beginning/end logic.
 

Offline Sirbomber

  • Hero Member
  • *****
  • Posts: 3237
Pausing In-game Music?
« Reply #15 on: February 28, 2009, 07:01:29 AM »
A few problems:
1) You spelled my name wrong in your comments, fool. - FIXED
2) You got rid of my ownage nightmare-inducing comments. - FIXED
3) The music is supposed to pause when any of the vehicles are destroyed (to add to the suspense); it doesn't pause until the bomb countdown starts.  I'll see if I can handle this on my own. - FIXED (Not to be rude, but did it ever occur to you that I paused/unpaused the music in certain places for a reason?)
4) About the music pausing/unpausing counting thing, I think we can add a "TimesPaused" counter of some sort, like this: - FIXED

Code: [Select]
// Functions for pausing and unpausing the in-game music.
void __declspec(naked) PauseMusic()
{
__asm
{
  MOV ECX, 0x565390  // Address of MusicManager object
  MOV EAX, 0x450BB0  // Address of Pause function
  JMP EAX
}
pauseCount++;
}


void __declspec(naked) UnpauseMusic()
{
__asm
{
  MOV ECX, 0x565390  // Address of MusicManager object
  MOV EAX, 0x450C20  // Address of Unpause function
  JMP EAX
}
pauseCount--;
}
// End music-controlling functions.

And then, up above:
Code: [Select]
BOOL APIENTRY DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
if (fdwReason == DLL_PROCESS_ATTACH)
{
  DisableThreadLibraryCalls(hinstDLL);
}
    else if(fdwReason == DLL_PROCESS_DETACH)
    {
        while (pauseCount > 0 ) // If the music is paused at the end of the game, unpause it.
        {
            __asm
            {
            MOV ECX, 0x565390  // Address of MusicManager object
            MOV EAX, 0x450C20  // Address of Unpause function
            JMP EAX
            }
            pauseCount--;
        }
    }
    return TRUE;
}

Of course, since I suck at stuff like this it probably won't work and I'll need you to make some correction that should have been blindingly obvious in the first place.

Edit: Yep; didn't work.

Edit 2: Fixed!  I realized I should have done > -1, rather than > 0.

Guess what time it is everyone! http://forum.outpostuniverse.net/index.php?showtopic=4463

Next up: a racing game!
« Last Edit: February 28, 2009, 07:32:33 AM by Sirbomber »
"As usual, colonist opinion is split between those who think the plague is a good idea, and those who are dying from it." - Outpost Evening Star

Outpost 2 Coding 101 Tutorials

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4954
Pausing In-game Music?
« Reply #16 on: February 28, 2009, 02:48:33 PM »
Like I said, I didn't bother to read into your round starting/ending logic too much. There was a lot of code, and it looked somewhat twisted. I just figured the point where everything is created, and where everything dies was a safe point for demonstration purposes. I wasn't trying to finish your level for you.


Hmm, you seem to have missed one of my SVN log comments. That's not a good way to keep track of the paused count.

Consider how that assembly block works. It jumps (JMP) to the target function. Which means it doesn't push a return address (i.e. CALL). When that jumped to function finishes, it executes a return (RET) instruction. That instruction will look at whatever is on the top of the stack, and jump to it (and remove it from the stack). Now, if that assembly block was placed in it's own little function, with no local variables (like the example function I wrote), then the value on the top of the stack will be the return address from that function. But if you place it in a larger function, or try to put code after the jump, guess what. It's not returning there. It's returning to the parent function, and skipping any code beyond that point (or crashing because it read a local variable instead of a return address). I.e., it uses the fact that the "CALL" was the last instruction in a function with an equivalent parameter list, and so it was optimized to a JMP to avoid the extra step while returning.

If you want that to work, then remove those ASM blocks, and just call the function I provided. It's important that it's actually a call.



Attempted simple explaination:
Code: [Select]
f():
1: ... f()'s code
2: CALL g(1, 2)
3: ... rest of f()'s code

g(int a, int b):
4: ... g()'s code
5: PUSH b
6: PUSH a
7: CALL h(a, b)
8: RET 8

h():
9: ... h()'s code
10: RET 8  // Remomve 2 paraters (8 bytes) from the stack while returning

Stack contents after line X:
2: <2> <1> <Address of 3>
7: <2> <1> <Address of 3> <b=2> <a=1> <Address of 8>
10: <2> <1> <Address of 3>
8: [empty]

Notice the similarity in the first 3 values and the last 3 values on the stack after executing line 7. This duplication can be optimized away, by noting that in g, there is no real code after the function call to h, and the parameter list of the two functions are equal. Instead of duplicating the parameter lists, and then removing them twice, we can just feed through the first set.

Code: [Select]
f():
1: ... f()'s code
2: CALL g(1, 2)
3: ... rest of f()'s code

g(int a, int b):
4: ... g()'s code
7: JMP h(a, b)

h():
9: ... h()'s code
10: RET 8  // Remomve 2 paraters (8 bytes) from the stack while returning

Stack contents after line X:
2: <2> <1> <Address of 3>
7: <2> <1> <Address of 3>
10: [empty]


Note that this removed the PUSH instructions for the parameter passing, and the RET instruction after the original CALL. The CALL was then replaced by a JMP.


Now, what you've done, is you made the code after the replaced CALL non-empty. But since at line 10, when you try to return from h(), it see's the return address pointing into f(), and will skip completely over any remaining code in g(). This optimization is not safe in that case.

This is almost like appending h onto the end of g, and to make it work, the call, where the two functions are joined, must be the last thing in g.
 

Offline Sirbomber

  • Hero Member
  • *****
  • Posts: 3237
Pausing In-game Music?
« Reply #17 on: February 28, 2009, 02:58:05 PM »
Err... But it works, Hooman.
At least I THINK it does...
"As usual, colonist opinion is split between those who think the plague is a good idea, and those who are dying from it." - Outpost Evening Star

Outpost 2 Coding 101 Tutorials

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4954
Pausing In-game Music?
« Reply #18 on: February 28, 2009, 04:39:53 PM »
No, I'm afraid it doesn't. It's just a fluke, and that -1 is wrong. Try to think why it is wrong. It's probably because the counts aren't working.

Plus, I noticed you have two copies of the count variable. One is global, and another in the global struct.



Btw, please don't delete and readd files in the SVN when you're doing minor edits. It breaks file revision history, and also takes up a lot more space. Rather than just storing the diff, it will store the whole file. That space can add up over time, and I always feel disk space is limited on that computer. Most annoying though is the broken history, since it doesn't let me just diff with the previous version to see what changed.
 

Offline Sirbomber

  • Hero Member
  • *****
  • Posts: 3237
Pausing In-game Music?
« Reply #19 on: February 28, 2009, 04:53:25 PM »
Oh, is there a way to just update the file?  That would have been nice to know earlier...
"As usual, colonist opinion is split between those who think the plague is a good idea, and those who are dying from it." - Outpost Evening Star

Outpost 2 Coding 101 Tutorials

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4954
Pausing In-game Music?
« Reply #20 on: February 28, 2009, 04:58:39 PM »
Yes! "SVN Commit..."
It will update the stored file, by sending a diff with the previous version to the server, which btw, will also save you some time as less data is sent over the network.

You should also get into the habit of leaving log messages while your at it. If you need some appreciation for log message, then from your project folder choose: TortoiseSVN -> Show Log. When you commit, there is a big empty text box for you to write those messages into.