Overview
How to properly create a mod to replace textures or sounds, without requiring manual installation!
Before you begin
This is a guide for those of you who’ve created texture packages to replace in-game textures. Unfortunately, unlike some games, the Unreal engine does not support simply dropping in replacement materials, so in order to properly replace textures you’ll have to do a bit of ground work.
Thankfully, you’re in luck – I’ve done the hard work for you already and created a generic AssetReplacer mod that will change out most textures and sounds for you automatically. All you have to do is specify what textures/sounds are to be replaced, and what they are to be replaced with.
There are some caveats, though:
- Materials on BSP surfaces cannot be accessed or replaced with script. You can either create a replacement map with the new surfaces, or require the user to install the mod manually.
- Pawn dialog can’t be replaced with this script. You’ll have to create a new Dialog Class and set that manually.
- Some actor classes change out their materials and sounds on the fly, this script won’t be able to handle that. You’ll need more specialized code for these cases.
- Other specific sounds and skins (particularly, weapon and effect sounds) won’t be replaced. Again, specialized code can handle these cases, and I’ll show you how to set some up with an example weapon mod.
This tutorial/code sample assumes that you are already set up in POSTed and have created your replacement textures/sounds and placed them in a package. I also suggest at least reading My First Mod to understand the basics of how P2GameMods work. And finally, having a command prompt set up will help you immensely while testing.
AssetReplacer: The script
I’m going to give you the source code for AssetReplacer, a generic texture/sound replacer. This will cover maybe 75% of most cases. Chances are, you can simply dump your textures in here and they will be replaced for you.
Create a new code package. You can call it whatever you want, but I’d recommend giving it a separate name from your texture/sound package. Let’s say you’ve made some new cop skins and want to put them in the game, and that your texture package is called NewCopSkins.utx. We’ll call our code package CopSkinReplacer. Create a folder in POSTAL2Editor called CopSkinReplacer, and a folder in that called Classes. Then paste the following code into a text editor and save as AssetReplacer.uc under POSTAL2EditorCopSkinReplacerClasses.
// Asset Replacer
//
// This is a sample Workshop mod based off of P2GameMod.
// Feel free to copy this code and use it as a basis for your own game mods.
//
// This is a mod that reads in a list of textures and sounds in the default
// properties, and attempts to replace them in-game wherever possible.
//
// Limitations:
// * BSP surfaces can’t be messed with. If you want to change the texture on
// a wall, rebuild the map and include it with your mod.
// * Dialog won’t be replaced. Script a new dialog class and assign it to
// the desired pawns.
// * Some classes change materials or sounds on the fly; this mod won’t change
// those.
// * Certain specific sounds or skins won’t be replaced, you’ll have to change
// them manually.
///////////////////////////////////////////////////////////////////////////////
class AssetReplacer extends P2GameMod;
struct MaterialReplaceStruct {
var() Material OldMaterial, NewMaterial;
};
struct SoundReplaceStruct {
var() Sound OldSound, NewSound;
};
var() array<MaterialReplaceStruct> MaterialReplace;
var() array<SoundReplaceStruct> SoundReplace;
///////////////////////////////////////////////////////////////////////////////
// ReplaceMaterial
// Attempts to replace passed-in material
///////////////////////////////////////////////////////////////////////////////
function Material ReplaceMaterial(Material ReplaceMe)
{
local int i;
for (i = 0; i < MaterialReplace.Length; i++)
{
if (ReplaceMe == MaterialReplace[i].OldMaterial)
return MaterialReplace[i].NewMaterial;
}
return None;
}
///////////////////////////////////////////////////////////////////////////////
// ReplaceSound
// Attempts to replace passed-in sound
///////////////////////////////////////////////////////////////////////////////
function Sound ReplaceSound(Sound ReplaceMe)
{
local int i;
for (i = 0; i < SoundReplace.Length; i++)
{
if (ReplaceMe == SoundReplace[i].OldSound)
return SoundReplace[i].NewSound;
}
return None;
}
///////////////////////////////////////////////////////////////////////////////
// ParseActor
// Looks to replace any textures, sounds on new actor
///////////////////////////////////////////////////////////////////////////////
function ParseActor(Actor Other)
{
local int i, j;
local Material NewMaterial;
local Sound NewSound;
local Name NewName;
if (Other == None)
return;
// Try Actor skins and sounds
for (i = 0; i < Other.Skins.Length; i++)
{
NewMaterial = ReplaceMaterial(Other.Skins[i]);
if (NewMaterial != None)
Other.Skins[i] = NewMaterial;
}
NewMaterial = ReplaceMaterial(Other.Texture);
if (NewMaterial != None)
Other.Texture = NewMaterial;
NewSound = ReplaceSound(Other.AmbientSound);
if (NewSound != None)
Other.AmbientSound = NewSound;
// Emitters.
if (Emitter(Other) != None)
{
for (i = 0; i < Emitter(Other).Emitters.Length; i++)
{
NewMaterial = ReplaceMaterial(Emitter(Other).Emitters[i].Texture);
if (Texture(NewMaterial) != None)
Emitter(Other).Emitters[i].Texture = Texture(NewMaterial);
}
}
// Sound Things
if (SoundThing(Other) != None)
{
for (i = 0; i < SoundThing(Other).Settings.Sounds.Length; i++)
{
NewSound = ReplaceSound(SoundThing(Other).Settings.Sounds[i]);
if (NewSound != None)
SoundThing(Other).Settings.Sounds[i] = NewSound;
}
}
}
///////////////////////////////////////////////////////////////////////////////
// CheckReplacement
// This function is called for any actor spawned into the world.
// You can use this function to change any default properties of that actor,
// or replace it entirely with something else using ReplaceWith.
// Return FALSE if you replace the actor or just want it to be destroyed.
// Return TRUE if you want to keep the actor and don’t want to replace it.
// Unlike other functions you do NOT need to call Super here.
///////////////////////////////////////////////////////////////////////////////
function bool CheckReplacement(Actor Other, out byte bSuperRelevant)
{
ParseActor(Other);
return true;
}
///////////////////////////////////////////////////////////////////////////////
// ModifyNPC
// Called by PersonController/AnimalController after adding default inventory.
// Use this function to alter any aspect of the NPC you like.
// At this point the pawn’s head and body are set, so we can change their skins
// now!
///////////////////////////////////////////////////////////////////////////////
function ModifyNPC(Pawn Other)
{
local int i;
Super.ModifyNPC(Other);
// Do both the body and head separately.
ParseActor(Other);
if (P2MocapPawn(Other) != None)
{
ParseActor(P2MocapPawn(Other).MyHead);
// And while we’re at it get the boltons too
for (i = 0; i < P2MocapPawn(Other).MAX_BOLTONS; i++)
ParseActor(P2MocapPawn(Other).Boltons[i].part);
}
}
///////////////////////////////////////////////////////////////////////////////
// Default properties required by all P2GameMods.
///////////////////////////////////////////////////////////////////////////////
defaultproperties
{
// GroupName – any Game Mods with the same GroupName will be considered incompatible, and only one will be allowed to run.
// Use this if you make mods that are not designed to run alongside each other.
GroupName=””
// FriendlyName – the name of your Game Mod, displayed in the game mod menu.
FriendlyName=”Asset Replacer”
// Description – optional short description of your Game Mod
Description=”Replaces textures and sounds in-game.”
MaterialReplace[0]=(OldMaterial=Texture’AnimalSkins.Dog’,NewMaterial=Texture’Engine.DefaultTexture’)
MaterialReplace[1]=(OldMaterial=Texture’ChameleonSkins.MB__033__Avg_M_SS_Pants’,NewMaterial=Texture’Engine.DefaultTexture’)
SoundReplace[0]=(OldSound=Sound’AnimalSounds.insects.katydid1′,NewSound=Sound’AmbientSounds.fart4′)}
Explanations
I’ll go over a few of the sections in the script before we continue.
Global variables: We’ve defined a couple of structs for doing Material (texture) and Sound replacements. This allows you to easily define your replacements in the Default Properties section at the bottom.
ReplaceMaterial: This is a function that, when given a Material input, returns the Material it should be replaced with, or None if it shouldn’t be replaced. If you didn’t know already, a Material is the lowest-level definition for a texture, color, shader, or other material to be applied as a skin to an object. Texture is a subclass of Material, and things such as ConstantColor, Shader, and FinalBlend are also Material.
ReplaceSound: As with ReplaceMaterial, but does sounds instead.
ParseActor: This is a function we’ve defined to take any type of Actor as an input, and attempt to replace any sounds or materials on that actor, such as skins, body skins, head skins, emitter textures, ambient sounds, and so on. The implementation I’ve given you should cover most cases, but it’s possible to expand this function to cover other things such as weapon firing sounds.
CheckReplacement: This is a P2GameMod function we’re overriding. CheckReplacement is called on every single actor in the game when it spawns, so it’s the best place in most cases to swap out materials and sounds. It doesn’t cover all cases though. For example, any character at this point will have no head and only a placeholder skin to tell the game what sort of body texture to apply later. This is handled by the game’s Chameleon actor, which enables level designers to drop in a generic bystander and have it randomized to be any type of character: male, female, fat, skinny, causasian, Mexican, black, and so on. Since we can’t replace character skins here…
ModifyNPC: …we do it here, in ModifyNPC, which is another P2GameMod function. This is called by every character in the game AFTER they are set up with a head, body, and so on. At this point they’ll have their “final” appearance, which means we have a skin that we can modify. This function also handles the head, and any “boltons” they may have (such as a gun belt or badge for police, randomized hats, purses or bags, etc)
defaultproperties: This block is where the mod’s name and description are defined, as well as the material and sound swaps. Right now go ahead and change the name and description to something more fitting. We’ll cover how to do material and sound swaps in the next section, but if you compile and run this mod right now in suburbs-3, you’ll notice that Champ and the police officer are replaced with a checkerboard texture, and the bushes make fart sounds.
Adding in your own stuff
Okay, so let’s continue on with the example that you’ve created some new cop skins, and want to replace them in-game. In the default properties section, delete the existing MaterialReplace and SoundReplace lines. We’re going to add our own.
First, type out the following:
Don’t put anything in the single quotes yet, we’re going to fill those in. Go into POSTed and load up the original cop skins set (make sure to press the “load entire package” button). Find one of the skins you’re going to replace, right-click it, and hit “Copy Name”. Then paste the name into the MaterialReplace[0] line, in the empty quotes after OldMaterial=. Example:
Then, go to your NEW package and find the skin you’re replacing the old one with. Right-click that and hit Copy Name, and paste THAT into the other set of empty quotes:
When replacing skins this way, make sure you replace long-sleeve skins with long-sleeve skins, etc. Otherwise they will look wrong on the pawn. If you want to add new skins or change the sleeve, body type, etc., you’ll have to change the pawn’s ChameleonSkins lines in P2GameMod’s ModifyAppearance, a topic we’ll cover another time.
Save changes, now it’s time to compile your script. (You can skip this section if you already know how.) Make sure the editor is closed, and open Postal2.ini in POSTAL2EditorSystem. You want to find the section with the header [Engine.EditorEngine], and toward the bottom of that section you’ll see a bunch of “EditPackages=” lines, like so:
Add in your new code package at the bottom:
Then, run “ucc make” from the command line (or use the provided UCC_Make.bat file in the System folder), and if all goes well, you’ll have a new file in System called CopSkinReplacer.u.
Next, you’ll need to create an INT file so the game knows your mod exists. Make a new file in System called CopSkinReplacer.int, and paste in the following:
With this file, your mod is now discoverable in the in-game Workshop Browser. Start Postal2.exe from your POSTAL2EditorSystem folder, pick New Game, then Workshop, and under the Mods tab you should see your new cop skin replacer. Add it and start a new game. Verify that your skin was indeed replaced — for our example we changed the cop skin given to the first police officer you’ll see.
If all is well, proceed to make MaterialReplace lines for the rest of your textures. Note that if you screw up, UCC will NOT inform you that you’ve made an error — mistakes made in the default properties section will fail silently, leading to incorrect results in-game. Check UCC.log after compiling to make sure there were no errors. Also, don’t forget to delete CopSkinReplacer.u (or whatever package name you used) before recompiling, or it won’t get compiled.
Sound replacement works similarly. The only difference is there is no right-click menu for sounds — merely right-clicking on a sound will automatically copy its name to your clipboard.
Once everything is good, publish your mod to Workshop! Remember to include all your texture/sound packages, your .U code package, and your .INT file. Publish as a private or friends-only mod first, then test on your main copy of POSTAL 2. Once all is good, don’t forget to set it to public.
Handling other material and sound swaps
The lines covered in AssetReplacer cannot possibly handle every situation. Many classes have their own materials and sounds defined in their properties outside of Skins or AmbientSounds, and replacing those will require some extra work.
For this example, let’s say you’re reskinning the default pistol, and you want to replace the default firing sound with something that has a little more kick to it. You’ve already added your replacement textures and sounds (including the new firing sound) to the default properties, but the pistol firing sound won’t change. What should we do?
Changing other properties requires a bit of know-how, and you may have to dig around in the source code until you find the class you need to replace. Fortunately, most of the classes are grouped together: pawns in People and BasePeople, weapons and pickups in Inventory, and so on. However, it can be difficult to find in some cases, and this is where an editor like Notepad++[notepad-plus-plus.org] comes in handy: by pressing Ctrl-Shift-F, you can search entire directory trees for files.
We know that we have a default pistol firing sound, and that has to be defined somewhere in the script. So we’ll use Notepad++’s search feature to find it. Open up POSTed and find the default pistol firing sound under WeaponSounds. Right-click it to copy its name to the clipboard, then go back to Notepad++. Press Ctrl-Shift-F and you’ll get a dialog box pop up.
Paste the name of the sound in “Find What” (for the pistol firing sound, it’s WeaponSounds.pistol_fire) Leave “Replace With” alone, and in “Filters”, put “*.uc” (since we’re searching in all UnrealScript files). Finally, in Directory, set that to the path of your POSTAL2Editor folder. When you’re done, your dialog box should look something like this:
Hit “Find All”. Depending on the speed of your computer, this can take up to a couple minutes, so be patient. Once it’s done, you should have two results:
Which one do we need? Well, PistolAttachment refers to the third-person actor (the gun in the Dude’s hand as seen through a mirror), and the FireSound there is used only in netplay, which doesn’t concern us since we’re working on a single-player mod. Thus, PistolWeapon is the one we want. Double-click it to open it up, and you’ll find yourself in the default properties section of PistolWeapon, where the FireSound is defined to be the sound we want to replace.
Now that you know what class contains your sound, let’s work on replacing it. Open up your AssetReplacer class and go down to the very end of the ParseActor function. We’re going to add a check for the PistolWeapon. After the block for SoundThings, paste in this code:
First of all, we have to cast Other (the actor to be checked) to PistolWeapon, since that’s the class containing the sound. We can’t go “Other.FireSound” or the compiler will throw an error. Then, we pass in the PistolWeapon(Other)’s FireSound to ReplaceSound and see if it needs to be replaced. If so, we assign it the new sound.
You don’t need to be a coding wiz to make your own replacements — just search the codebase for the sound or texture you want to replace, and then replace “PistolWeapon” and “FireSound” in the above code snippet as needed. If you’re doing a material swap instead, you’d use a code snippet that looked like this:
Obviously you’d change “ClassToChange” and “MaterialToChange” to the target class and material property.
Save and recompile, and your pistol should now have that extra kick you made for it. Well done!
Conclusion
Now you have a good idea of how to do your own texture and sound replacements without requiring manual installation by the end user. You should always, always, always script your asset replacements wherever possible, as it reduces the amount of interaction required by the user. A workshop full of “manual install” items serves only to frustrate users and defeats the purpose of the Steam Workshop.
Not all asset replacements can be done in this manner, though. If you make a large “HD texture pack” that replaces a huge variety of textures and requires manual installation by the user, then I suggest you upload it to a third-party site such as ModDB. That doesn’t mean you can’t upload stuff here, too — give users a taste of your work by making a HD Dude or HD Weapons pack, with a link to the full mod off-site.
Now get out there and make some awesome texture/sound replacements!