X2CommunityCore / X2WOTCCommunityHighlander

https://steamcommunity.com/workshop/filedetails/?id=1134256495
MIT License
60 stars 69 forks source link

POIs stop appearing over a long campaign #1069

Open Xymanek opened 3 years ago

Xymanek commented 3 years ago

At some point over a long campaign, Points Of Interest (non-supply-drop scanning sites that grant something and disappear after completing the scan) will stop appearing, whether they are attempted to be spawned by base game logic (e.g. after some of the missions) or by mods. More Points Of Interest WOTC includes the following in its description:

NO MORE POIs POP UP AFTER A WHILE Use the Rewards Deck Refresher mod, it's in the Requirements. It may not fix the issue however it's not 100% tested but it wont hurt. Suggest keeping # of POIs that spawn at 4 maximum for a long campaign.

While I personally haven't replicated this issue - it requires a prolonged campaign - the sheer amount of reports (which consistently point to long campaigns as the trigger for the problem) indicates that this problem is either a base-game bug or one caused by a very popular mod (CHL itself?)

Clibanarius commented 2 years ago

I experienced this error back in VANILLA XCOM2 in Nov/Dec 2016 when using the mod to spawn tons of POIs. I forget which, but this is so long before any mods currently in circulation were released. So I say base-game bug most like.

FearTheBunnies commented 2 years ago

What I found when building my refresher: The POIs are designed with an array of appearance weights for how they are built into the POI deck. The two factors that affect the POI appearing is where in the weighted array the game is, and how many times the POI has appeared. It looks like it was designed this way so that POIs would eventually "stop" showing up as part of the balancing of the game.

The solution I took was at the end of each month I look to see if the weight of the POI was 0 (meaning no entries were to be put into the deck) and if the number of appearances was > 0 (meaning it has worked through the weighted array) and then I updated the cursor for where in the weighted array the POI was to the first active slot in the array that generates weights.

FearTheBunnies commented 2 years ago

Next goody I have found:

[0322.12] ScriptLog: [Refresh POIs] Active POI: POI_Supplies, Weight: 8, NumSpawns: 6, NextWeightUpdateDate: 8/28/2035, CurrentWeightIndex: 2, bAvailable: False, bTriggerAppearedPopup: False, bNeedsAppearedPopup: False, bNeedsScanCompletePopup: False, DespawnTime: 8/8/2035, CanBeScanned: False, GameTime: 5/15/2036
[0322.12] ScriptLog: [Refresh POIs] Active POI: POI_Intel, Weight: 14, NumSpawns: 3, NextWeightUpdateDate: 8/28/2035, CurrentWeightIndex: 4, bAvailable: False, bTriggerAppearedPopup: False, bNeedsAppearedPopup: False, bNeedsScanCompletePopup: False, DespawnTime: 10/27/2035, CanBeScanned: False, GameTime: 5/15/2036
[0322.12] ScriptLog: [Refresh POIs] Active POI: POI_Engineer, Weight: 5, NumSpawns: 1, NextWeightUpdateDate: 0/0/0, CurrentWeightIndex: 0, bAvailable: False, bTriggerAppearedPopup: False, bNeedsAppearedPopup: False, bNeedsScanCompletePopup: False, DespawnTime: 1/8/2036, CanBeScanned: False, GameTime: 5/15/2036
[0322.12] ScriptLog: [Refresh POIs] Active POI: POI_Rookies, Weight: 30, NumSpawns: 0, NextWeightUpdateDate: 5/21/2036, CurrentWeightIndex: 0, bAvailable: False, bTriggerAppearedPopup: False, bNeedsAppearedPopup: False, bNeedsScanCompletePopup: False, DespawnTime: 2/17/2036, CanBeScanned: False, GameTime: 5/15/2036

Looks like there is a bug somewhere in the bowels of the base game that keeps an expired POI from being removed from the ResistanceHQ's active POI list, which means it will "eat up" one of the slots you can spawn a POI into.

This stops new ones from appearing since there is a hard limit of a max of 3 POIs for the attempt action. Needs deeper digging, but for now my recycler will patch this issue by cleaning up the expired POIs from the active list on month end.

BlackDog86 commented 1 year ago

This is the code which is (supposed to be) responsible for removing dead POIs from the game in XCGS_PointOfInterest (Update method). I suspect some of the conditions in here are not working for some POIs so they're never removed from the array.

    // Do not trigger anything while the Avenger or Skyranger are flying, or if another popup is already being presented
    if (bAvailable && StrategyMap != none && StrategyMap.m_eUIState != eSMS_Flight && !`HQPRES.ScreenStack.IsCurrentClass(class'UIAlert'))
    {
        // If the Avenger is not at the location and time runs out, despawn the POI
        if (XComHQ.GetCurrentScanningSite().GetReference().ObjectID != ObjectID && !GetMyTemplate().bNeverExpires && class'X2StrategyGameRulesetDataStructures'.static.LessThan(DespawnTime, GetCurrentTime()))
        {
            bAvailable = false;
            ResetPOI(NewGameState);
            bModified = true;
        }

Reset POI does this:

function ResetPOI(optional XComGameState NewGameState)
{
    local XComGameState_HeadquartersResistance ResHQ;

    ResetScan();

    ResHQ = class'UIUtilities_Strategy'.static.GetResistanceHQ();
    ResHQ.DeactivatePOI(NewGameState, GetReference());
}

Calling the DeactivatePOI method in XCGS_Headquarters_Resistance:


static function DeactivatePOI(XComGameState NewGameState, StateObjectReference POIRef)
{
    local XComGameState_HeadquartersResistance ResHQ;
    local int idx;

    foreach NewGameState.IterateByClassType(class'XComGameState_HeadquartersResistance', ResHQ)
    {
        break;
    }

    if (ResHQ == none)
    {
        ResHQ = XComGameState_HeadquartersResistance(`XCOMHISTORY.GetSingleGameStateObjectForClass(class'XComGameState_HeadquartersResistance'));
        ResHQ = XComGameState_HeadquartersResistance(NewGameState.ModifyStateObject(class'XComGameState_HeadquartersResistance', ResHQ.ObjectID));
    }

    // Check Active POIs for the need to deactivate
    for (idx = 0; idx < ResHQ.ActivePOIs.Length; idx++)
    {
        if (ResHQ.ActivePOIs[idx].ObjectID == POIRef.ObjectID)
        {
            ResHQ.ActivePOIs.Remove(idx, 1);
            break;
        }
    }
}
BlackDog86 commented 1 year ago

In the example above, something has set the problem POIs bAvailable to false, which means that the shouldupdate and update methods which despawn them are never called.