Skip to content

Commit

Permalink
Implemented Issue X2CommunityCore#783 - add DLC hook to modify genera…
Browse files Browse the repository at this point in the history
…ted unit appearance.
  • Loading branch information
Iridar authored and Iridar51 committed May 28, 2021
1 parent 56d19fe commit d8b0784
Show file tree
Hide file tree
Showing 6 changed files with 455 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -762,3 +762,43 @@ final function string GetDisplayName()
return ModDependency.DisplayName;
}
/// End Issue #524

// Start Issue #783
// <summary>
/// Called from XGCharacterGenerator:CreateTSoldier
/// Has no return value, just modify the CharGen.kSoldier directly.
/// HL-Docs: feature:ModifyGeneratedUnitAppearance; issue:783; tags:customization,compatibility
/// ## Usage
/// This DLC hook allows mods to make arbitrary changes to unit appearance
/// after it has been generated by `XGCharacterGenerator::CreateTSoldier()`.
/// The generated appearance is stored in `CharGen.kSoldier`, which you can modify directly.
/// Other arguments are provided to you mostly for reference,
/// and presented to you as they were used by the `CreateTSoldier()` function.
/// The UnitState and the GameState will be passed to this hook
/// only if the `CreateTSoldier()` function was called from `CreateTSoldierFromUnit()`,
/// which normally happens only in the Shell code (TQL / Challenge Mode / Character Pool),
/// and will be `none` otherwise.
/// If you wish to "redo" some parts of the process of generating unit's appearance,
/// you can call various methods in the Character Generator,
/// but you must avoid calling the `CreateTSoldier()` and `CreateTSoldierFromUnit()` methods,
/// as that will retrigger the hook, potentially causing an inception loop and crashing the game.
/// ## Compatibility
/// Custom `XGCharacterGenerator` classes used by mods to generate appearance of custom units
/// can potentially interfere with the normal operation of this hook for themselves.
/// If the Character Generator implements a custom `CreateTSoldier()` function that
/// does not call `super.CreateTSoldier()`, then this DLC hook will not be called for that class.
/// If `super.CreateTSoldier()` *is* called, but the custom `CreateTSoldier()` function
/// makes changes to the generated appearance afterwards, it can potentially override
/// changes made by this hook.
/// For example, Character Generators for Faction Hero classes had to be adjusted
/// in the Highlander so that they do not override Country and Nickname after
/// calling `super.CreateTSoldier()`, and instead override the `SetCountry()` and
/// `GenerateName()` methods, which are called by `super.CreateTSoldier()`.
/// For best compatibility with this hook, mod-added `XGCharacterGenerator()` classes
/// should avoid making any appearance changes after calling `super.CreateTSoldier()`.
/// Ideally, that function should not be overridden at all, and the Character Generator
/// should rely on overriding other methods called by `CreateTSoldier()` as much as possible.
// </summary>
static function ModifyGeneratedUnitAppearance(XGCharacterGenerator CharGen, const name CharacterTemplateName, const EGender eForceGender, const name nmCountry, const int iRace, const name ArmorName, XComGameState_Unit UnitState, XComGameState UseGameState)
{}
/// End Issue #783
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,13 @@ var config float DLCPartPackDefaultChance;
var int m_iHairType;

// temporary variable for soldier creation, used by TemplateMgr filter functions
var protected TSoldier kSoldier;
var protected X2BodyPartTemplate kTorsoTemplate;
var protected name MatchCharacterTemplateForTorso;
var protected name MatchArmorTemplateForTorso;
var protected array<name> DLCNames; //List of DLC packs to pull parts from for the currently generating soldier.
// Start unprotect variables for issue #783
var /*protected*/ TSoldier kSoldier;
var /*protected*/ X2BodyPartTemplate kTorsoTemplate;
var /*protected*/ name MatchCharacterTemplateForTorso;
var /*protected*/ name MatchArmorTemplateForTorso;
var /*protected*/ array<name> DLCNames; //List of DLC packs to pull parts from for the currently generating soldier.
// End unprotect variables for issue #783

// Store a country name to be use in bios for soldiers that force a unique country
var name BioCountryName;
Expand All @@ -139,6 +141,11 @@ var X2CharacterTemplate m_CharTemplate;
// New variable for issue #397
var config(Content) int iDefaultWeaponTint;

// Start issue #783
var XComGameState_Unit GenerateAppearanceForUnitState;
var XComGameState GenerateAppearanceForGameState;
// End issue #783

function GenerateName( int iGender, name CountryName, out string strFirst, out string strLast, optional int iRace = -1 )
{
local X2StrategyElementTemplateManager StratMgr;
Expand Down Expand Up @@ -294,10 +301,26 @@ function TSoldier CreateTSoldierFromUnit( XComGameState_Unit Unit, XComGameState
{
local XComGameState_Item ArmorItem;
local name ArmorName;
// Variable for issue #783
local TSoldier CreatedTSoldier;

ArmorItem = Unit.GetItemInSlot(eInvSlot_Armor, UseGameState, true);
ArmorName = ArmorItem == none ? '' : ArmorItem.GetMyTemplateName();
return CreateTSoldier( Unit.GetMyTemplateName(), EGender(Unit.kAppearance.iGender), Unit.kAppearance.nmFlag, Unit.kAppearance.iRace, ArmorName );

// Start issue #783
GenerateAppearanceForUnitState = Unit;
GenerateAppearanceForGameState = UseGameState;

CreatedTSoldier = CreateTSoldier( Unit.GetMyTemplateName(), EGender(Unit.kAppearance.iGender), Unit.kAppearance.nmFlag, Unit.kAppearance.iRace, ArmorName );

// Blank the properties just in case this instance of the Character Generator will be used to also call CreateTSoldier() separately,
// which would trigger the 'PostUnitAppearanceGenerated' event another time, but it would still pass the same Unit State and Game State from the time CreateTSoldierFromUnit()
// was called. So we blank them out to make sure we're not passing irrelevant information with the event.
GenerateAppearanceForUnitState = none;
GenerateAppearanceForGameState = none;

return CreatedTSoldier;
// End issue #783
}

delegate bool FilterCallback(X2BodyPartTemplate Template);
Expand Down Expand Up @@ -380,9 +403,28 @@ function TSoldier CreateTSoldier( optional name CharacterTemplateName, optional

BioCountryName = kSoldier.nmCountry;

// Start issue #783
ModifyGeneratedUnitAppearance(CharacterTemplateName, eForceGender, nmCountry, iRace, ArmorName);
// End issue #783

return kSoldier;
}

// Start issue #783
private function ModifyGeneratedUnitAppearance(optional name CharacterTemplateName, optional EGender eForceGender, optional name nmCountry = '', optional int iRace = -1, optional name ArmorName)
{
local array<X2DownloadableContentInfo> DLCInfos;
local int i;

/// HL-Docs: ref:ModifyGeneratedUnitAppearance; issue:783
DLCInfos = `ONLINEEVENTMGR.GetDLCInfos(false);
for (i = 0; i < DLCInfos.Length; i++)
{
DLCInfos[i].ModifyGeneratedUnitAppearance(self, CharacterTemplateName, eForceGender, nmCountry, iRace, ArmorName, GenerateAppearanceForUnitState, GenerateAppearanceForGameState);
}
}
// End issue #783

static function Name GetLanguageByString(optional string strLanguage="")
{
if (len(strLanguage) == 0)
Expand Down Expand Up @@ -1027,4 +1069,4 @@ function string GenerateNickname(X2SoldierClassTemplate Template, int iGender)
}

return "";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
class XGCharacterGenerator_Reaper extends XGCharacterGenerator
dependson(X2StrategyGameRulesetDataStructures) config(NameList);

var config array<int> PrimaryArmorColors;
var config array<int> SecondaryArmorColors;
var config array<name> MaleHeads;
var config array<Name> FemaleHeads;
var config array<name> MaleHelmets;
var config array<name> FemaleHelmets;
var config array<name> MaleLeftArms;
var config array<name> MaleRightArms;
var config array<name> FemaleLeftArms;
var config array<name> FemaleRightArms;


function bool IsSoldier(name CharacterTemplateName)
{
return true;
}

function X2CharacterTemplate SetCharacterTemplate(name CharacterTemplateName, name ArmorName)
{
MatchArmorTemplateForTorso = (ArmorName == '') ? 'ReaperArmor' : ArmorName;
MatchCharacterTemplateForTorso = 'NoCharacterTemplateName'; //Force the selector to use the armor type to filter torsos

return class'X2CharacterTemplateManager'.static.GetCharacterTemplateManager().FindCharacterTemplate('ReaperSoldier');
}

// Start issue #783
// Normally this function calls the super.CreateTSoldier, and then manually sets the country and nickname.
// In order to make the DLC hook for this issue more compatible with resistance faction soldiers,
// this functionality has been moved into SetCountry() and GenerateName() methods which will be called by super.CreateTSoldier.
function TSoldier CreateTSoldier(optional name CharacterTemplateName, optional EGender eForceGender, optional name nmCountry = '', optional int iRace = -1, optional name ArmorName)
{
kSoldier = super.CreateTSoldier('ReaperSoldier', eForceGender, nmCountry, iRace, ArmorName);
return kSoldier;
}

function SetCountry(name nmCountry)
{
kSoldier.nmCountry = 'Country_Reaper';
kSoldier.kAppearance.nmFlag = kSoldier.nmCountry; // needs to be copied here for pawns -- jboswell
}

function GenerateName(int iGender, name CountryName, out string strFirst, out string strLast, optional int iRace = -1)
{
local X2SoldierClassTemplateManager ClassMgr;
local X2SoldierClassTemplate ClassTemplate;

super.GenerateName(kSoldier.kAppearance.iGender, kSoldier.nmCountry, kSoldier.strFirstName, kSoldier.strLastName, kSoldier.kAppearance.iRace);

ClassMgr = class'X2SoldierClassTemplateManager'.static.GetSoldierClassTemplateManager();
ClassTemplate = ClassMgr.FindSoldierClassTemplate('Reaper');
kSoldier.strNickName = GenerateNickname(ClassTemplate, kSoldier.kAppearance.iGender);
}
// End issue #783

function SetRace(int iRace)
{
kSoldier.kAppearance.iRace = eRace_Caucasian;
}

function SetHead(X2SimpleBodyPartFilter BodyPartFilter, X2CharacterTemplate CharacterTemplate)
{
super.SetHead(BodyPartFilter, CharacterTemplate);

if (kSoldier.kAppearance.iGender == eGender_Male)
{
kSoldier.kAppearance.nmHead = default.MaleHeads[`SYNC_RAND(default.MaleHeads.Length)];
}
else
{
kSoldier.kAppearance.nmHead = default.FemaleHeads[`SYNC_RAND(default.FemaleHeads.Length)];
}
}

function SetArmsLegsAndDeco(X2SimpleBodyPartFilter BodyPartFilter)
{
super.SetArmsLegsAndDeco(BodyPartFilter);

if(kSoldier.kAppearance.iGender == eGender_Male)
{
kSoldier.kAppearance.nmLeftArm = default.MaleLeftArms[`SYNC_RAND(default.MaleLeftArms.Length)];
kSoldier.kAppearance.nmRightArm = default.MaleRightArms[`SYNC_RAND(default.MaleRightArms.Length)];
}
else
{
kSoldier.kAppearance.nmLeftArm = default.FemaleLeftArms[`SYNC_RAND(default.FemaleLeftArms.Length)];
kSoldier.kAppearance.nmRightArm = default.FemaleRightArms[`SYNC_RAND(default.FemaleRightArms.Length)];
}
}

function SetAccessories(X2SimpleBodyPartFilter BodyPartFilter, name CharacterTemplateName)
{
super.SetAccessories(BodyPartFilter, CharacterTemplateName);

if(kSoldier.kAppearance.iGender == eGender_Male)
{
kSoldier.kAppearance.nmHelmet = default.MaleHelmets[`SYNC_RAND(default.MaleHelmets.Length)];
}
else
{
kSoldier.kAppearance.nmHelmet = default.FemaleHelmets[`SYNC_RAND(default.FemaleHelmets.Length)];
}
}

function SetArmorTints(X2CharacterTemplate CharacterTemplate)
{
super.SetArmorTints(CharacterTemplate);

kSoldier.kAppearance.iArmorTint = default.PrimaryArmorColors[`SYNC_RAND(default.PrimaryArmorColors.Length)];
kSoldier.kAppearance.iArmorTintSecondary = default.SecondaryArmorColors[`SYNC_RAND(default.SecondaryArmorColors.Length)];
}

function SetVoice(name CharacterTemplateName, name CountryName)
{
if (IsSoldier(CharacterTemplateName))
{
kSoldier.kAppearance.nmVoice = GetVoiceFromCountryAndGenderAndCharacter(CountryName, kSoldier.kAppearance.iGender, CharacterTemplateName);

if (kSoldier.kAppearance.nmVoice == '')
{
if (kSoldier.kAppearance.iGender == eGender_Male)
{
kSoldier.kAppearance.nmVoice = 'ReaperMaleVoice1_Localized';
}
else
{
kSoldier.kAppearance.nmVoice = 'ReaperFemaleVoice1_Localized';
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
class XGCharacterGenerator_Skirmisher extends XGCharacterGenerator
dependson(X2StrategyGameRulesetDataStructures);

var config array<int> PrimaryArmorColors;
var config array<int> SecondaryArmorColors;
var config array<name> MaleHelmets;
var config array<name> FemaleHelmets;

function bool IsSoldier(name CharacterTemplateName)
{
return true;
}

function X2CharacterTemplate SetCharacterTemplate(name CharacterTemplateName, name ArmorName)
{
MatchArmorTemplateForTorso = (ArmorName == '') ? 'SkirmisherArmor' : ArmorName;
MatchCharacterTemplateForTorso = 'NoCharacterTemplateName'; //Force the selector to use the armor type to filter torsos

return class'X2CharacterTemplateManager'.static.GetCharacterTemplateManager().FindCharacterTemplate('SkirmisherSoldier');
}

// Start issue #783
// Normally this function calls the super.CreateTSoldier, and then manually sets the country and nickname.
// In order to make the DLC hook for this issue more compatible with resistance faction soldiers,
// this functionality has been moved into SetCountry() and GenerateName() methods which will be called by super.CreateTSoldier.
function TSoldier CreateTSoldier(optional name CharacterTemplateName, optional EGender eForceGender, optional name nmCountry = '', optional int iRace = -1, optional name ArmorName)
{
kSoldier = super.CreateTSoldier('SkirmisherSoldier', eForceGender, nmCountry, iRace, ArmorName);
return kSoldier;
}

function SetCountry(name nmCountry)
{
kSoldier.nmCountry = 'Country_Skirmisher';
kSoldier.kAppearance.nmFlag = kSoldier.nmCountry; // needs to be copied here for pawns -- jboswell
}

function GenerateName( int iGender, name CountryName, out string strFirst, out string strLast, optional int iRace = -1 )
{
local X2SoldierClassTemplateManager ClassMgr;
local X2SoldierClassTemplate ClassTemplate;

super.GenerateName( kSoldier.kAppearance.iGender, kSoldier.nmCountry, kSoldier.strFirstName, kSoldier.strLastName, kSoldier.kAppearance.iRace );

ClassMgr = class'X2SoldierClassTemplateManager'.static.GetSoldierClassTemplateManager();
ClassTemplate = ClassMgr.FindSoldierClassTemplate('Skirmisher');
kSoldier.strNickName = GenerateNickname(ClassTemplate, kSoldier.kAppearance.iGender);
}
// End issue #783

function SetAccessories(X2SimpleBodyPartFilter BodyPartFilter, name CharacterTemplateName)
{
local X2BodyPartTemplateManager PartTemplateManager;

PartTemplateManager = class'X2BodyPartTemplateManager'.static.GetBodyPartTemplateManager();

//Turn off most customization options for Skirmishers
SetBodyPartToFirstInArray(PartTemplateManager, kSoldier.kAppearance.nmPatterns, "Patterns", BodyPartFilter.FilterAny);
SetBodyPartToFirstInArray(PartTemplateManager, kSoldier.kAppearance.nmWeaponPattern, "Patterns", BodyPartFilter.FilterAny);
SetBodyPartToFirstInArray(PartTemplateManager, kSoldier.kAppearance.nmTattoo_LeftArm, "Tattoos", BodyPartFilter.FilterAny);
SetBodyPartToFirstInArray(PartTemplateManager, kSoldier.kAppearance.nmTattoo_RightArm, "Tattoos", BodyPartFilter.FilterAny);
SetBodyPartToFirstInArray(PartTemplateManager, kSoldier.kAppearance.nmHaircut, "Hair", BodyPartFilter.FilterAny);
SetBodyPartToFirstInArray(PartTemplateManager, kSoldier.kAppearance.nmBeard, "Beards", BodyPartFilter.FilterAny);
SetBodyPartToFirstInArray(PartTemplateManager, kSoldier.kAppearance.nmHelmet, "Helmets", BodyPartFilter.FilterAny);
SetBodyPartToFirstInArray(PartTemplateManager, kSoldier.kAppearance.nmFacePropLower, "FacePropsLower", BodyPartFilter.FilterAny);
SetBodyPartToFirstInArray(PartTemplateManager, kSoldier.kAppearance.nmFacePropUpper, "FacePropsUpper", BodyPartFilter.FilterAny);

// Randomly choose a Skirmisher scar
RandomizeSetBodyPart(PartTemplateManager, kSoldier.kAppearance.nmScars, "Scars", BodyPartFilter.FilterByCharacter);

if (kSoldier.kAppearance.iGender == eGender_Male)
{
kSoldier.kAppearance.nmHelmet = default.MaleHelmets[`SYNC_RAND(default.MaleHelmets.Length)];
}
else
{
kSoldier.kAppearance.nmHelmet = default.FemaleHelmets[`SYNC_RAND(default.FemaleHelmets.Length)];
}
}

function SetArmorTints(X2CharacterTemplate CharacterTemplate)
{
super.SetArmorTints(CharacterTemplate);

kSoldier.kAppearance.iArmorTint = default.PrimaryArmorColors[`SYNC_RAND(default.PrimaryArmorColors.Length)];
kSoldier.kAppearance.iArmorTintSecondary = default.SecondaryArmorColors[`SYNC_RAND(default.SecondaryArmorColors.Length)];
}

function SetVoice(name CharacterTemplateName, name CountryName)
{
if (IsSoldier(CharacterTemplateName))
{
kSoldier.kAppearance.nmVoice = GetVoiceFromCountryAndGenderAndCharacter(CountryName, kSoldier.kAppearance.iGender, CharacterTemplateName);

if (kSoldier.kAppearance.nmVoice == '')
{
if (kSoldier.kAppearance.iGender == eGender_Male)
{
kSoldier.kAppearance.nmVoice = 'SkirmisherMaleVoice1_Localized';
}
else
{
kSoldier.kAppearance.nmVoice = 'SkirmisherFemaleVoice1_Localized';
}
}
}
}
Loading

0 comments on commit d8b0784

Please sign in to comment.