JC Outfits Mod Integration Guide

Intro

This guide is designed to serve as a comprehensive resource to allow mod authors to add JC Outfits support to their mod. It details all of the steps required in order to integrate the outfits system into your mod as seamlessly as possible. The Outfits mod has been designed to be as modular as possible, with almost all functionality being held within JCOutfits.u, with only minimal changes being required to core game classes. This mod is designed to be integrated in a way that makes it optional - for users with JC Outfits installed, they will see the Outfits screen. For everyone else, this functionality will be hidden and the outfits system will be disabled.

JCOutfits.u is designed to be completely self-contained, and exposes an API to DeusEx.u. This means that it’s possible for new versions of JCOutfits.u to work with existing mods, even if those mods were only updated to work with a previous version. Because of this, it is highly recommended that you do not include JCOutfits.u in your own mod. Instead, you should let players know that it is compatible, but do not include it. This will mean that players who do not want the functionality can easily avoid it, and will essentially future-proof your mod in the long term against changes made to future Outfits versions.

Step-by-step integration

Copy over files

Copy the provided OutfitSpawner.uc, OutfitSpawner2.uc and OutfitManagerBase.uc files into your DeusEx classes folder.

DeusExPlayer.uc

The first edit should be made to DeusExPlayer.uc

First, locate the end of list of variables. It will usually be immediately followed by a list of native functions.

Here’s a snippet of what the end of the variable list looks like:

// For closing comptuers if the server quits
var Computers ActiveComputer;

// native Functions
native(1099) final function string GetDeusExVersion();
native(2100) final function ConBindEvents();

The following code needs to be inserted between the end of the variables and the start of the native functions

// OUTFIT STUFF
var travel OutfitManagerBase outfitManager;
var globalconfig string unlockedOutfits[255];

Unless other edits have been made by your mod, in most cases, the code should end up looking like this:

// For closing comptuers if the server quits
var Computers ActiveComputer;

// OUTFIT STUFF
var travel OutfitManagerBase outfitManager;
var globalconfig string unlockedOutfits[255];

// native Functions
native(1099) final function string GetDeusExVersion();
native(2100) final function ConBindEvents();

Locate the start of the functions, which usually looks like this:

var localized String ImagesButtonLabel;
var localized String LogsButtonLabel;

// ----------------------------------------------------------------------
// CreateButtons()
// ----------------------------------------------------------------------

And insert the following code after the final variables, but before the start of the functions:

//Sarge: Outfits Stuff
var PersonaNavButtonWindow btnOutfits;

var localized String ConsButtonLabelShort; //Sarge: Added
var localized String OutfitsButtonLabel;

Unless other edits have been made by your mod, in most cases, the code should end up looking like this:

var localized String ImagesButtonLabel;
var localized String LogsButtonLabel;

//Sarge: Outfits Stuff
var PersonaNavButtonWindow btnOutfits;

var localized String ConsButtonLabelShort; //Sarge: Added
var localized String OutfitsButtonLabel;

// ----------------------------------------------------------------------
// CreateButtons()
// ----------------------------------------------------------------------

Next, locate the line Super.CreateButtons(), and add the following line above it:

CreateOutfitsButton();                  //Sarge: Added

Unless other edits have been made by your mod, in most cases, the code should end up looking like this:

    btnAugs      = CreateNavButton(winNavButtons, AugsButtonLabel);
    btnHealth    = CreateNavButton(winNavButtons, HealthButtonLabel);
    btnInventory = CreateNavButton(winNavButtons, InventoryButtonLabel);

    CreateOutfitsButton();                  //Sarge: Added
    
    Super.CreateButtons();
}

Add the following code anywhere in the file (it is recommended to place it right below the CreateButtons function):

// ----------------------------------------------------------------------
// CreateOutfitsButton()
// Will shorten the Conversations button to fit it in
// ----------------------------------------------------------------------

function CreateOutfitsButton()
{
    local class<PersonaScreenBaseWindow> test;
    test = class<PersonaScreenBaseWindow>(DynamicLoadObject("JCOutfits.PersonaScreenOutfits", class'Class'));

    //Only create the Outfits button if the outfits window is actually available
    if (test != None)
    {
        btnOutfits   = CreateNavButton(winNavButtons, OutfitsButtonLabel);
        btnCons.SetButtonText(ConsButtonLabelShort);
    }
}

Next, locate the following lines in the ButtonActivated function:

        case btnImages:
            winClass = Class'PersonaScreenImages';
            break;

        case btnLogs:
            winClass = Class'PersonaScreenLogs';
            break;

        default:
            bHandled = False;
            break;

Add the following code between the btnLogs case and the default case:

        //Sarge: Add new button for Outfits
        case btnOutfits:
            winClass = class<PersonaScreenBaseWindow>(DynamicLoadObject("JCOutfits.PersonaScreenOutfits", class'Class'));
            break;

Unless other edits have been made by your mod, in most cases, the code should end up looking like this:

        case btnImages:
            winClass = Class'PersonaScreenImages';
            break;

        case btnLogs:
            winClass = Class'PersonaScreenLogs';
            break;
        
        //Sarge: Add new button for Outfits
        case btnOutfits:
            winClass = class<PersonaScreenBaseWindow>(DynamicLoadObject("JCOutfits.PersonaScreenOutfits", class'Class'));
            break;

        default:
            bHandled = False;
            break;

Finally, add the following two properties to the defaultproperties section:

     OutfitsButtonLabel="|&Outfits"
     ConsButtonLabelShort="|&Conv."

Unless other edits have been made by your mod, in most cases, the code should end up looking like this:

     ConsButtonLabel="|&Conversations"
     ImagesButtonLabel="I|&mages"
     LogsButtonLabel="|&Logs"
     OutfitsButtonLabel="|&Outfits"
     ConsButtonLabelShort="|&Conv."
}

JCDentonMale.uc

First, find the TravelPostAccept function, which typically looks like this:

event TravelPostAccept()
{
    local DeusExLevelInfo info;

    Super.TravelPostAccept();

    switch(PlayerSkin)
    {
        case 0: MultiSkins[0] = Texture'JCDentonTex0'; break;
        case 1: MultiSkins[0] = Texture'JCDentonTex4'; break;
        case 2: MultiSkins[0] = Texture'JCDentonTex5'; break;
        case 3: MultiSkins[0] = Texture'JCDentonTex6'; break;
        case 4: MultiSkins[0] = Texture'JCDentonTex7'; break;
    }
}

Insert the following lines at the end of the function:

//SARGE: Setup outfit manager
SetTimer(0.5,false);

Unless other edits have been made by your mod, in most cases, the code should end up looking like this:

event TravelPostAccept()
{
    local DeusExLevelInfo info;

    Super.TravelPostAccept();

    switch(PlayerSkin)
    {
        case 0: MultiSkins[0] = Texture'JCDentonTex0'; break;
        case 1: MultiSkins[0] = Texture'JCDentonTex4'; break;
        case 2: MultiSkins[0] = Texture'JCDentonTex5'; break;
        case 3: MultiSkins[0] = Texture'JCDentonTex6'; break;
        case 4: MultiSkins[0] = Texture'JCDentonTex7'; break;
    }

    //SARGE: Setup outfit manager
    SetTimer(0.5,false);
}

Finally, copy the following code below the TravelPostAccept function:

// ----------------------------------------------------------------------
// Timer()
// SARGE: We need to delay slightly before setting models, to allow mods like LDDP to work properly
// ----------------------------------------------------------------------

function Timer()
{
    Super.Timer();
    SetupOutfitManager();
}


// ----------------------------------------------------------------------
// ResetPlayerToDefaults()
// SARGE: When we start a new game, throw away our outfit manager
// ----------------------------------------------------------------------
function ResetPlayerToDefaults()
{
    outfitManager = None;
    Super.ResetPlayerToDefaults();
}

// ----------------------------------------------------------------------
// SetupOutfitManager()
// SARGE: Setup the outfit manager and restore current outfit
// ----------------------------------------------------------------------

function SetupOutfitManager()
{
    local class<OutfitManagerBase> managerBaseClass;

    // create the Outfit Manager if not found
    if (outfitManager == None)
    {
        //ClientMessage("Outfit Manager successfully created");
        //outfitManager = new(Self) class'OutfitManager';
        managerBaseClass = class<OutfitManagerBase>(DynamicLoadObject("JCOutfits.OutfitManager", class'Class'));
        outfitManager = new(Self) managerBaseClass;
    }

    if (outfitManager != None)
    {
        //ClientMessage("Outfit Manager successfully inited");

        //Call base setup code, required each map load
        outfitManager.Setup(Self);
        
        //Add additional outfits below this line
        //---------------------------------------
        //See docs/mod_integration.pdf for more info
        //---------------------------------------

        //Re-assign current outfit
        outfitManager.ApplyCurrentOutfit();
    }
}

Creating new Outfits

New outfits can be added quite easily, using the following code:

outfitManager.AddOutfit("chef","Chefs Outfit","This outfit is usually worn by chefs","previewTex",true,false,"GM_Suit","PantsTex10","skin","ChefTex1","ChefTex1","GrayMaskTex","BlackMaskTex","ChefTex3",6);

The function arguments are as follows: 1. The ID of the outfit. This is used by outfit pickups to associate a specific outfit with a pickup. This should be a simple name like “myMod_outfit1”. Multiple outfits can have the same id if they are assigned to different genders. 2. The name of the outfit. This will appear in the Outfits list. 3. The long description of the outfit. This will eventually appear in the Outfits list but is currently unused. 4. The preview image of the outfit. This will appear in the main display when selecting the outfit in the menu. 5. Whether or not this outfit can be worn by males. 6. Whether or not this outfit can be worn by females (if LDDP is installed). 7. The mesh used by the outfit. This is typically “GM_Suit”, “GM_Trench”, etc. 8. A list of 7 textures used by the outfit. Typically textures 1-5 are used normally by the model, and textures 6-7 are used for glasses/hats/etc. - The string “skin” can be used to use the characters current skin texture in any texture slot. - The string “default” can be used to use the texture from the characters default outfit (JC Denton’s trenchcoat). This is useful for making outfits that are minor edits to the original trenchcoat - The string “none” can be used to denote no texture (or the argument can be left blank) 9. Optionally a number can be specified to determine which textures should be set to “none” when the user has the “Use Accessories” checkbox disabled. If no value is set, this uses slots 6 and 7, which are mostly used in vanilla for glasses frames and lenses. The number determines which slot is considered the first slot for disabling, and 2 slots will always be disabled.

It is recommended to add any additional outfits at the end of the SetupOutfitManager function, before the ApplyCurrentOutfit(); line. For example:

// ----------------------------------------------------------------------
// SetupOutfitManager()
// SARGE: Setup the outfit manager and restore current outfit
// ----------------------------------------------------------------------

function SetupOutfitManager()
{
    local class<OutfitManagerBase> managerBaseClass;

    // create the Outfit Manager if not found
    if (outfitManager == None)
    {
        //ClientMessage("Outfit Manager successfully created");
        //outfitManager = new(Self) class'OutfitManager';
        managerBaseClass = class<OutfitManagerBase>(DynamicLoadObject("JCOutfits.OutfitManager", class'Class'));
        outfitManager = new(Self) managerBaseClass;
    }

    if (outfitManager != None)
    {
        //ClientMessage("Outfit Manager successfully inited");

        //Call base setup code, required each map load
        outfitManager.Setup(Self);

        //Add additional outfits below this line
        //---------------------------------------
        outfitManager.AddOutfit("chef","Chefs Outfit","Some Desc","hudImg1",true,false,"GM_Suit","PantsTex10","skin","ChefTex1","ChefTex1","GrayMaskTex","BlackMaskTex","ChefTex3",6);
        outfitManager.AddOutfit("chef2","Chefs Outfit","Some Desc","hudImg2",true,false,"GM_Suit","PantsTex10","skin","ChefTex1","ChefTex1","GrayMaskTex","BlackMaskTex","ChefTex3",6);
        outfitManager.AddOutfit("chef3","Chefs Outfit","Some Desc","hudImg3",true,false,"GM_Suit","PantsTex10","skin","ChefTex1","ChefTex1","GrayMaskTex","BlackMaskTex","ChefTex3",6);
        //---------------------------------------

        //Re-assign current outfit
        outfitManager.ApplyCurrentOutfit();
    }
}

Outfits with Skin Colours

It is possible to create outfits which reflect the players chosen skin colour.

The mod automatically looks for textures named <texture>_S[0-4], where the numbers 0-4 represent the available skin colours (white, black, hispanic, ginger, albino), when given a base texture name.

So for instance, you might add a new outfit with the texture MyShirtTex01. Your mod can additionally contain a texture MyShirtTex01_S1 for a version containing dark skin, MyShirtTex_S2 for hispanic, etc.

You can then simply add the default texture in the AddOutfit call, like so:

outfitManager.AddOutfit("myOutfit","My Cool Outfit","previewTex",true,false,"GM_Suit","PantsTex10","skin","MyShirtTex01","MyShirtTex02","GrayMaskTex","BlackMaskTex","MyShirtTex03",6);

Note: It is recommended to include a default version of MyShirtTex01, and not ship a special white skin version MyShirtTex01_S0 to provide a fallback in case someone is using a mod that provides additional skin colours. This way, any request for an invalid variant texture will default to the basic white version. This can be done for other skin colours instead, if desired. As long as one skin colour is the “default” texture, it should always work in some capacity.

Don’t forget to include these textures in your mod, via the usual #exec statements, for example:

//Goth GF Outfit
#exec TEXTURE IMPORT FILE="Textures\Female\Goth GF Outfit\Tex1.bmp"                                NAME="Outfit3F_Tex1"               GROUP="Outfits"
#exec TEXTURE IMPORT FILE="Textures\Female\Goth GF Outfit\Tex1_S1.bmp"                             NAME="Outfit3F_Tex1_S1"            GROUP="Outfits"
#exec TEXTURE IMPORT FILE="Textures\Female\Goth GF Outfit\Tex1_S2.bmp"                             NAME="Outfit3F_Tex1_S2"            GROUP="Outfits"
#exec TEXTURE IMPORT FILE="Textures\Female\Goth GF Outfit\Tex1_S3.bmp"                             NAME="Outfit3F_Tex1_S3"            GROUP="Outfits"
#exec TEXTURE IMPORT FILE="Textures\Female\Goth GF Outfit\Tex1_S4.bmp"                             NAME="Outfit3F_Tex1_S4"            GROUP="Outfits"
#exec TEXTURE IMPORT FILE="Textures\Female\Goth GF Outfit\Tex2.pcx"                                NAME="Outfit3F_Tex2"               GROUP="Outfits"
#exec TEXTURE IMPORT FILE="Textures\Female\Goth GF Outfit\Tex3.bmp"                                NAME="Outfit3F_Tex3"               GROUP="Outfits"