Skip to content

DLC2 causes extranenous mission schedule re-rolls #1077

@Xymanek

Description

@Xymanek

X2DownloadableContentInfo_DLC_Day60::UpdateMissionSpawningInfo has 2 conditions for forcing a re-roll:

  1. Ruler is present, but shouldn't be
  2. XComHQ tactical tags were changed by XComGameState_AlienRulerManager::UpdateRulerSpawningData

This was probably fine in vanilla X2, but in WOTC this gets messy. The following 2 scenarios (that I'm aware of, there could be more) will cause the game to repeatedly re-roll the schedule (note that in unmodded game this requires the Shadow Chamber to be built, but this behaviour is only observable with SC built anyway):

Ruler waiting in a facility + "normal" mission

Suppose we are using the xpack integrated mode and the Viper King just activated. It will be waiting in a facility and all other missions (the "normal") will be without a ruler.

  1. Assume no prior leftover ruler tags in XComHQ
  2. Open facility blades
  3. Ruler is needed -> tags added -> re-roll
  4. Close facility blades
  5. Open normal mission blades
  6. No ruler is needed -> leftover ruler tag in XComHQ is removed -> tags are changed -> re-roll
  7. Open facility blades
  8. Ruler is needed -> tags added -> re-roll... Oh. We are back to step 2.

And the cycle will repeat endlessly

Cycling between GOps options with Chosen present

Suppose all active rulers are loose and can appear on any mission. A set of 3 GOps spawns. Some of them have a chosen, some do not (e.g. permanently killed already). Unlike integrated facilities (where rulers force out the chosen), loose rulers give priority to the chosen (i.e. cannot appear if a chosen is appearing).

private function bool CanRulerAppearOnMission(XComGameState_MissionSite MissionState)
{
return (!ChosenOnMission(MissionState) && !MissionSitrepsBanRulers(MissionState));
}

Mission Chosen Ruler
1st Yes No
2nd Yes No
3rd No Yes
  1. Assume no prior leftover ruler tags in XComHQ
  2. Open mission blades and cycle through the 3 options
  3. First mission has no ruler -> DLC2 is chill
  4. Second mission has no ruler -> DLC2 is chill
  5. Third mission has a ruler -> tags added -> re-roll
  6. First mission has no ruler -> leftover ruler tag in XComHQ is removed -> tags are changed -> re-roll
  7. Second mission has no ruler -> DLC2 is chill
  8. Third mission has a ruler -> tags added -> re-roll
  9. We are back to step 5

As such, as we keep cycling between the options, we will keep getting different schedule/enemy selection

Potential solutions

It might be tempting to just check whether the ruler is present or not (vs the desired) and use that to drive the re-rolling decision. Unfortunately, that cannot work due to additional tags:

private function AddRulerAdditionalTacticalTags(XComGameState_HeadquartersXCom XComHQ, int NumAppearances, AlienRulerData RulerData)
{
local int idx, MaxNumAppearances, MaxAppearanceIndex;
if(RulerData.AdditionalTags.Length == 0)
{
return;
}
MaxNumAppearances = 0;
MaxAppearanceIndex = 0;
for(idx = 0; idx < RulerData.AdditionalTags.Length; idx++)
{
if(NumAppearances == RulerData.AdditionalTags[idx].NumTimesAppeared)
{
XComHQ.TacticalGameplayTags.AddItem(RulerData.AdditionalTags[idx].TacticalTag);
return;
}
if(RulerData.AdditionalTags[idx].NumTimesAppeared > MaxNumAppearances)
{
MaxNumAppearances = RulerData.AdditionalTags[idx].NumTimesAppeared;
MaxAppearanceIndex = idx;
}
}
if(NumAppearances >= MaxNumAppearances)
{
XComHQ.TacticalGameplayTags.AddItem(RulerData.AdditionalTags[MaxAppearanceIndex].TacticalTag);
}
}

function ClearActiveRulerTags(XComGameState_HeadquartersXCom XComHQ)
{
local int idx, i;
for(idx = 0; idx < default.AlienRulerTemplates.Length; idx++)
{
// Remove the active tag
XComHQ.TacticalGameplayTags.RemoveItem(default.AlienRulerTemplates[idx].ActiveTacticalTag);
// Remove any additional tags
for(i = 0; i < default.AlienRulerTemplates[idx].AdditionalTags.Length; i++)
{
XComHQ.TacticalGameplayTags.RemoveItem(default.AlienRulerTemplates[idx].AdditionalTags[i].TacticalTag);
}
}
}

Using the Viper King as an example:

+AlienRulerTemplates=(AlienRulerTemplateName="ViperKing", ForceLevel=4, ActiveTacticalTag="Ruler_ViperKingActive", DeadTacticalTag="Ruler_ViperKingDead", \\
					  AdditionalTags[0]=(NumTimesAppeared=2, TacticalTag="Ruler_ViperKing_02"), AdditionalTags[1]=(NumTimesAppeared=3, TacticalTag="Ruler_ViperKing_03"), AdditionalTags[2]=(NumTimesAppeared=4, TacticalTag="Ruler_ViperKing_04"))
+EncounterBuckets=(EncounterBucketID="LIST_RULER_Viper", \\
				  EncounterIDs[0]=(EncounterID="DLC2_ViperKingx4", \\
				  				   IncludeTacticalTag="Ruler_ViperKing_04"), \\
				  EncounterIDs[1]=(EncounterID="DLC2_ViperKingx3", \\
				  				   IncludeTacticalTag="Ruler_ViperKing_03"), \\
				  EncounterIDs[2]=(EncounterID="DLC2_ViperKingx2", \\
				  				   IncludeTacticalTag="Ruler_ViperKing_02"), \\
				  EncounterIDs[3]=(EncounterID="DLC2_ViperKingx1"), \\
				  )
+ConfigurableEncounters=(EncounterID="DLC2_ViperKingx1", \\
					 MaxSpawnCount=1, \\
					 ForceSpawnTemplateNames[0]="ViperKing", \\
					 bGroupDoesNotAwardLoot=true)
					 
+ConfigurableEncounters=(EncounterID="DLC2_ViperKingx2", \\
					 MaxSpawnCount=2, \\
					 ForceSpawnTemplateNames[0]="ViperKing", \\
					 ForceSpawnTemplateNames[1]="Viper", \\
					 bGroupDoesNotAwardLoot=true)

Here we can see that the 2nd time you see the King, he will be accompanied by an escort of a single viper and this is achieved via the additional tags. Checking only the presence/absence of ViperKing will overlook this behaviour.

Ideally, we would just store the tags that were used to generate the current schedule (although the comparison logic won't be simple, since we can't just naively compare 1:1 like the current logic does), but everything is native, so we would need to do so via a game state component (or other similar means)

struct native X2SelectedMissionData
{
// The Alert Level for which this mission data is valid
var() int AlertLevel;
// The Force Level for which this mission data is valid
var() int ForceLevel;
// The name of the mission schedule which has been selected for this mission
var() Name SelectedMissionScheduleName;
// The list of encounters which have been selected for this mission
var() array<X2SelectedEncounterData> SelectedEncounters;
};
// Mission data that has been selected for this Mission Site.
var() X2SelectedMissionData SelectedMissionData;

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions