Skip to content

Commit

Permalink
Implement Issue X2CommunityCore#783 - add a DLC hook to modify genera…
Browse files Browse the repository at this point in the history
…ted unit appearance, refactored faction hero XGCharGens.
  • Loading branch information
Iridar51 committed May 28, 2021
1 parent 783d1e9 commit b9d2867
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -813,3 +813,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
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,34 @@ function X2CharacterTemplate SetCharacterTemplate(name CharacterTemplateName, na
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;

kSoldier = super.CreateTSoldier('ReaperSoldier', eForceGender, nmCountry, iRace, ArmorName);
SetCountry('Country_Reaper');
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);

return kSoldier;
}
// End issue #783

function SetRace(int iRace)
{
Expand Down Expand Up @@ -115,4 +130,4 @@ function SetVoice(name CharacterTemplateName, name CountryName)
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,34 @@ function X2CharacterTemplate SetCharacterTemplate(name CharacterTemplateName, na
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;

kSoldier = super.CreateTSoldier('SkirmisherSoldier', eForceGender, nmCountry, iRace, ArmorName);
SetCountry('Country_Skirmisher');
GenerateName(kSoldier.kAppearance.iGender, kSoldier.nmCountry, kSoldier.strFirstName, kSoldier.strLastName, kSoldier.kAppearance.iRace);
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);

return kSoldier;
}
// End issue #783

function SetAccessories(X2SimpleBodyPartFilter BodyPartFilter, name CharacterTemplateName)
{
Expand Down Expand Up @@ -90,4 +104,4 @@ function SetVoice(name CharacterTemplateName, name CountryName)
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,34 @@ function X2CharacterTemplate SetCharacterTemplate(name CharacterTemplateName, na
return class'X2CharacterTemplateManager'.static.GetCharacterTemplateManager().FindCharacterTemplate(CharacterTemplateName);
}

// 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('TemplarSoldier', eForceGender, nmCountry, iRace, ArmorName);
return kSoldier;
}

function SetCountry(name nmCountry)
{
kSoldier.nmCountry = 'Country_Templar';
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;

kSoldier = super.CreateTSoldier('TemplarSoldier', eForceGender, nmCountry, iRace, ArmorName);
SetCountry('Country_Templar');
super.GenerateName( kSoldier.kAppearance.iGender, kSoldier.nmCountry, kSoldier.strFirstName, kSoldier.strLastName, kSoldier.kAppearance.iRace );

ClassMgr = class'X2SoldierClassTemplateManager'.static.GetSoldierClassTemplateManager();
ClassTemplate = ClassMgr.FindSoldierClassTemplate('Templar');
kSoldier.strNickName = GenerateNickname(ClassTemplate, kSoldier.kAppearance.iGender);

return kSoldier;
}
// End issue #783

function SetRace(int iRace)
{
Expand Down Expand Up @@ -99,4 +114,4 @@ function SetVoice(name CharacterTemplateName, name CountryName)
function SetAttitude()
{
kSoldier.kAppearance.iAttitude = 0; // Should correspond with Personality_ByTheBook
}
}

0 comments on commit b9d2867

Please sign in to comment.