VERC: MP3 playing entity with Steam Last edited 1 year ago2022-09-29 07:55:15 UTC

Introduction - Using mp3s in Steam

For a while now Steam has had the ability to play back mp3 files in-game. It even has support for playing an mp3 when a game (or mod) is fired up. This unique ability can be accessed with a simple console command called, ta da, mp3! Example:
mp3 play media\mysong.mp3
Typing that into the console will cause Steam to play mysong.mp3 from the media folder of the mod once. There is one thing to note. When typing the path to the mp3 file, Steam accepts both \ and / for directory representation. There are still a few more mp3 commands we need to know.
mp3 loop media\mysong.mp3
Can you guess what this does? Rather than playing the mp3 once it loops the mp3 until we stop it. How do we stop it? Like this:
mp3 stop
If an mp3 is playing then that command will stop it. There's just one more thing we need to know about before we make our entity - controlling the volume of the mp3 playback is possible by using:
mp3volume x
Where x has a value ranging from 0 to 1, representing a fractional amount. The default value is 0.8 but this can be changed in the options part when a mod is loaded.

How to use mp3s in your own mod

Okay, now that we know about all the mp3 playing commands, we need to create an entity that will put them to use. I'm not going to go into anything complicated; the only things this entity will do are play an mp3 when triggered and stop it when triggered again. It will also have a flag for looping and a flag to remove on fire.

So let's start out by defining our flags and our class. Open up triggers.cpp and find the declaration of the CTriggerCDAudio. Right above that let's add this:
#define SF_LOOP     1
#define SF_REMOVE_ON_FIRE 2

class CTargetMP3Audio : public CPointEntity
{
public:
    void Spawn( void );

    void Use( CBaseEntity *pActivator, CBaseEntity *pCaller,
        USE_TYPE useType, float value );

    BOOL m_bPlaying;
};

LINK_ENTITY_TO_CLASS( trigger_mp3audio, CTargetMP3Audio );
That's our entire class right there. Note the boolean m_bPlaying, which will be turned on when triggered the first time and off the second. Next we need our spawn function, so let's do that now. Right below our class declaration and entity link add in...
void CTargetMP3Audio :: Spawn( void )
{
    pev->solid = SOLID_NOT;
    pev->movetype = MOVETYPE_NONE;

    m_bPlaying = FALSE; // start out not playing
}
Quite simple. Since this is an invisible point based entity it has no solidity and doesn't move. We also initialize the playing boolean to false since the entity doesn't start out playing. Finally we need the meat of the entire entity, its Use function. This should go right below the Spawn function we previously added.
void CTargetMP3Audio::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
    char command[64];

    if (!pActivator->IsPlayer()) // activator should be a player
        return;

    if (!m_bPlaying) // if we're not playing, start playing!
        m_bPlaying = TRUE;
    else
    {     // if we're already playing, stop the mp3
        m_bPlaying = FALSE;
        CLIENT_COMMAND(pActivator->edict(), "mp3 stop\n");
        return;
    }

    // issue the play/loop command
    sprintf(command, "mp3 %s %s\n", FBitSet(pev->spawnflags, SF_LOOP) ? "loop" : "play", STRING(pev->message));

    CLIENT_COMMAND(pActivator->edict(), command);

    // remove if set
    if (FBitSet(pev->spawnflags, SF_REMOVE_ON_FIRE))
        UTIL_Remove(this);
}
I'll analyze this for you. First we want to make sure the activator is a player, sending a client command to a monster wouldn't do much help. Next we check to see if the entity is playing, because if it is we want to stop the mp3 rather than play a new one. If it isn't playing then we go on to set up our command, which is stored in the character array command. The bit check of the spawn flags is there to determine if we should use "loop" or "play" when constructing our command to issue. STRING(pev->message) is the name of the mp3 to play. This is all set up in the .fgd which I'll do next. Finally we issue our command and then determine if the entity should remove itself or not.

That's it aside from the .fgd stuff - simple, isn't it? The .fgd entry is very simple too, as shown below.
@PointClass base(Targetname) = trigger_mp3audio : "Trigger Mp3 Audio"
[
    message(string) : "MP3 Name"
    spawnflags(flags) =
    [
        1: "Loop" : 0
        2: "Remove on fire" : 0
    ]
]
There's one thing to note for mappers. When entering the path for the mp3 into the "MP3 Name" field, make sure you use forward slashes (/) and not back slashes, just as you would when specifying a particular wav to play using an ambient_generic, for example.

All done! That wasn't very difficult eh? There are more possibilities such as volume control, fading the mp3 in or out, and other little touch ups. I won't go into those though as they're beyond the scope of this article.

Important note: this entity will only work in Steam mods. Normal HL does not support mp3 playback. So if it isn't working make sure you're running the game in Steam. :)
This article was originally published on Valve Editing Resource Collective (VERC).
The archived page is available here.
TWHL only publishes archived articles from defunct websites, or with permission. For more information on TWHL's archiving efforts, please visit the TWHL Archiving Project page.

Comments

You must log in to post a comment. You can login or register a new account.