From e79684fbf9406cf4549ed682cb9861d9720945fc Mon Sep 17 00:00:00 2001 From: ToallaNova-O2D Date: Sat, 30 May 2026 15:46:24 -0400 Subject: [PATCH 1/6] Update XComLW_Overhaul.ini --- LongWarOfTheChosen/Config/XComLW_Overhaul.ini | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/LongWarOfTheChosen/Config/XComLW_Overhaul.ini b/LongWarOfTheChosen/Config/XComLW_Overhaul.ini index 864321e57..9863234f9 100644 --- a/LongWarOfTheChosen/Config/XComLW_Overhaul.ini +++ b/LongWarOfTheChosen/Config/XComLW_Overhaul.ini @@ -2869,3 +2869,68 @@ bLeftSquadButton = false +AlienRulerTags = "Ruler_ViperKingActive" +AlienRulerTags = "Ruler_BerserkerQueenActive" +AlienRulerTags = "Ruler_ArchonKingActive" + +; ============================================================================= +; MODULAR REBEL LOADOUT SYSTEM ConfigDriven Weapon & Utility Setup +; ============================================================================= +;; Weapon Categories: Each entry defines a weapon type with 4 tech tiers. +; When rebels spawn, the system rolls a random category, then picks the +; weapon matching the highest researched tier. +;; Utility Archetypes: Define slot combinations (offensive/defensive/utility). +; Each archetype names two pools; one item is drawn from each pool at random. +;; Item Pools: Categorized lists of items. Rebels draw from these based on +; their rolled archetype. +;; REBEL_ALWAYS_EQUIP: Items that every rebel soldier always receives. +;; If these arrays are empty, the LEGACY loadout system is used automatically +; (loadout string lookup via "RebelSoldier" + tier + weapon type). +; ============================================================================= + +; Weapon categories (one random category per rebel) ++REBEL_WEAPON_CATEGORIES=(CategoryName="Rifle", Tier1Weapon="AssaultRifle_CV", Tier2Weapon="AssaultRifle_LS", Tier3Weapon="AssaultRifle_MG", Tier4Weapon="AssaultRifle_CG") ++REBEL_WEAPON_CATEGORIES=(CategoryName="SMG", Tier1Weapon="SMG_CV", Tier2Weapon="SMG_LS", Tier3Weapon="SMG_MG", Tier4Weapon="SMG_CG") ++REBEL_WEAPON_CATEGORIES=(CategoryName="Shotgun", Tier1Weapon="Shotgun_CV", Tier2Weapon="Shotgun_LS", Tier3Weapon="Shotgun_MG", Tier4Weapon="Shotgun_CG") +; New categories can be added, but need to check whether the assets can load them correctly, by your mod. +;+REBEL_WEAPON_CATEGORIES=(CategoryName="Cannon", Tier1Weapon="Cannon_CV", Tier2Weapon="Cannon_LS", Tier3Weapon="Cannon_MG", Tier4Weapon="Cannon_CG") +;+REBEL_WEAPON_CATEGORIES=(CategoryName="Sniper", Tier1Weapon="SniperRifle_CV", Tier2Weapon="SniperRifle_LS", Tier3Weapon="SniperRifle_MG", Tier4Weapon="SniperRifle_CG") + +; Offensive items pool, can be increased with personalized items, need to check whether they can use them ++REBEL_OFFENSIVE_ITEMS="FragGrenade" +;+REBEL_OFFENSIVE_ITEMS="AlienGrenade" +;+REBEL_OFFENSIVE_ITEMS="Firebomb" +;+REBEL_OFFENSIVE_ITEMS="FirebombMK2" +;+REBEL_OFFENSIVE_ITEMS="GasGrenade" +;+REBEL_OFFENSIVE_ITEMS="GasGrenadeMk2" +;+REBEL_OFFENSIVE_ITEMS="AcidGrenade" +;+REBEL_OFFENSIVE_ITEMS="AcidGrenadeMk2" +;+REBEL_OFFENSIVE_ITEMS="EMPGrenade" +;+REBEL_OFFENSIVE_ITEMS="EMPGrenadeMk2" +;+REBEL_OFFENSIVE_ITEMS="ShapedCharge" +;+REBEL_OFFENSIVE_ITEMS="Skulljack" +;+REBEL_OFFENSIVE_ITEMS="Neurowhip" +;+REBEL_OFFENSIVE_ITEMS="ProximityMine" + +; Defensive items pool, can be increased with personalized items, need to check whether they can use them ++REBEL_DEFENSIVE_ITEMS="FlashbangGrenade" ++REBEL_DEFENSIVE_ITEMS="SmokeGrenadeMk2" +;+REBEL_DEFENSIVE_ITEMS="BattleScanner" +;+REBEL_DEFENSIVE_ITEMS="MimicBeacon" +;+REBEL_DEFENSIVE_ITEMS="SmokeGrenade" + +; Utility items pool, can be increased with personalized items, need to check whether they can use them ++REBEL_UTILITY_ITEMS="Medikit" ++REBEL_UTILITY_ITEMS="NanoMedikit" +;+REBEL_UTILITY_ITEMS="MindShield" +;+REBEL_UTILITY_ITEMS="CombatStims" +;+REBEL_UTILITY_ITEMS="SustainingSphere" +;+REBEL_UTILITY_ITEMS="RefractionField" + +; Items every rebel always gets ++REBEL_ALWAYS_EQUIP="EvacFlare" + +; Utility archetypes (one random archetype per rebel) +; Each archetype names two slot pools. One item is drawn from each pool. New archetypes are not available for now, but this array covers them all IMHO ++REBEL_UTILITY_ARCHETYPES=(ArchetypeName="OffDef", Slot1Pool="Offensive", Slot2Pool="Defensive") ++REBEL_UTILITY_ARCHETYPES=(ArchetypeName="OffOff", Slot1Pool="Offensive", Slot2Pool="Offensive") ++REBEL_UTILITY_ARCHETYPES=(ArchetypeName="DefDef", Slot1Pool="Defensive", Slot2Pool="Defensive") ++REBEL_UTILITY_ARCHETYPES=(ArchetypeName="OffUtil", Slot1Pool="Offensive", Slot2Pool="Utility") ++REBEL_UTILITY_ARCHETYPES=(ArchetypeName="DefUtil", Slot1Pool="Defensive", Slot2Pool="Utility") From 346076c02e5bb246038657a82b89239f34b452ca Mon Sep 17 00:00:00 2001 From: ToallaNova-O2D Date: Sat, 30 May 2026 15:54:43 -0400 Subject: [PATCH 2/6] Update Utilities_LW.uc --- .../Src/LW_Overhaul/Classes/Utilities_LW.uc | 368 ++++++++++++++---- 1 file changed, 286 insertions(+), 82 deletions(-) diff --git a/LongWarOfTheChosen/Src/LW_Overhaul/Classes/Utilities_LW.uc b/LongWarOfTheChosen/Src/LW_Overhaul/Classes/Utilities_LW.uc index dc8186f55..4b3ad681e 100644 --- a/LongWarOfTheChosen/Src/LW_Overhaul/Classes/Utilities_LW.uc +++ b/LongWarOfTheChosen/Src/LW_Overhaul/Classes/Utilities_LW.uc @@ -19,6 +19,33 @@ struct MissionEnemyCountOffset var int OffsetAmount; }; +// === Modular Rebel Loadout System structs === +// Defines a weapon category with template names for each tech tier. +struct RebelWeaponCategoryEntry +{ + var name CategoryName; // e.g. 'Rifle', 'SMG', 'Shotgun' + var name Tier1Weapon; // e.g. 'AssaultRifle_CV' + var name Tier2Weapon; // e.g. 'AssaultRifle_LS' + var name Tier3Weapon; // e.g. 'AssaultRifle_MG' + var name Tier4Weapon; // e.g. 'AssaultRifle_CG' +}; + +// Defines a utility slot combination archetype. +struct RebelUtilityArchetype +{ + var name ArchetypeName; // e.g. 'OffDef', 'OffOff', 'DefDef' + var name Slot1Pool; // 'Offensive', 'Defensive', or 'Utility' + var name Slot2Pool; // 'Offensive', 'Defensive', or 'Utility' +}; + +// === Modular Rebel Loadout System config === +var config array REBEL_WEAPON_CATEGORIES; +var config array REBEL_OFFENSIVE_ITEMS; +var config array REBEL_DEFENSIVE_ITEMS; +var config array REBEL_UTILITY_ITEMS; +var config array REBEL_ALWAYS_EQUIP; +var config array REBEL_UTILITY_ARCHETYPES; + var config array REFLEX_ACTION_CHANCE_YELLOW; var config array REFLEX_ACTION_CHANCE_GREEN; var config float REFLEX_ACTION_CHANCE_REDUCTION; @@ -457,15 +484,83 @@ static function array GetCompleteDefaultLoadout(XComGameSta return completedefaultloadout; } -// Create a soldier proxy for the given rebel, set them as on-mission, and give them a loadout. All done within the -// provided game state. -function static XComGameState_Unit CreateRebelSoldier(StateObjectReference RebelRef, StateObjectReference OutpostRef, XComGameState NewGameState, optional name Loadout) +// ============================================================================= +// MODULAR REBEL LOADOUT SYSTEM Helper Functions +// ============================================================================= + +/// Returns the appropriate item pool array for the given pool name. +/// Valid pool names: 'Offensive', 'Defensive', 'Utility' +static function array GetUtilityPool(name PoolName) { - local XComGameState_LWOutpost Outpost; - local XComGameState_Unit Proxy; - local name TemplateName; - local int LaserChance, MagChance, CoilChance, iRand; - local string LoadoutStr; + local array EmptyArray; + + if (PoolName == 'Offensive') + return default.REBEL_OFFENSIVE_ITEMS; + else if (PoolName == 'Defensive') + return default.REBEL_DEFENSIVE_ITEMS; + else if (PoolName == 'Utility') + return default.REBEL_UTILITY_ITEMS; + + `Redscreen("Utilities_LW.GetUtilityPool: Unknown pool name:" @ PoolName); + return EmptyArray; +} + +/// Returns the weapon template name for the given category and tier. +static function name GetWeaponForTier(RebelWeaponCategoryEntry Category, int Tier) +{ + `LWTrace("Rebel Weapon Tier:" @ Tier); + switch(Tier) + { + case 4: return Category.Tier4Weapon; + case 3: return Category.Tier3Weapon; + case 2: return Category.Tier2Weapon; + default: return Category.Tier1Weapon; + } +} + +/// Creates and equips an item on a unit by template name. +/// Handles weapon appearance transfer for primary/secondary weapons. +static function EquipItemOnUnit(XComGameState_Unit Unit, name ItemTemplateName, XComGameState ModifyGameState) +{ + local X2ItemTemplateManager ItemMgr; + local X2EquipmentTemplate EquipTemplate; + local XComGameState_Item NewItem; + + if (ItemTemplateName == '') + { + `Redscreen("Utilities_LW.EquipItemOnUnit: Empty item template name!"); + return; + } + + ItemMgr = class'X2ItemTemplateManager'.static.GetItemTemplateManager(); + EquipTemplate = X2EquipmentTemplate(ItemMgr.FindItemTemplate(ItemTemplateName)); + + if (EquipTemplate == none) + { + `Redscreen("Utilities_LW.EquipItemOnUnit: Template not found:" @ ItemTemplateName); + return; + } + + NewItem = EquipTemplate.CreateInstanceFromTemplate(ModifyGameState); + + // Transfer weapon appearance from the soldier (same as original ApplyLoadout) + if (EquipTemplate.InventorySlot == eInvSlot_PrimaryWeapon || EquipTemplate.InventorySlot == eInvSlot_SecondaryWeapon) + { + NewItem.WeaponAppearance.iWeaponTint = Unit.kAppearance.iWeaponTint; + NewItem.WeaponAppearance.nmWeaponPattern = Unit.kAppearance.nmWeaponPattern; + } + + Unit.AddItemToInventory(NewItem, EquipTemplate.InventorySlot, ModifyGameState); + ModifyGameState.AddStateObject(NewItem); +} + +/// Rolls the weapon tier based on researched techs and rebel level. +/// Returns 1 (conventional), 2 (laser), 3 (magnetic), or 4 (coilgun). +/// Uses configurable tech names via TechResearchedOrHasHQInventoryItem (upstream PR #1959). +static function int RollRebelWeaponTier(XComGameState_LWOutpost Outpost, StateObjectReference RebelRef) +{ + local int LaserChance, MagChance, CoilChance; + local int RebelLevel, iRand; local bool AdvancedLasersResearched; local bool MagnetizedWeaponsResearched; @@ -477,7 +572,146 @@ function static XComGameState_Unit CreateRebelSoldier(StateObjectReference Rebel local bool HeavyPlasmaResearched; local bool PlasmaSniperResearched; + RebelLevel = Outpost.GetRebelLevel(RebelRef); + + // Cache tech research status using configurable tech names (PR #1959 pattern) + + AdvancedLasersResearched = class'X2DownloadableContentInfo_LongWarOfTheChosen'.static.TechResearchedOrHasHQInventoryItem(class'X2DownloadableContentInfo_LongWarOfTheChosen'.default.AdvancedLasersTechName); + MagnetizedWeaponsResearched = class'X2DownloadableContentInfo_LongWarOfTheChosen'.static.TechResearchedOrHasHQInventoryItem(class'X2DownloadableContentInfo_LongWarOfTheChosen'.default.MagnetizedWeaponsTechName); + GaussWeaponsResearched = class'X2DownloadableContentInfo_LongWarOfTheChosen'.static.TechResearchedOrHasHQInventoryItem(class'X2DownloadableContentInfo_LongWarOfTheChosen'.default.GaussWeaponsTechName); + CoilgunsResearched = class'X2DownloadableContentInfo_LongWarOfTheChosen'.static.TechResearchedOrHasHQInventoryItem(class'X2DownloadableContentInfo_LongWarOfTheChosen'.default.CoilgunsTechName); + AdvancedCoilgunsResearched = class'X2DownloadableContentInfo_LongWarOfTheChosen'.static.TechResearchedOrHasHQInventoryItem(class'X2DownloadableContentInfo_LongWarOfTheChosen'.default.AdvancedCoilgunsTechName); + PlasmaRifleResearched = class'X2DownloadableContentInfo_LongWarOfTheChosen'.static.TechResearchedOrHasHQInventoryItem(class'X2DownloadableContentInfo_LongWarOfTheChosen'.default.PlasmaRifleTechName); + AlloyCannonResearched = class'X2DownloadableContentInfo_LongWarOfTheChosen'.static.TechResearchedOrHasHQInventoryItem(class'X2DownloadableContentInfo_LongWarOfTheChosen'.default.AlloyCannonTechName); + HeavyPlasmaResearched = class'X2DownloadableContentInfo_LongWarOfTheChosen'.static.TechResearchedOrHasHQInventoryItem(class'X2DownloadableContentInfo_LongWarOfTheChosen'.default.HeavyPlasmaTechName); + PlasmaSniperResearched = class'X2DownloadableContentInfo_LongWarOfTheChosen'.static.TechResearchedOrHasHQInventoryItem(class'X2DownloadableContentInfo_LongWarOfTheChosen'.default.PlasmaSniperTechName); + + `LWTrace("AdvancedLasersResearched:" @ AdvancedLasersResearched); + `LWTrace("MagnetizedWeaponsResearched:" @ MagnetizedWeaponsResearched); + `LWTrace("GaussWeaponsResearched:" @ GaussWeaponsResearched); + `LWTrace("CoilgunsResearched:" @ CoilgunsResearched); + `LWTrace("AdvancedCoilgunsResearched: " @ AdvancedCoilgunsResearched); + `LWTrace("PlasmaRifleResearched:" @ PlasmaRifleResearched); + `LWTrace("AlloyCannonResearched:" @ AlloyCannonResearched); + `LWTrace("HeavyPlasmaResearched:" @ HeavyPlasmaResearched); + `LWTrace("PlasmaSniperResearched:" @ PlasmaSniperResearched); + + LaserChance = 0; + MagChance = 0; + CoilChance = 0; + + // Laser tier chances + if (MagnetizedWeaponsResearched && AdvancedLasersResearched) + { + LaserChance += (20 + 10 * (RebelLevel + 1)); // 30/40/50 + } + if (GaussWeaponsResearched && AdvancedLasersResearched) + { + LaserChance += (20 + 10 * (RebelLevel + 1)); // 60/80/100 + } + + // Magnetic tier chances + if (CoilgunsResearched && GaussWeaponsResearched) + { + if (AdvancedLasersResearched) + { + LaserChance = 100; + } + MagChance += (20 + 10 * (RebelLevel + 1)); // 30/40/50 + } + if (AdvancedCoilgunsResearched && GaussWeaponsResearched) + { + MagChance += (20 + 10 * (RebelLevel + 1)); // 60/80/100 + } + + // Coilgun tier chances + if (PlasmaRifleResearched) + { + if (GaussWeaponsResearched) + { + MagChance = 100; + } + CoilChance += (20 + 10 * (RebelLevel + 1)); // 30/40/50 + } + // All plasma weapon related research + if (HeavyPlasmaResearched && + AlloyCannonResearched && + PlasmaSniperResearched && + AdvancedCoilgunsResearched) + { + CoilChance += (20 + 10 * (RebelLevel + 1)); // 60/80/100 + } + + iRand = `SYNC_RAND_STATIC(100); + + `LWTrace("Coil Chance:" @ CoilChance + @ " Mag Chance:" @ MagChance + @ " Laser Chance:" @ LaserChance + @ " iRand:" @ iRand); + + // unique roll for all tiers + if (iRand > CoilChance) + return 4; + else if (iRand > MagChance) + return 3; + else if (iRand > LaserChance) + return 2; + + return 1; +} + +/// Fallback: Rolls a legacy loadout name for backward compatibility. +/// Used when REBEL_WEAPON_CATEGORIES config is empty. +static function name RollLegacyLoadout(XComGameState_LWOutpost Outpost, StateObjectReference RebelRef) +{ + local string LoadoutStr; + local int WeaponTier, iRand; + + LoadoutStr = "RebelSoldier"; + WeaponTier = RollRebelWeaponTier(Outpost, RebelRef); + + if (WeaponTier >= 4) + LoadoutStr $= "4"; + else if (WeaponTier >= 3) + LoadoutStr $= "3"; + else if (WeaponTier >= 2) + LoadoutStr $= "2"; + + iRand = `SYNC_RAND_STATIC(100); + if (iRand < 20) + LoadoutStr $= "SMG"; + else if (iRand < 40) + LoadoutStr $= "Shotgun"; + + return name(LoadoutStr); +} + +=========================================================== +// CreateRebelSoldier MODULAR VERSION +// ============================================================================= +// Creates a soldier proxy for the given rebel, sets them as onmission, +// and gives them a loadout. +// +// Loadout assignment: +// For custom character templates, uses the template's own DefaultLoadout. +// For default proxies: uses modular system if configured, else legacy. +// +// See: XComLW_Overhaul.ini [LW_Overhaul.Utilities_LW] for config defaults. +// ============================================================================= +function static XComGameState_Unit CreateRebelSoldier(StateObjectReference RebelRef, StateObjectReference OutpostRef, XComGameState NewGameState, optional name Loadout) +{ + local XComGameState_LWOutpost Outpost; + local XComGameState_Unit Proxy; + local Name TemplateName; + local int WeaponTier; + local int CategoryIdx, ArchetypeIdx, ItemIdx; + local RebelWeaponCategoryEntry SelectedCategory; + local RebelUtilityArchetype SelectedArchetype; + local name WeaponTemplate; + local array Pool; + Outpost = XComGameState_LWOutpost(`XCOMHISTORY.GetGameStateForObjectID(OutpostRef.ObjectID)); + switch(Outpost.GetRebelLevel(RebelRef)) { case 0: @@ -490,97 +724,67 @@ function static XComGameState_Unit CreateRebelSoldier(StateObjectReference Rebel TemplateName = 'RebelSoldierProxyM3'; break; default: - `Redscreen("CreateRebelSoldier: Unsupported rebel level " $ Outpost.GetRebelLevel(RebelRef)); + `Redscreen("CreateRebelSoldier: Unsupported rebel level" @ Outpost.GetRebelLevel(RebelRef)); TemplateName = 'RebelSoldierProxy'; } Proxy = CreateRebelProxy(RebelRef, OutpostRef, TemplateName, true, NewGameState); Proxy.SetSoldierClassTemplate('LWS_RebelSoldier'); - LaserChance = 0; - MagChance = 0; - CoilChance = 0; - - if (Loadout == '') + // === LOADOUT ASSIGNMENT === + if (Loadout != '') { - AdvancedLasersResearched = class'X2DownloadableContentInfo_LongWarOfTheChosen'.static.TechResearchedOrHasHQInventoryItem(class'X2DownloadableContentInfo_LongWarOfTheChosen'.default.AdvancedLasersTechName); - MagnetizedWeaponsResearched = class'X2DownloadableContentInfo_LongWarOfTheChosen'.static.TechResearchedOrHasHQInventoryItem(class'X2DownloadableContentInfo_LongWarOfTheChosen'.default.MagnetizedWeaponsTechName); - GaussWeaponsResearched = class'X2DownloadableContentInfo_LongWarOfTheChosen'.static.TechResearchedOrHasHQInventoryItem(class'X2DownloadableContentInfo_LongWarOfTheChosen'.default.GaussWeaponsTechName); - CoilgunsResearched = class'X2DownloadableContentInfo_LongWarOfTheChosen'.static.TechResearchedOrHasHQInventoryItem(class'X2DownloadableContentInfo_LongWarOfTheChosen'.default.CoilgunsTechName); - AdvancedCoilgunsResearched = class'X2DownloadableContentInfo_LongWarOfTheChosen'.static.TechResearchedOrHasHQInventoryItem(class'X2DownloadableContentInfo_LongWarOfTheChosen'.default.AdvancedCoilgunsTechName); - PlasmaRifleResearched = class'X2DownloadableContentInfo_LongWarOfTheChosen'.static.TechResearchedOrHasHQInventoryItem(class'X2DownloadableContentInfo_LongWarOfTheChosen'.default.PlasmaRifleTechName); - AlloyCannonResearched = class'X2DownloadableContentInfo_LongWarOfTheChosen'.static.TechResearchedOrHasHQInventoryItem(class'X2DownloadableContentInfo_LongWarOfTheChosen'.default.AlloyCannonTechName); - HeavyPlasmaResearched = class'X2DownloadableContentInfo_LongWarOfTheChosen'.static.TechResearchedOrHasHQInventoryItem(class'X2DownloadableContentInfo_LongWarOfTheChosen'.default.HeavyPlasmaTechName); - PlasmaSniperResearched = class'X2DownloadableContentInfo_LongWarOfTheChosen'.static.TechResearchedOrHasHQInventoryItem(class'X2DownloadableContentInfo_LongWarOfTheChosen'.default.PlasmaSniperTechName); + ApplyLoadout(Proxy, Loadout, NewGameState); + `LWTrace("Rebel Loadout (forced):" @ Loadout); + } + else if (default.REBEL_WEAPON_CATEGORIES.Length > 0 && default.REBEL_UTILITY_ARCHETYPES.Length > 0) + { + // NEW MODULAR LOADOUT SYSTEM + WeaponTier = RollRebelWeaponTier(Outpost, RebelRef); - LoadoutStr = "RebelSoldier"; - if (MagnetizedWeaponsResearched && AdvancedLasersResearched) - { - LaserChance += (20 + 10 * (Outpost.GetRebelLevel(RebelRef) + 1)); // 30/40/50 - } - if (GaussWeaponsResearched && AdvancedLasersResearched) - { - LaserChance += (20 + 10 * (Outpost.GetRebelLevel(RebelRef) + 1)); // 60/80/100 - } - if (CoilgunsResearched && GaussWeaponsResearched) - { - if (AdvancedLasersResearched) - { - LaserChance = 100; - } - MagChance += (20 + 10 * (Outpost.GetRebelLevel(RebelRef) + 1)); // 30/40/50 - } - if (AdvancedCoilgunsResearched && GaussWeaponsResearched) - { - MagChance += (20 + 10 * (Outpost.GetRebelLevel(RebelRef) + 1)); // 60/80/100 - } - if (PlasmaRifleResearched) - { - if (GaussWeaponsResearched) - { - MagChance = 100; - } - CoilChance += (20 + 10 * (Outpost.GetRebelLevel(RebelRef) + 1)); // 30/40/50 - } - // All plasma weapon related research - if (HeavyPlasmaResearched && - AlloyCannonResearched && - PlasmaSniperResearched && - AdvancedCoilgunsResearched) - { - CoilChance += (20 + 10 * (Outpost.GetRebelLevel(RebelRef) + 1)); // 60/80/100 - } + CategoryIdx = `SYNC_RAND_STATIC(default.REBEL_WEAPON_CATEGORIES.Length); + SelectedCategory = default.REBEL_WEAPON_CATEGORIES[CategoryIdx]; - if (`SYNC_RAND_STATIC(100) < CoilChance) - { - LoadoutStr $= "4"; - } - else if (`SYNC_RAND_STATIC(100) < MagChance) + WeaponTemplate = GetWeaponForTier(SelectedCategory, WeaponTier); + `LWTrace("Rebel Weapon: " @WeaponTemplate); + EquipItemOnUnit(Proxy, WeaponTemplate, NewGameState); + + ArchetypeIdx = `SYNC_RAND_STATIC(default.REBEL_UTILITY_ARCHETYPES.Length); + SelectedArchetype = default.REBEL_UTILITY_ARCHETYPES[ArchetypeIdx]; + + Pool = GetUtilityPool(SelectedArchetype.Slot1Pool); + if (Pool.Length > 0) { - LoadoutStr $= "3"; + ItemIdx = `SYNC_RAND_STATIC(Pool.Length); + `LWTrace("Rebel Slot 1:" @ Pool[ItemIdx]); + EquipItemOnUnit(Proxy, Pool[ItemIdx], NewGameState); } - else - { - if (`SYNC_RAND_STATIC(100) < LaserChance) - { - LoadoutStr $= "2"; - } - } - iRand = `SYNC_RAND_STATIC(100); - if (iRand < 20) + + Pool = GetUtilityPool(SelectedArchetype.Slot2Pool); + if (Pool.Length > 0) { - LoadoutStr $= "SMG"; + ItemIdx = `SYNC_RAND_STATIC(Pool.Length); + `LWTrace("Rebel Slot 2:" @ Pool[ItemIdx]); + EquipItemOnUnit(Proxy, Pool[ItemIdx], NewGameState); } - else if (iRand < 40) + + for (ItemIdx = 0; ItemIdx < default.REBEL_ALWAYS_EQUIP.Length; ItemIdx++) { - LoadoutStr $= "Shotgun"; + EquipItemOnUnit(Proxy, default.REBEL_ALWAYS_EQUIP[ItemIdx], NewGameState); } - //`LWTRACE ("Rebel Loadout" @ LoadoutStr); - Loadout = name(LoadOutStr); + `LWTrace("Rebel Loadout:" @ SelectedCategory.CategoryName + @ " Weapon Tier:" $ WeaponTier + @ " Weapon Template:" @ WeaponTemplate + @ " Archetype:" @ SelectedArchetype.ArchetypeName); + } + else + { + // LEGACY FALLBACK + `LWTrace("Rebel Loadout: Legacy fallback (config arrays empty)"); + Loadout = RollLegacyLoadout(Outpost, RebelRef); + ApplyLoadout(Proxy, Loadout, NewGameState); } - - ApplyLoadout(Proxy, Loadout, NewGameState); return Proxy; } From 268e79470064b8f16e9956daaba32789757481be Mon Sep 17 00:00:00 2001 From: ToallaNova-O2D Date: Sat, 30 May 2026 16:25:08 -0400 Subject: [PATCH 3/6] Update Utilities_LW.uc Fixed an uncommented line (689) --- LongWarOfTheChosen/Src/LW_Overhaul/Classes/Utilities_LW.uc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LongWarOfTheChosen/Src/LW_Overhaul/Classes/Utilities_LW.uc b/LongWarOfTheChosen/Src/LW_Overhaul/Classes/Utilities_LW.uc index 4b3ad681e..864862a69 100644 --- a/LongWarOfTheChosen/Src/LW_Overhaul/Classes/Utilities_LW.uc +++ b/LongWarOfTheChosen/Src/LW_Overhaul/Classes/Utilities_LW.uc @@ -686,7 +686,7 @@ static function name RollLegacyLoadout(XComGameState_LWOutpost Outpost, StateObj return name(LoadoutStr); } -=========================================================== +// =========================================================== // CreateRebelSoldier MODULAR VERSION // ============================================================================= // Creates a soldier proxy for the given rebel, sets them as onmission, From 23bf4a3922032a3d38e23090ee06f6c0b7a5eead Mon Sep 17 00:00:00 2001 From: ToallaNova-O2D Date: Sat, 30 May 2026 17:33:57 -0400 Subject: [PATCH 4/6] Update XComLW_Overhaul.ini Fixed wrong position. --- LongWarOfTheChosen/Config/XComLW_Overhaul.ini | 130 +++++++++--------- 1 file changed, 65 insertions(+), 65 deletions(-) diff --git a/LongWarOfTheChosen/Config/XComLW_Overhaul.ini b/LongWarOfTheChosen/Config/XComLW_Overhaul.ini index 9863234f9..58bc019ac 100644 --- a/LongWarOfTheChosen/Config/XComLW_Overhaul.ini +++ b/LongWarOfTheChosen/Config/XComLW_Overhaul.ini @@ -2751,6 +2751,71 @@ LOW_INFILTRATION_MODIFIER_ON_REFLEX_ACTIONS[1]=0.1 LOW_INFILTRATION_MODIFIER_ON_REFLEX_ACTIONS[2]=0.5 LOW_INFILTRATION_MODIFIER_ON_REFLEX_ACTIONS[3]=1.0 +; ============================================================================= +; MODULAR REBEL LOADOUT SYSTEM ConfigDriven Weapon & Utility Setup +; ============================================================================= +;; Weapon Categories: Each entry defines a weapon type with 4 tech tiers. +; When rebels spawn, the system rolls a random category, then picks the +; weapon matching the highest researched tier. +;; Utility Archetypes: Define slot combinations (offensive/defensive/utility). +; Each archetype names two pools; one item is drawn from each pool at random. +;; Item Pools: Categorized lists of items. Rebels draw from these based on +; their rolled archetype. +;; REBEL_ALWAYS_EQUIP: Items that every rebel soldier always receives. +;; If these arrays are empty, the LEGACY loadout system is used automatically +; (loadout string lookup via "RebelSoldier" + tier + weapon type). +; ============================================================================= + +; Weapon categories (one random category per rebel) ++REBEL_WEAPON_CATEGORIES=(CategoryName="Rifle", Tier1Weapon="AssaultRifle_CV", Tier2Weapon="AssaultRifle_LS", Tier3Weapon="AssaultRifle_MG", Tier4Weapon="AssaultRifle_CG") ++REBEL_WEAPON_CATEGORIES=(CategoryName="SMG", Tier1Weapon="SMG_CV", Tier2Weapon="SMG_LS", Tier3Weapon="SMG_MG", Tier4Weapon="SMG_CG") ++REBEL_WEAPON_CATEGORIES=(CategoryName="Shotgun", Tier1Weapon="Shotgun_CV", Tier2Weapon="Shotgun_LS", Tier3Weapon="Shotgun_MG", Tier4Weapon="Shotgun_CG") +; New categories can be added, but need to check whether the assets can load them correctly, by your mod. +;+REBEL_WEAPON_CATEGORIES=(CategoryName="Cannon", Tier1Weapon="Cannon_CV", Tier2Weapon="Cannon_LS", Tier3Weapon="Cannon_MG", Tier4Weapon="Cannon_CG") +;+REBEL_WEAPON_CATEGORIES=(CategoryName="Sniper", Tier1Weapon="SniperRifle_CV", Tier2Weapon="SniperRifle_LS", Tier3Weapon="SniperRifle_MG", Tier4Weapon="SniperRifle_CG") + +; Offensive items pool, can be increased with personalized items, need to check whether they can use them ++REBEL_OFFENSIVE_ITEMS="FragGrenade" +;+REBEL_OFFENSIVE_ITEMS="AlienGrenade" +;+REBEL_OFFENSIVE_ITEMS="Firebomb" +;+REBEL_OFFENSIVE_ITEMS="FirebombMK2" +;+REBEL_OFFENSIVE_ITEMS="GasGrenade" +;+REBEL_OFFENSIVE_ITEMS="GasGrenadeMk2" +;+REBEL_OFFENSIVE_ITEMS="AcidGrenade" +;+REBEL_OFFENSIVE_ITEMS="AcidGrenadeMk2" +;+REBEL_OFFENSIVE_ITEMS="EMPGrenade" +;+REBEL_OFFENSIVE_ITEMS="EMPGrenadeMk2" +;+REBEL_OFFENSIVE_ITEMS="ShapedCharge" +;+REBEL_OFFENSIVE_ITEMS="Skulljack" +;+REBEL_OFFENSIVE_ITEMS="Neurowhip" +;+REBEL_OFFENSIVE_ITEMS="ProximityMine" + +; Defensive items pool, can be increased with personalized items, need to check whether they can use them ++REBEL_DEFENSIVE_ITEMS="FlashbangGrenade" ++REBEL_DEFENSIVE_ITEMS="SmokeGrenadeMk2" +;+REBEL_DEFENSIVE_ITEMS="BattleScanner" +;+REBEL_DEFENSIVE_ITEMS="MimicBeacon" +;+REBEL_DEFENSIVE_ITEMS="SmokeGrenade" + +; Utility items pool, can be increased with personalized items, need to check whether they can use them ++REBEL_UTILITY_ITEMS="Medikit" ++REBEL_UTILITY_ITEMS="NanoMedikit" +;+REBEL_UTILITY_ITEMS="MindShield" +;+REBEL_UTILITY_ITEMS="CombatStims" +;+REBEL_UTILITY_ITEMS="SustainingSphere" +;+REBEL_UTILITY_ITEMS="RefractionField" + +; Items every rebel always gets ++REBEL_ALWAYS_EQUIP="EvacFlare" + +; Utility archetypes (one random archetype per rebel) +; Each archetype names two slot pools. One item is drawn from each pool. New archetypes are not available for now, but this array covers them all IMHO ++REBEL_UTILITY_ARCHETYPES=(ArchetypeName="OffDef", Slot1Pool="Offensive", Slot2Pool="Defensive") ++REBEL_UTILITY_ARCHETYPES=(ArchetypeName="OffOff", Slot1Pool="Offensive", Slot2Pool="Offensive") ++REBEL_UTILITY_ARCHETYPES=(ArchetypeName="DefDef", Slot1Pool="Defensive", Slot2Pool="Defensive") ++REBEL_UTILITY_ARCHETYPES=(ArchetypeName="OffUtil", Slot1Pool="Offensive", Slot2Pool="Utility") ++REBEL_UTILITY_ARCHETYPES=(ArchetypeName="DefUtil", Slot1Pool="Defensive", Slot2Pool="Utility") + [LW_Overhaul.SeqAct_ResetCivilians] +CharacterTemplatesToSwap=Rebel +CharacterTemplatesToSwap=Soldier_VIP @@ -2869,68 +2934,3 @@ bLeftSquadButton = false +AlienRulerTags = "Ruler_ViperKingActive" +AlienRulerTags = "Ruler_BerserkerQueenActive" +AlienRulerTags = "Ruler_ArchonKingActive" - -; ============================================================================= -; MODULAR REBEL LOADOUT SYSTEM ConfigDriven Weapon & Utility Setup -; ============================================================================= -;; Weapon Categories: Each entry defines a weapon type with 4 tech tiers. -; When rebels spawn, the system rolls a random category, then picks the -; weapon matching the highest researched tier. -;; Utility Archetypes: Define slot combinations (offensive/defensive/utility). -; Each archetype names two pools; one item is drawn from each pool at random. -;; Item Pools: Categorized lists of items. Rebels draw from these based on -; their rolled archetype. -;; REBEL_ALWAYS_EQUIP: Items that every rebel soldier always receives. -;; If these arrays are empty, the LEGACY loadout system is used automatically -; (loadout string lookup via "RebelSoldier" + tier + weapon type). -; ============================================================================= - -; Weapon categories (one random category per rebel) -+REBEL_WEAPON_CATEGORIES=(CategoryName="Rifle", Tier1Weapon="AssaultRifle_CV", Tier2Weapon="AssaultRifle_LS", Tier3Weapon="AssaultRifle_MG", Tier4Weapon="AssaultRifle_CG") -+REBEL_WEAPON_CATEGORIES=(CategoryName="SMG", Tier1Weapon="SMG_CV", Tier2Weapon="SMG_LS", Tier3Weapon="SMG_MG", Tier4Weapon="SMG_CG") -+REBEL_WEAPON_CATEGORIES=(CategoryName="Shotgun", Tier1Weapon="Shotgun_CV", Tier2Weapon="Shotgun_LS", Tier3Weapon="Shotgun_MG", Tier4Weapon="Shotgun_CG") -; New categories can be added, but need to check whether the assets can load them correctly, by your mod. -;+REBEL_WEAPON_CATEGORIES=(CategoryName="Cannon", Tier1Weapon="Cannon_CV", Tier2Weapon="Cannon_LS", Tier3Weapon="Cannon_MG", Tier4Weapon="Cannon_CG") -;+REBEL_WEAPON_CATEGORIES=(CategoryName="Sniper", Tier1Weapon="SniperRifle_CV", Tier2Weapon="SniperRifle_LS", Tier3Weapon="SniperRifle_MG", Tier4Weapon="SniperRifle_CG") - -; Offensive items pool, can be increased with personalized items, need to check whether they can use them -+REBEL_OFFENSIVE_ITEMS="FragGrenade" -;+REBEL_OFFENSIVE_ITEMS="AlienGrenade" -;+REBEL_OFFENSIVE_ITEMS="Firebomb" -;+REBEL_OFFENSIVE_ITEMS="FirebombMK2" -;+REBEL_OFFENSIVE_ITEMS="GasGrenade" -;+REBEL_OFFENSIVE_ITEMS="GasGrenadeMk2" -;+REBEL_OFFENSIVE_ITEMS="AcidGrenade" -;+REBEL_OFFENSIVE_ITEMS="AcidGrenadeMk2" -;+REBEL_OFFENSIVE_ITEMS="EMPGrenade" -;+REBEL_OFFENSIVE_ITEMS="EMPGrenadeMk2" -;+REBEL_OFFENSIVE_ITEMS="ShapedCharge" -;+REBEL_OFFENSIVE_ITEMS="Skulljack" -;+REBEL_OFFENSIVE_ITEMS="Neurowhip" -;+REBEL_OFFENSIVE_ITEMS="ProximityMine" - -; Defensive items pool, can be increased with personalized items, need to check whether they can use them -+REBEL_DEFENSIVE_ITEMS="FlashbangGrenade" -+REBEL_DEFENSIVE_ITEMS="SmokeGrenadeMk2" -;+REBEL_DEFENSIVE_ITEMS="BattleScanner" -;+REBEL_DEFENSIVE_ITEMS="MimicBeacon" -;+REBEL_DEFENSIVE_ITEMS="SmokeGrenade" - -; Utility items pool, can be increased with personalized items, need to check whether they can use them -+REBEL_UTILITY_ITEMS="Medikit" -+REBEL_UTILITY_ITEMS="NanoMedikit" -;+REBEL_UTILITY_ITEMS="MindShield" -;+REBEL_UTILITY_ITEMS="CombatStims" -;+REBEL_UTILITY_ITEMS="SustainingSphere" -;+REBEL_UTILITY_ITEMS="RefractionField" - -; Items every rebel always gets -+REBEL_ALWAYS_EQUIP="EvacFlare" - -; Utility archetypes (one random archetype per rebel) -; Each archetype names two slot pools. One item is drawn from each pool. New archetypes are not available for now, but this array covers them all IMHO -+REBEL_UTILITY_ARCHETYPES=(ArchetypeName="OffDef", Slot1Pool="Offensive", Slot2Pool="Defensive") -+REBEL_UTILITY_ARCHETYPES=(ArchetypeName="OffOff", Slot1Pool="Offensive", Slot2Pool="Offensive") -+REBEL_UTILITY_ARCHETYPES=(ArchetypeName="DefDef", Slot1Pool="Defensive", Slot2Pool="Defensive") -+REBEL_UTILITY_ARCHETYPES=(ArchetypeName="OffUtil", Slot1Pool="Offensive", Slot2Pool="Utility") -+REBEL_UTILITY_ARCHETYPES=(ArchetypeName="DefUtil", Slot1Pool="Defensive", Slot2Pool="Utility") From e740565b0a3d0f8dfdf92e1b7d403964572d4cf7 Mon Sep 17 00:00:00 2001 From: ToallaNova-O2D Date: Sat, 30 May 2026 18:53:09 -0400 Subject: [PATCH 5/6] Update Utilities_LW.uc Cleaning LWTraces used for debugging. --- .../Src/LW_Overhaul/Classes/Utilities_LW.uc | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/LongWarOfTheChosen/Src/LW_Overhaul/Classes/Utilities_LW.uc b/LongWarOfTheChosen/Src/LW_Overhaul/Classes/Utilities_LW.uc index 864862a69..3736e0d3b 100644 --- a/LongWarOfTheChosen/Src/LW_Overhaul/Classes/Utilities_LW.uc +++ b/LongWarOfTheChosen/Src/LW_Overhaul/Classes/Utilities_LW.uc @@ -586,16 +586,6 @@ static function int RollRebelWeaponTier(XComGameState_LWOutpost Outpost, StateOb HeavyPlasmaResearched = class'X2DownloadableContentInfo_LongWarOfTheChosen'.static.TechResearchedOrHasHQInventoryItem(class'X2DownloadableContentInfo_LongWarOfTheChosen'.default.HeavyPlasmaTechName); PlasmaSniperResearched = class'X2DownloadableContentInfo_LongWarOfTheChosen'.static.TechResearchedOrHasHQInventoryItem(class'X2DownloadableContentInfo_LongWarOfTheChosen'.default.PlasmaSniperTechName); - `LWTrace("AdvancedLasersResearched:" @ AdvancedLasersResearched); - `LWTrace("MagnetizedWeaponsResearched:" @ MagnetizedWeaponsResearched); - `LWTrace("GaussWeaponsResearched:" @ GaussWeaponsResearched); - `LWTrace("CoilgunsResearched:" @ CoilgunsResearched); - `LWTrace("AdvancedCoilgunsResearched: " @ AdvancedCoilgunsResearched); - `LWTrace("PlasmaRifleResearched:" @ PlasmaRifleResearched); - `LWTrace("AlloyCannonResearched:" @ AlloyCannonResearched); - `LWTrace("HeavyPlasmaResearched:" @ HeavyPlasmaResearched); - `LWTrace("PlasmaSniperResearched:" @ PlasmaSniperResearched); - LaserChance = 0; MagChance = 0; CoilChance = 0; @@ -686,14 +676,13 @@ static function name RollLegacyLoadout(XComGameState_LWOutpost Outpost, StateObj return name(LoadoutStr); } -// =========================================================== +// ============================================================================= // CreateRebelSoldier MODULAR VERSION // ============================================================================= // Creates a soldier proxy for the given rebel, sets them as onmission, // and gives them a loadout. // // Loadout assignment: -// For custom character templates, uses the template's own DefaultLoadout. // For default proxies: uses modular system if configured, else legacy. // // See: XComLW_Overhaul.ini [LW_Overhaul.Utilities_LW] for config defaults. @@ -771,6 +760,7 @@ function static XComGameState_Unit CreateRebelSoldier(StateObjectReference Rebel for (ItemIdx = 0; ItemIdx < default.REBEL_ALWAYS_EQUIP.Length; ItemIdx++) { EquipItemOnUnit(Proxy, default.REBEL_ALWAYS_EQUIP[ItemIdx], NewGameState); + `LWTrace("Always equip:" @ default.REBEL_ALWAYS_EQUIP[ItemIdx]); } `LWTrace("Rebel Loadout:" @ SelectedCategory.CategoryName From 76d2553c19ece799b479ca86b97dde4f1587dcc6 Mon Sep 17 00:00:00 2001 From: ToallaNova-O2D Date: Mon, 8 Jun 2026 21:15:14 -0400 Subject: [PATCH 6/6] Update files - use custom weight for rolls Changes randomized rolls to use weights instead of equal chances for certain items and weapon categories. Mantains 60/20/20 for Rifle/SMG/Shotgun and sets custom weights for utility items. --- .gitignore | 2 + LongWarOfTheChosen/Config/XComLW_Overhaul.ini | 108 +++++----- .../Src/LW_Overhaul/Classes/Utilities_LW.uc | 187 +++++++++++------- 3 files changed, 165 insertions(+), 132 deletions(-) diff --git a/.gitignore b/.gitignore index eafa7755d..4b595132d 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,5 @@ PublishedFileID.Id !/LongWarOfTheChosen/Content/Plots/* !/LongWarOfTheChosen/ContentForCook/Missions/* unrealscript-debugger-0.1.2.vsix +X2CommunityPromotionScreen/ +X2WOTCCommunityHighlander/ diff --git a/LongWarOfTheChosen/Config/XComLW_Overhaul.ini b/LongWarOfTheChosen/Config/XComLW_Overhaul.ini index 58bc019ac..bf7c3b0b4 100644 --- a/LongWarOfTheChosen/Config/XComLW_Overhaul.ini +++ b/LongWarOfTheChosen/Config/XComLW_Overhaul.ini @@ -2752,70 +2752,62 @@ LOW_INFILTRATION_MODIFIER_ON_REFLEX_ACTIONS[2]=0.5 LOW_INFILTRATION_MODIFIER_ON_REFLEX_ACTIONS[3]=1.0 ; ============================================================================= -; MODULAR REBEL LOADOUT SYSTEM ConfigDriven Weapon & Utility Setup +; REBEL LOADOUT SYSTEM - Config-Driven Weapon & Utility Setup ; ============================================================================= -;; Weapon Categories: Each entry defines a weapon type with 4 tech tiers. -; When rebels spawn, the system rolls a random category, then picks the -; weapon matching the highest researched tier. -;; Utility Archetypes: Define slot combinations (offensive/defensive/utility). -; Each archetype names two pools; one item is drawn from each pool at random. -;; Item Pools: Categorized lists of items. Rebels draw from these based on -; their rolled archetype. -;; REBEL_ALWAYS_EQUIP: Items that every rebel soldier always receives. -;; If these arrays are empty, the LEGACY loadout system is used automatically +; +; REBEL_WEAPON_CATEGORIES: Each entry defines a weapon type with 4 tech tiers and roll-chance weight. +; When rebels spawn, the system rolls a random category on equal or weighted chances +; then picks the weapon matching the highest researched tier. +; +; REBEL_VALID_ITEMS: Categorized lists of items and roll-chance weight. Rebels draw from these based on +; equal or weighted chances. +; +; REBEL_ALWAYS_EQUIP: Items that every rebel soldier always receives. +; +; If these arrays are empty, the LEGACY loadout system is used automatically ; (loadout string lookup via "RebelSoldier" + tier + weapon type). ; ============================================================================= -; Weapon categories (one random category per rebel) -+REBEL_WEAPON_CATEGORIES=(CategoryName="Rifle", Tier1Weapon="AssaultRifle_CV", Tier2Weapon="AssaultRifle_LS", Tier3Weapon="AssaultRifle_MG", Tier4Weapon="AssaultRifle_CG") -+REBEL_WEAPON_CATEGORIES=(CategoryName="SMG", Tier1Weapon="SMG_CV", Tier2Weapon="SMG_LS", Tier3Weapon="SMG_MG", Tier4Weapon="SMG_CG") -+REBEL_WEAPON_CATEGORIES=(CategoryName="Shotgun", Tier1Weapon="Shotgun_CV", Tier2Weapon="Shotgun_LS", Tier3Weapon="Shotgun_MG", Tier4Weapon="Shotgun_CG") -; New categories can be added, but need to check whether the assets can load them correctly, by your mod. -;+REBEL_WEAPON_CATEGORIES=(CategoryName="Cannon", Tier1Weapon="Cannon_CV", Tier2Weapon="Cannon_LS", Tier3Weapon="Cannon_MG", Tier4Weapon="Cannon_CG") -;+REBEL_WEAPON_CATEGORIES=(CategoryName="Sniper", Tier1Weapon="SniperRifle_CV", Tier2Weapon="SniperRifle_LS", Tier3Weapon="SniperRifle_MG", Tier4Weapon="SniperRifle_CG") - -; Offensive items pool, can be increased with personalized items, need to check whether they can use them -+REBEL_OFFENSIVE_ITEMS="FragGrenade" -;+REBEL_OFFENSIVE_ITEMS="AlienGrenade" -;+REBEL_OFFENSIVE_ITEMS="Firebomb" -;+REBEL_OFFENSIVE_ITEMS="FirebombMK2" -;+REBEL_OFFENSIVE_ITEMS="GasGrenade" -;+REBEL_OFFENSIVE_ITEMS="GasGrenadeMk2" -;+REBEL_OFFENSIVE_ITEMS="AcidGrenade" -;+REBEL_OFFENSIVE_ITEMS="AcidGrenadeMk2" -;+REBEL_OFFENSIVE_ITEMS="EMPGrenade" -;+REBEL_OFFENSIVE_ITEMS="EMPGrenadeMk2" -;+REBEL_OFFENSIVE_ITEMS="ShapedCharge" -;+REBEL_OFFENSIVE_ITEMS="Skulljack" -;+REBEL_OFFENSIVE_ITEMS="Neurowhip" -;+REBEL_OFFENSIVE_ITEMS="ProximityMine" - -; Defensive items pool, can be increased with personalized items, need to check whether they can use them -+REBEL_DEFENSIVE_ITEMS="FlashbangGrenade" -+REBEL_DEFENSIVE_ITEMS="SmokeGrenadeMk2" -;+REBEL_DEFENSIVE_ITEMS="BattleScanner" -;+REBEL_DEFENSIVE_ITEMS="MimicBeacon" -;+REBEL_DEFENSIVE_ITEMS="SmokeGrenade" - -; Utility items pool, can be increased with personalized items, need to check whether they can use them -+REBEL_UTILITY_ITEMS="Medikit" -+REBEL_UTILITY_ITEMS="NanoMedikit" -;+REBEL_UTILITY_ITEMS="MindShield" -;+REBEL_UTILITY_ITEMS="CombatStims" -;+REBEL_UTILITY_ITEMS="SustainingSphere" -;+REBEL_UTILITY_ITEMS="RefractionField" - -; Items every rebel always gets +; --- Enable weighted chances --- +REBEL_USE_WEIGHTS = true +; --- Weapon categories (one random category per rebel) --- ++REBEL_WEAPON_CATEGORIES=(CategoryName="Rifle", Tier1Weapon="AssaultRifle_CV", Tier2Weapon="AssaultRifle_LS", Tier3Weapon="AssaultRifle_MG", Tier4Weapon="AssaultRifle_CG", Weight=60) ++REBEL_WEAPON_CATEGORIES=(CategoryName="SMG", Tier1Weapon="SMG_CV", Tier2Weapon="SMG_LS", Tier3Weapon="SMG_MG", Tier4Weapon="SMG_CG", Weight=20) ++REBEL_WEAPON_CATEGORIES=(CategoryName="Shotgun", Tier1Weapon="Shotgun_CV", Tier2Weapon="Shotgun_LS", Tier3Weapon="Shotgun_MG", Tier4Weapon="Shotgun_CG", Weight=20) +; --- New categories can be added, but need to check whether the assets can load them correctly, by your mod. +;+REBEL_WEAPON_CATEGORIES=(CategoryName="Cannon", Tier1Weapon="Cannon_CV", Tier2Weapon="Cannon_LS", Tier3Weapon="Cannon_MG", Tier4Weapon="Cannon_CG", Weight=0) +;+REBEL_WEAPON_CATEGORIES=(CategoryName="Sniper", Tier1Weapon="SniperRifle_CV", Tier2Weapon="SniperRifle_LS", Tier3Weapon="SniperRifle_MG", Tier4Weapon="SniperRifle_CG", Weight=0) + +; --- Offensive items pool, can be increased with personalized items, need to check whether they can use them --- ++REBEL_VALID_ITEMS=(ItemName="FragGrenade", Weight=50) +;+REBEL_VALID_ITEMS="AlienGrenade" +;+REBEL_VALID_ITEMS="Firebomb" +;+REBEL_VALID_ITEMS="FirebombMK2" +;+REBEL_VALID_ITEMS="GasGrenade" +;+REBEL_VALID_ITEMS="GasGrenadeMk2" +;+REBEL_VALID_ITEMS="AcidGrenade" +;+REBEL_VALID_ITEMS="AcidGrenadeMk2" +;+REBEL_VALID_ITEMS="EMPGrenade" +;+REBEL_VALID_ITEMS="EMPGrenadeMk2" +;+REBEL_VALID_ITEMS="ShapedCharge" +;+REBEL_VALID_ITEMS="Skulljack" +;+REBEL_VALID_ITEMS="Neurowhip" +;+REBEL_VALID_ITEMS="ProximityMine" + +; --- Defensive items pool, can be increased with personalized items, need to check whether they can use them --- ++REBEL_VALID_ITEMS=(ItemName="FlashbangGrenade", Weight=30) ++REBEL_VALID_ITEMS=(ItemName="SmokeGrenadeMk2", Weight=30) +;+REBEL_VALID_ITEMS="BattleScanner" +;+REBEL_VALID_ITEMS="MimicBeacon" +;+REBEL_VALID_ITEMS="SmokeGrenade" + +; --- Utility items pool, can be increased with personalized items, need to check whether they can use them --- ++REBEL_VALID_ITEMS=(ItemName="Medikit", Weight=10) ++REBEL_VALID_ITEMS=(ItemName="NanoMedikit", Weight=5) + +; --- Items every rebel always gets --- +REBEL_ALWAYS_EQUIP="EvacFlare" -; Utility archetypes (one random archetype per rebel) -; Each archetype names two slot pools. One item is drawn from each pool. New archetypes are not available for now, but this array covers them all IMHO -+REBEL_UTILITY_ARCHETYPES=(ArchetypeName="OffDef", Slot1Pool="Offensive", Slot2Pool="Defensive") -+REBEL_UTILITY_ARCHETYPES=(ArchetypeName="OffOff", Slot1Pool="Offensive", Slot2Pool="Offensive") -+REBEL_UTILITY_ARCHETYPES=(ArchetypeName="DefDef", Slot1Pool="Defensive", Slot2Pool="Defensive") -+REBEL_UTILITY_ARCHETYPES=(ArchetypeName="OffUtil", Slot1Pool="Offensive", Slot2Pool="Utility") -+REBEL_UTILITY_ARCHETYPES=(ArchetypeName="DefUtil", Slot1Pool="Defensive", Slot2Pool="Utility") - [LW_Overhaul.SeqAct_ResetCivilians] +CharacterTemplatesToSwap=Rebel +CharacterTemplatesToSwap=Soldier_VIP diff --git a/LongWarOfTheChosen/Src/LW_Overhaul/Classes/Utilities_LW.uc b/LongWarOfTheChosen/Src/LW_Overhaul/Classes/Utilities_LW.uc index 3736e0d3b..39575388d 100644 --- a/LongWarOfTheChosen/Src/LW_Overhaul/Classes/Utilities_LW.uc +++ b/LongWarOfTheChosen/Src/LW_Overhaul/Classes/Utilities_LW.uc @@ -28,23 +28,20 @@ struct RebelWeaponCategoryEntry var name Tier2Weapon; // e.g. 'AssaultRifle_LS' var name Tier3Weapon; // e.g. 'AssaultRifle_MG' var name Tier4Weapon; // e.g. 'AssaultRifle_CG' + var int Weight; // e.g. 20, 30, 100 }; // Defines a utility slot combination archetype. -struct RebelUtilityArchetype +struct RebelUtilityItem { - var name ArchetypeName; // e.g. 'OffDef', 'OffOff', 'DefDef' - var name Slot1Pool; // 'Offensive', 'Defensive', or 'Utility' - var name Slot2Pool; // 'Offensive', 'Defensive', or 'Utility' + var name ItemName; // e.g. 'Medikit', 'FragGrenade', 'EvacFlare' + var int Weight; // e.g. 20, 30, 100 }; -// === Modular Rebel Loadout System config === +var config bool REBEL_USE_WEIGHTS; var config array REBEL_WEAPON_CATEGORIES; -var config array REBEL_OFFENSIVE_ITEMS; -var config array REBEL_DEFENSIVE_ITEMS; -var config array REBEL_UTILITY_ITEMS; +var config array REBEL_VALID_ITEMS; var config array REBEL_ALWAYS_EQUIP; -var config array REBEL_UTILITY_ARCHETYPES; var config array REFLEX_ACTION_CHANCE_YELLOW; var config array REFLEX_ACTION_CHANCE_GREEN; @@ -485,30 +482,12 @@ static function array GetCompleteDefaultLoadout(XComGameSta } // ============================================================================= -// MODULAR REBEL LOADOUT SYSTEM Helper Functions +// MODULAR REBEL LOADOUT SYSTEM - Helper Functions // ============================================================================= -/// Returns the appropriate item pool array for the given pool name. -/// Valid pool names: 'Offensive', 'Defensive', 'Utility' -static function array GetUtilityPool(name PoolName) -{ - local array EmptyArray; - - if (PoolName == 'Offensive') - return default.REBEL_OFFENSIVE_ITEMS; - else if (PoolName == 'Defensive') - return default.REBEL_DEFENSIVE_ITEMS; - else if (PoolName == 'Utility') - return default.REBEL_UTILITY_ITEMS; - - `Redscreen("Utilities_LW.GetUtilityPool: Unknown pool name:" @ PoolName); - return EmptyArray; -} - /// Returns the weapon template name for the given category and tier. static function name GetWeaponForTier(RebelWeaponCategoryEntry Category, int Tier) { - `LWTrace("Rebel Weapon Tier:" @ Tier); switch(Tier) { case 4: return Category.Tier4Weapon; @@ -518,6 +497,58 @@ static function name GetWeaponForTier(RebelWeaponCategoryEntry Category, int Tie } } +static function int RollRebelWeaponByWeight(array Category) +{ + local int ItemIter,WeightChance,iRand; + + WeightChance = 0; + + for (ItemIter = 0;ItemIter < Category.length;ItemIter++) + { + WeightChance += Category[ItemIter].Weight; + `LWTrace("Category:" @ Category[ItemIter].CategoryName + @ "Adds:" @ Category[ItemIter].Weight + @ "Aggregate:" @ WeightChance @ "overall"); + } + + iRand = `SYNC_RAND_STATIC(WeightChance); + + `LWTrace("Final Weapon Weight Chance: " @ WeightChance + @ " - iRand:" @ iRand); + + for (ItemIter = Category.length; ItemIter > 0;ItemIter--) + { + if (iRand > (WeightChance - Category[ItemIter].Weight)) return ItemIter; + else WeightChance -= Category[ItemIter].Weight; + } +} + +static function int RollRebelItemByWeight(array Category) +{ + local int ItemIter,WeightChance,iRand; + + WeightChance = 0; + + for (ItemIter = 0;ItemIter < Category.length;ItemIter++) + { + WeightChance += Category[ItemIter].Weight; + `LWTrace("Category:" @ Category[ItemIter].ItemName + @ "Adds:" @ Category[ItemIter].Weight + @ "Aggregate:" @ WeightChance @ "overall"); + } + + iRand = `SYNC_RAND_STATIC(WeightChance); + + `LWTrace("Final Item Weight Chance: " @ WeightChance + @ " - iRand:" @ iRand); + + for (ItemIter = Category.length; ItemIter > 0;ItemIter--) + { + if (iRand > (WeightChance - Category[ItemIter].Weight)) return ItemIter; + else WeightChance -= Category[ItemIter].Weight; + } +} + /// Creates and equips an item on a unit by template name. /// Handles weapon appearance transfer for primary/secondary weapons. static function EquipItemOnUnit(XComGameState_Unit Unit, name ItemTemplateName, XComGameState ModifyGameState) @@ -531,7 +562,6 @@ static function EquipItemOnUnit(XComGameState_Unit Unit, name ItemTemplateName, `Redscreen("Utilities_LW.EquipItemOnUnit: Empty item template name!"); return; } - ItemMgr = class'X2ItemTemplateManager'.static.GetItemTemplateManager(); EquipTemplate = X2EquipmentTemplate(ItemMgr.FindItemTemplate(ItemTemplateName)); @@ -550,6 +580,11 @@ static function EquipItemOnUnit(XComGameState_Unit Unit, name ItemTemplateName, NewItem.WeaponAppearance.nmWeaponPattern = Unit.kAppearance.nmWeaponPattern; } + `LWTrace("Adding" @ NewItem.Name + @ "to" @ Unit.GetFullName() + @ "on slot" @ EquipTemplate.InventorySlot + @ "with" @ NewItem.Ammo @ "charges"); + Unit.AddItemToInventory(NewItem, EquipTemplate.InventorySlot, ModifyGameState); ModifyGameState.AddStateObject(NewItem); } @@ -560,7 +595,7 @@ static function EquipItemOnUnit(XComGameState_Unit Unit, name ItemTemplateName, static function int RollRebelWeaponTier(XComGameState_LWOutpost Outpost, StateObjectReference RebelRef) { local int LaserChance, MagChance, CoilChance; - local int RebelLevel, iRand; + local int RebelLevel, iRand, iTier; local bool AdvancedLasersResearched; local bool MagnetizedWeaponsResearched; @@ -589,8 +624,9 @@ static function int RollRebelWeaponTier(XComGameState_LWOutpost Outpost, StateOb LaserChance = 0; MagChance = 0; CoilChance = 0; + iTier = 1; - // Laser tier chances + // --- Laser tier chances --- if (MagnetizedWeaponsResearched && AdvancedLasersResearched) { LaserChance += (20 + 10 * (RebelLevel + 1)); // 30/40/50 @@ -600,7 +636,7 @@ static function int RollRebelWeaponTier(XComGameState_LWOutpost Outpost, StateOb LaserChance += (20 + 10 * (RebelLevel + 1)); // 60/80/100 } - // Magnetic tier chances + // --- Magnetic tier chances --- if (CoilgunsResearched && GaussWeaponsResearched) { if (AdvancedLasersResearched) @@ -614,7 +650,7 @@ static function int RollRebelWeaponTier(XComGameState_LWOutpost Outpost, StateOb MagChance += (20 + 10 * (RebelLevel + 1)); // 60/80/100 } - // Coilgun tier chances + // --- Coilgun tier chances --- if (PlasmaRifleResearched) { if (GaussWeaponsResearched) @@ -635,19 +671,20 @@ static function int RollRebelWeaponTier(XComGameState_LWOutpost Outpost, StateOb iRand = `SYNC_RAND_STATIC(100); `LWTrace("Coil Chance:" @ CoilChance - @ " Mag Chance:" @ MagChance - @ " Laser Chance:" @ LaserChance - @ " iRand:" @ iRand); + @ " - Mag Chance:" @ MagChance + @ " - Laser Chance:" @ LaserChance + @ " - iRand:" @ iRand); // unique roll for all tiers if (iRand > CoilChance) - return 4; + iTier = 4; else if (iRand > MagChance) - return 3; + iTier = 3; else if (iRand > LaserChance) - return 2; + iTier = 2; - return 1; + `LWTrace("Rebel Weapon Tier:" @ iTier); + return iTier; } /// Fallback: Rolls a legacy loadout name for backward compatibility. @@ -677,12 +714,13 @@ static function name RollLegacyLoadout(XComGameState_LWOutpost Outpost, StateObj } // ============================================================================= -// CreateRebelSoldier MODULAR VERSION +// CreateRebelSoldier - MODULAR VERSION // ============================================================================= -// Creates a soldier proxy for the given rebel, sets them as onmission, +// Creates a soldier proxy for the given rebel, sets them as on-mission, // and gives them a loadout. // // Loadout assignment: +// For custom character templates, uses the template's own DefaultLoadout. // For default proxies: uses modular system if configured, else legacy. // // See: XComLW_Overhaul.ini [LW_Overhaul.Utilities_LW] for config defaults. @@ -693,11 +731,9 @@ function static XComGameState_Unit CreateRebelSoldier(StateObjectReference Rebel local XComGameState_Unit Proxy; local Name TemplateName; local int WeaponTier; - local int CategoryIdx, ArchetypeIdx, ItemIdx; + local int WeaponIdx, ItemIdx1, ItemIdx2, ItemIdx3; local RebelWeaponCategoryEntry SelectedCategory; - local RebelUtilityArchetype SelectedArchetype; local name WeaponTemplate; - local array Pool; Outpost = XComGameState_LWOutpost(`XCOMHISTORY.GetGameStateForObjectID(OutpostRef.ObjectID)); @@ -720,53 +756,56 @@ function static XComGameState_Unit CreateRebelSoldier(StateObjectReference Rebel Proxy = CreateRebelProxy(RebelRef, OutpostRef, TemplateName, true, NewGameState); Proxy.SetSoldierClassTemplate('LWS_RebelSoldier'); - // === LOADOUT ASSIGNMENT === if (Loadout != '') { ApplyLoadout(Proxy, Loadout, NewGameState); `LWTrace("Rebel Loadout (forced):" @ Loadout); } - else if (default.REBEL_WEAPON_CATEGORIES.Length > 0 && default.REBEL_UTILITY_ARCHETYPES.Length > 0) + else if (default.REBEL_WEAPON_CATEGORIES.Length > 0) { // NEW MODULAR LOADOUT SYSTEM WeaponTier = RollRebelWeaponTier(Outpost, RebelRef); - CategoryIdx = `SYNC_RAND_STATIC(default.REBEL_WEAPON_CATEGORIES.Length); - SelectedCategory = default.REBEL_WEAPON_CATEGORIES[CategoryIdx]; + if (default.REBEL_USE_WEIGHTS) + { + WeaponIdx = RollRebelWeaponByWeight(default.REBEL_WEAPON_CATEGORIES); + } + else + { + WeaponIdx = `SYNC_RAND_STATIC(default.REBEL_WEAPON_CATEGORIES.Length); + } + SelectedCategory = default.REBEL_WEAPON_CATEGORIES[WeaponIdx]; WeaponTemplate = GetWeaponForTier(SelectedCategory, WeaponTier); - `LWTrace("Rebel Weapon: " @WeaponTemplate); + `LWTrace("Array position:" @ WeaponIdx @ "chosen for Weapon:" @ WeaponTemplate); EquipItemOnUnit(Proxy, WeaponTemplate, NewGameState); - ArchetypeIdx = `SYNC_RAND_STATIC(default.REBEL_UTILITY_ARCHETYPES.Length); - SelectedArchetype = default.REBEL_UTILITY_ARCHETYPES[ArchetypeIdx]; - - Pool = GetUtilityPool(SelectedArchetype.Slot1Pool); - if (Pool.Length > 0) - { - ItemIdx = `SYNC_RAND_STATIC(Pool.Length); - `LWTrace("Rebel Slot 1:" @ Pool[ItemIdx]); - EquipItemOnUnit(Proxy, Pool[ItemIdx], NewGameState); - } - - Pool = GetUtilityPool(SelectedArchetype.Slot2Pool); - if (Pool.Length > 0) + if (default.REBEL_VALID_ITEMS.length > 0) { - ItemIdx = `SYNC_RAND_STATIC(Pool.Length); - `LWTrace("Rebel Slot 2:" @ Pool[ItemIdx]); - EquipItemOnUnit(Proxy, Pool[ItemIdx], NewGameState); + if (default.REBEL_USE_WEIGHTS) + { + ItemIdx1 = RollRebelItemByWeight(default.REBEL_VALID_ITEMS); + `LWTrace("Array position:" @ ItemIdx1 @ "chosen for Item Slot 1:" @ default.REBEL_VALID_ITEMS[ItemIdx1].ItemName); + EquipItemOnUnit(Proxy, default.REBEL_VALID_ITEMS[ItemIdx1].ItemName, NewGameState); + ItemIdx2 = RollRebelItemByWeight(default.REBEL_VALID_ITEMS); + `LWTrace("Array position:" @ ItemIdx2 @ "chosen for Item Slot 2:" @ default.REBEL_VALID_ITEMS[ItemIdx2].ItemName); + EquipItemOnUnit(Proxy, default.REBEL_VALID_ITEMS[ItemIdx2].ItemName, NewGameState); + } + else + { + ItemIdx1 = `SYNC_RAND_STATIC(default.REBEL_VALID_ITEMS.Length); + `LWTrace("Array position:" @ ItemIdx1 @ "chosen for Item Slot 1:" @ default.REBEL_VALID_ITEMS[ItemIdx1].ItemName); + EquipItemOnUnit(Proxy, default.REBEL_VALID_ITEMS[ItemIdx1].ItemName, NewGameState); + ItemIdx2 = `SYNC_RAND_STATIC(default.REBEL_VALID_ITEMS.Length); + `LWTrace("Array position:" @ ItemIdx2 @ "chosen for Item Slot 2:" @ default.REBEL_VALID_ITEMS[ItemIdx2].ItemName); + EquipItemOnUnit(Proxy, default.REBEL_VALID_ITEMS[ItemIdx2].ItemName, NewGameState); + } } - for (ItemIdx = 0; ItemIdx < default.REBEL_ALWAYS_EQUIP.Length; ItemIdx++) + for (ItemIdx3 = 0; ItemIdx3 < default.REBEL_ALWAYS_EQUIP.Length; ItemIdx3++) { - EquipItemOnUnit(Proxy, default.REBEL_ALWAYS_EQUIP[ItemIdx], NewGameState); - `LWTrace("Always equip:" @ default.REBEL_ALWAYS_EQUIP[ItemIdx]); + EquipItemOnUnit(Proxy, default.REBEL_ALWAYS_EQUIP[ItemIdx3], NewGameState); } - - `LWTrace("Rebel Loadout:" @ SelectedCategory.CategoryName - @ " Weapon Tier:" $ WeaponTier - @ " Weapon Template:" @ WeaponTemplate - @ " Archetype:" @ SelectedArchetype.ArchetypeName); } else {