Tag Archives: Python

More Mods and Modding

Epic Games Mod Music

In a continuing effort to further perfect my mod music library, I’ve been redoing my conversions of the music from several of my favorite Epic MegaGames soundtracks, specifically Unreal, Unreal Tournament, and Jazz Jackrabbit 2.

My previous efforts were all plagued with improper volume levels as I naively thought that music should just be normalized and that’s it, job done. Since then, I’ve come to understand about loudness levels and limiters that can increase the volume without clipping. To be fair, normalizing is still the most appropriate way to level audio as it doesn’t alter the dynamic range. The only problem is that normalizing alone is impractical as all other music these days is boosted to a loudness of around -10 LUFS, and you don’t want to constantly adjust your speaker volume to account for the variance in volume. It seems mod music is particularly susceptible to being problematic in this regard as well since many tracks were made to just slam up against 0dB and let the player’s Auto-Gain Control sort it out.

Unfortunately, the automated mod conversion process that I mentioned last post has one major flaw in that it just kinda makes all the tracks the same volume. -10 LUFS loudness is fine for most lively music, but for softer tracks (like that of Unreal’s ambient music), you might want the volume up to 10 LUFS quieter. You really have to listen to each track and decide on an appropriate loudness. So, that’s what I did. You can download all three game soundtracks and listen to some samples from the Epic Games Tracker Music project page.

The majority of the three soundtracks were converted in the automated way using FFmpeg’s OpenMPT library and built-in audio filters. Some of the tracks were ones that I selectively recorded/remastered and then released here back in 2008, but just with the fixed volume levels. Luckily, I still had all the wave files available to use. And a few tracks I actually remastered recently in the same way that I did for the Deus Ex Soundtrack remaster. So, it’s kind of a hodge-podge of different conversion methods, unfortunately, but I wanted to just make it available for people as-is and hopefully add more remastered tracks later. It’s still very listenable, though, as long as you don’t mind some clicking and pops from bad samples and sudden track ends.

There’s also a bit of an issue with much of the Unreal music included as it only contains the ambient sections of each track. It wasn’t until after I had everything ready for upload that I finally figured out how to get FFmpeg to convert the other sections of the tracks. These sections are often referred to as “subsongs” (at least in contemporary mod players), but the mod formats don’t have a specific mechanism in place to define these sections. Instead, they merely use the Bxx effect command to jump to different patterns in the song. These can be used to loop different sections of the song and be programmatically switched between, e.g. in a video game. The OpenMPT library actually has a process included that can identify these sections, and it’s also available as an option in FFmpeg by utilizing the option -subsong. However, I couldn’t find a single example of how this option is used anywhere online and only later (through shear persistence… or maybe just dumb luck, I forget) discovered that the subsong option has to go before the input file is given (-i). Maybe that makes sense because it’s technically modifying what the input is…? I dunno, it seems pretty stupid to me, especially undocumented.

Regardless, I need to reconvert a lot of the Unreal soundtrack as such. I would have just gone ahead and done it, but many of the sections are rather long and perhaps need to be a separate track. Some people always complain about how I did the Deus Ex soundtrack remaster with every section included in one track, and I still think that was the right choice for Deus Ex, but it’s making me hesitant to repeat that arrangement for Unreal. It just needs further consideration.

And honestly, Unreal is probably the weaker of the three soundtracks. There’s definitely some great tracks in there, but UT and Jazz 2 just have some seriously bangin’ tracks. Alex Brandon really did some amazing mod compositions back then. I actually also realized that the versions of the Jazz 2 soundtrack that I had (from Lori Central) were slightly different from the game versions, so I converted all the Jazz 2 music a second time to see which versions I should include in my compilation. I ended up writing a whole PHP script to extract the mod format files from the j2b files that come with the game, and then found out that OpenMPT will just open the j2b files as is. 😄

Borderlands Modding

I’ve been playing a lot of Borderlands 2 recently. There’s just something about that game that makes it probably the best “looter shooter” AKA first-person action RPG—it’s probably the very satisfying visuals combined with the endlessly-humorous dialogue. Seeing damage numbers fly out of enemies as you shoot, punctuated by the occasional “CRITICAL” hit really prods those dopamine receptors. And all of the characters being just such over-the-top caricatures with the most irreverent dialogue—Scooter’s incestuousness, Moxxi’s innuendos, Brick’s insatiable bloodlust, all the bandits’ ridiculous one-liners, etc.—it always lifts my spirits.

But even for all its good qualities, the game isn’t without its faults. And I’m apparently perpetually drawn to attempting to fix such faults in the form of game modding. However, I specifically remember trying to find some BL2 modding tools several years back and coming up empty-handed. But after a recent playthrough, I looked again and was delighted to see that a modding community has sprung up around the game after a couple of different modding methods were devised.

The first modding method is quite straightforward and intrinsic to the Unreal engine that the game is built on, and thus I am surprised there weren’t more mods made for the game sooner. It relies on the console being enabled and then using two console commands to override game settings. The first being the set command, which changes any variable to some given value. And the second being the exec command, which reads a text file in and executes whatever commands it finds. These allow one to change as many game settings as necessary and then package the changes all in one convenient text file. There’s even a tool made to help format and merge multiple mods into one file called OpenBLCMM.

The other method I only started looking into recently, but it is very powerful if you have enough motivation to figure it all out. It’s of the variety of DLL-injected code wrappers that can load any number of external scripts and is called succinctly-enough PythonSDK. Unfortunately for me, I’ve been neglecting to learn Python for years (mainly because of my illness to be fair). Mostly I just find Python to only be popular because of this one gimmick it has where text indentation and line breaks also control code blocks. It is nice that all Python code looks really clean and uniform (boy, do I hate people that put beginning curlies as the only thing on a whole line in other languages), but it really doesn’t seem so groundbreaking that everyone needs to switch to it as the new hotness. But that’s probably just my uninformed and biased position as a newcomer. 😛 I also thought Half-Life 2 was overrated at first. 🤐

Anyways, I’ve already fixed a number of small nagging issues with BL2 and have a basic framework for a larger change mostly figured out. One thing that’s always annoyed me about Borderlands games is their inclusion of an accuracy attribute on all weapons. So, you can have your crosshair directly over an enemy and the shot just plain misses or hits but isn’t a critical hit. It removes a lot of the skill from the game and replaces it with luck, which feels less satisfying.

I’ve changed the game to always be completely accurate when aiming down the sights, and to also have slightly better accuracy control when hip-firing. It’s totally revitalized the game for me, making me rely much less on sniper rifles (which were always close to 100% accurate when aiming down the sights) and experimenting more with the other types of weapons. I’ve also been tweaking some of the recoil characteristics of the guns to make them more controllable, especially for the SMGs, which I could never quite put my finger on as to why they felt so bad to spray, but have since realized that it’s because they recoil mostly only horizontally—try keeping the reticle on-target when it constantly bounces left and right randomly.

I’d like to release my BL2 mods, of course, but the weapon handling mod will need a lot more testing and tweaking until it’s to a point where I’m satisfied with the feel and game balance. I guess it does make the game easier, provided your aim and recoil control is good, so that’s something I have to look into as well. Check back later to see if it gets completed and released. 😛

Deus Ex Soundtrack Videos

One thing I like to do these days when I’m feeling only-slightly crappy is edit videos. It requires only a modest level of thought and creativity while being mostly an engaging yet repetitive task—make a small timeline tweak, rewatch to see how it looks, repeat. My CPU is over 10 years old now, so it chugs a lot when editing HD video, but it’s bearable I suppose.

One project to come out of this video editing recently has been a series of music videos to accompany my remastered versions of the Deus Ex Soundtrack. I really wanted to expose a wider potential audience to the remasters, and I thought YouTube was a good platform for that, being so open and also having a huge library of music already. I thought about just putting a static image, the cover image, for the video portion (as so many people do for music videos on YouTube), but that seemed too lame and uninteresting for someone that was watching on anything with a screen. But I also didn’t want to have a lot of fast-moving gameplay captures since that would massively increase the file-size (for variable bit-rate video compression at least, which is what YouTube uses) for users only listening. So I decided on a compromise of in-game capture with a stationary camera to keep the motion down.

I ended up creating a little library of shortcuts and console commands to help simplify the process. Luckily, Deus Ex has all its scripts easily editable from inside UnrealEd (the original one still runs if you have all the ActiveX library files installed) unlike Borderlands 2. Here are the edits I made to the “DeusExPlayer” class.

var bool bToggleHud;
var bool bToggleGhost;
var bool bToggleInvisible;

// ----------------------------------------------------------------------
// ToggleHud - Snake Modification
// ----------------------------------------------------------------------

exec function ToggleHud()
{
    if (!bToggleHud)
        ShowHud(false);
    else
        ShowHud(true);
        
    bToggleHud = !bToggleHud;
}

// ----------------------------------------------------------------------
// ToggleGhost - Snake Modification
// ----------------------------------------------------------------------

exec function ToggleGhost()
{
    if (!bToggleGhost) {
        Ghost();
    } else {
        Walk();
        ClientMessage("Walking");
    }
        
    bToggleGhost = !bToggleGhost;
}

// ----------------------------------------------------------------------
// ToggleInvisible - Snake Modification
// ----------------------------------------------------------------------

exec function ToggleInvisible()
{
    bToggleInvisible = !bToggleInvisible;
    Invisible(bToggleInvisible);
}

// ----------------------------------------------------------------------
// SpawnMassPawn - Snake Modification
//
// Spawns a bunch of pawns around the player with an optional number, alliances, and weapons
// ----------------------------------------------------------------------

exec function SpawnMassPawn(Name ClassName, optional int TotalCount, optional Name Allies, optional Name Enemies, optional string WeaponPackage)
{
    local ScriptedPawn spawnee;
    local vector       spawnPos;
    local vector       center;
    local rotator      direction;
    local int          maxTries;
    local int          count;
    local int          numTries;
    local float        maxRange;
    local float        range;
    local float        angle;
    local class        spawnClass;
    local string       holdName;
    local float        rnd;
    local int          i;

    if (!bCheatsEnabled)
        return;

    if (!bAdmin && (Level.Netmode != NM_Standalone))
        return;

    if (instr(ClassName, ".") == -1)
        holdName = "DeusEx." $ ClassName;
    else
        holdName = "" $ ClassName;  // barf

    spawnClass = class(DynamicLoadObject(holdName, class'Class'));
    if (spawnClass == None)
    {
        ClientMessage("Illegal pawn actor name "$GetItemName(String(ClassName)));
        return;
    }

    if (totalCount <= 0) totalCount = 10; if (totalCount > 250)
        totalCount = 250;
    maxTries = totalCount*2;
    count = 0;
    numTries = 0;
    maxRange = sqrt(totalCount/3.1416)*4*SpawnClass.Default.CollisionRadius;

    direction = ViewRotation;
    direction.pitch = 0;
    direction.roll  = 0;
    center = Location + Vector(direction)*(maxRange+SpawnClass.Default.CollisionRadius+CollisionRadius+20);
    while ((count < totalCount) && (numTries < maxTries))
    {
        angle = FRand()*3.14159265359*2;
        range = sqrt(FRand())*maxRange;
        spawnPos.X = sin(angle)*range;
        spawnPos.Y = cos(angle)*range;
        spawnPos.Z = 0;
        spawnee = spawn(SpawnClass,,,center+spawnPos, Rotation);
        if (spawnee != None) {
            if (WeaponPackage != "") {
                //clear the default inventory
                for (i=0; i<8; i++) {
                    spawnee.InitialInventory[i].Inventory = None;
                    spawnee.InitialInventory[i].Count = 0;
                }
                switch (Caps(WeaponPackage)) {
                    case "PISTOL":
                        spawnee.AddInitialInventory(Class'DeusEx.WeaponPistol');
                        spawnee.AddInitialInventory(Class'DeusEx.Ammo10mm', 2);
                        spawnee.AddInitialInventory(Class'DeusEx.WeaponCrowbar');
                        break;
                    case "MELEE":
                        rnd = Rand(3);
                        if (rnd == 0)
                            spawnee.AddInitialInventory(Class'DeusEx.WeaponCrowbar');
                        else if (rnd == 1)
                            spawnee.AddInitialInventory(Class'DeusEx.WeaponCombatKnife');
                        else
                            spawnee.AddInitialInventory(Class'DeusEx.WeaponBaton');
                        break;
                    case "ASSAULT":
                        spawnee.AddInitialInventory(Class'DeusEx.WeaponAssaultGun');
                        spawnee.AddInitialInventory(Class'DeusEx.Ammo762mm', 12);
                        spawnee.AddInitialInventory(Class'DeusEx.WeaponAssaultShotgun');
                        spawnee.AddInitialInventory(Class'DeusEx.AmmoShell', 12);
                        spawnee.AddInitialInventory(Class'DeusEx.WeaponCombatKnife');
                        break;
                    case "FLAME":
                        spawnee.AddInitialInventory(Class'DeusEx.WeaponFlamethrower');
                        spawnee.AddInitialInventory(Class'DeusEx.AmmoNapalm', 8);
                        spawnee.AddInitialInventory(Class'DeusEx.WeaponCombatKnife');
                        break;
                    case "NONLETHAL":
                        spawnee.AddInitialInventory(Class'DeusEx.WeaponProd');
                        spawnee.AddInitialInventory(Class'DeusEx.AmmoBattery', 6);
                        spawnee.AddInitialInventory(Class'DeusEx.WeaponPepperGun');
                        spawnee.AddInitialInventory(Class'DeusEx.AmmoPepper', 2);
                        spawnee.AddInitialInventory(Class'DeusEx.WeaponBaton');
                        break;
                    case "PLASMA":
                        spawnee.AddInitialInventory(Class'DeusEx.WeaponPlasmaRifle');
                        spawnee.AddInitialInventory(Class'DeusEx.AmmoPlasma', 20);
                        spawnee.AddInitialInventory(Class'DeusEx.WeaponNanoSword');
                        break;
                    case "SWORD":
                        spawnee.AddInitialInventory(Class'DeusEx.WeaponNanoSword');
                        break;
                    case "STUN":
                        spawnee.AddInitialInventory(Class'DeusEx.WeaponProd');
                        spawnee.AddInitialInventory(Class'DeusEx.AmmoBattery', 50);
                        break; 
                }
            }
            spawnee.InitializePawn();
            if (Allies != '')
                spawnee.SetAlliance(Allies);
                spawnee.ChangeAlly(Allies, 1, True);
            if (Enemies != '')
                spawnee.ChangeAlly(Enemies, -1, True);
            count++;
        }
        numTries++;
    }

    ClientMessage(count$" actor(s) spawned");

}

The first three functions just allow you to have a single key for each of the commands by toggling them. The last function is a modification of an existing function that lets you spawn a bunch of objects around the player, but I’ve expanded it so you can set alliances and give them weapons. This was handy for spawning two groups of characters and then having them fight so I could record it. 😄

To use all of these functions, you need to make some modifications to your Deus Ex keybinds in the User.ini file. Find the section [Engine.Input] and add the following commands to some keys.

L=legend
K=set deusex.jcdentonmale bcheatsenabled false
J=set deusex.jcdentonmale bcheatsenabled true
I=ToggleInvisible
H=ToggleHud
G=ToggleGhost
Tilde=type

As for what these keybinds all do: J will turn cheats (and the type command) on, K turns cheats back off. The game puts a “cheats enabled” watermark on save game screenshots to make you feel bad about cheating, so it’s helpful to be able to turn it back off. Tilde (~) opens the command line so you can type in commands like the SpawnMassPawn() defined above. H toggles hiding the HUD, useful, of course, for getting clean screenshots or video captures. G toggles “ghost mode”, which is basically just noclip plus fly, so you can position the camera wherever you need to get the best shot. I toggles invisibility, which is useful for making actors not respond to you. You’d think ghost would be enough, but they’ll still target you if not invisible. And L opens the in-game debug / “secret” menu. It’s useful for switching maps without having to type in the open console command. See the screenshots below.

So, utilizing all of these tools was how I was able to record all the b-roll footage for the YouTube videos. I guess it’s nothing special, but maybe it helps give people the nostalgia berries even more, really putting them into the levels, plus some silly fight scenes for the lulz. The response from YouTube comments and views has certainly been positive, at least. For the record, I only did videos for the tracks that I redid in the recent soundtrack update. I’m hoping to do the rest of the videos when I do the rest of the remasters.

What Else I’m Up To

As I spend most of my days feeling unwell, I end up watching a lot of gaming streams. I’m quite thankful that such lengthy forms of entertainment exist these days. There are three streamers that I watch pretty much exclusively. I got into ChocoTaco from my PUBG addiction, and I think he best mirrors what my playstyle would be if I weren’t sick–extremely calculated and with good instincts. Unfortunately, Choco’s sense of humor is a bit lacking, so for a dose of levity, I like to watch a British streamer called Platform32. I also discovered him through PUBG, but he plays a fair amount of other games that I enjoy as well, all laced with copious amounts of witty and raunchy banter. Finally, and this is bit of an odd one, but I really enjoy watching Felicia Day for some reason. She’s not the greatest of gamers, but she does okay, and the arc of her failing repeatedly before finally overcoming challenges is entertaining somehow—Felicia can be quite bombastic. That, and she talks a lot and is generally insightful enough to keep it interesting.

Like I said earlier, I’m doing a fair bit of video and audio editing. I’ve become rather decent at it, frankly. I’m also doing a lot of home improvement since it allows me to not have to think so much. Plumbing, electrical, woodworking, HVAC repair, painting—I’ve been dabbling in it all. Getting people to come out on my schedule and fix things has become quite inconvenient and expensive these days, so DIY almost seems like a necessity as long as it’s not an emergency repair.

And I’m still playing some video games, though not for as long or as wide a variety as I would like. I played a lot of Vampire Survivors and Ooblets last year. And, of course, I have my yearly replay of Slime Rancher and Stardew Valley to occupy me.

Well, this is an epicly-long post, so I better end it already. I do appreciate the comments everyone leaves; sometimes they do encourage me to poke around with a project again.

Posted in Gaming, Media, Modding, Programming | Tagged , , , , , , , , , , | Leave a comment