Add migration for invalid pockets

This commit is contained in:
Archangel
2025-10-31 16:53:08 +01:00
parent e3114ae23f
commit ae15dc1bee
@@ -0,0 +1,279 @@
using System.Text.Json.Nodes;
using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.Models.Common;
using SPTarkov.Server.Core.Models.Enums;
using SPTarkov.Server.Core.Services;
namespace SPTarkov.Server.Core.Migration.Migrations;
[Injectable]
public class InvalidPocketFix(DatabaseService databaseService) : AbstractProfileMigration
{
public const string DEFAULT_POCKETS = "627a4e6b255f7527fb05a0f6";
public const string UNHEARD_POCKETS = "65e080be269cbd5c5005e529";
public override string FromVersion
{
get { return "~4.0"; }
}
public override string ToVersion
{
get { return "~4.0"; }
}
public override string MigrationName
{
get { return "InvalidPocketFix"; }
}
private enum PocketStatus
{
Valid,
Missing,
Invalid,
}
private PocketStatus GetPmcPocketStatus(JsonObject profile)
{
if (profile["characters"]?["pmc"]?["Inventory"]?["items"] is not JsonArray items)
{
// Uninitialized profile, just pass valid
return PocketStatus.Valid;
}
foreach (var itemNode in items)
{
if (itemNode is not JsonObject itemObj)
{
continue;
}
if (
itemObj.TryGetPropertyValue("slotId", out var slotNode)
&& slotNode is JsonValue slotValue
&& slotValue.TryGetValue<string>(out var slotId)
&& slotId == "Pockets"
)
{
if (
itemObj.TryGetPropertyValue("_tpl", out var tplNode)
&& tplNode is JsonValue tplValue
&& tplValue.TryGetValue<string>(out var template)
)
{
return databaseService.GetItems().ContainsKey(template) ? PocketStatus.Valid : PocketStatus.Invalid;
}
}
}
return PocketStatus.Missing;
}
private PocketStatus GetScavPocketStatus(JsonObject profile)
{
if (profile["characters"]?["scav"]?["Inventory"]?["items"] is not JsonArray items)
{
// Uninitialized profile, just pass valid
return PocketStatus.Valid;
}
foreach (var itemNode in items)
{
if (itemNode is not JsonObject itemObj)
{
continue;
}
if (
itemObj.TryGetPropertyValue("slotId", out var slotNode)
&& slotNode is JsonValue slotValue
&& slotValue.TryGetValue<string>(out var slotId)
&& slotId == "Pockets"
)
{
if (
itemObj.TryGetPropertyValue("_tpl", out var tplNode)
&& tplNode is JsonValue tplValue
&& tplValue.TryGetValue<string>(out var template)
)
{
return databaseService.GetItems().ContainsKey(template) ? PocketStatus.Valid : PocketStatus.Invalid;
}
}
}
return PocketStatus.Missing;
}
private bool HasCompletedOldPatterns(JsonObject profile)
{
if (profile["characters"]?["pmc"]?["Quests"] is not JsonArray quests)
{
return false;
}
foreach (var questNode in quests)
{
if (questNode is not JsonObject questObj)
{
continue;
}
if (
questObj.TryGetPropertyValue("qid", out var qIdNode)
&& qIdNode is JsonValue qIdValue
&& qIdValue.TryGetValue<string>(out var qId)
&& qId == QuestTpl.OLD_PATTERNS.ToString()
&& questObj.TryGetPropertyValue("status", out var statusNode)
&& statusNode is JsonValue statusValue
&& statusValue.TryGetValue<string>(out var status)
&& status.Equals(nameof(QuestStatusEnum.Success), StringComparison.OrdinalIgnoreCase)
)
{
return true;
}
}
return false;
}
private bool IsUnheardProfile(JsonObject profile)
{
var gameVersion = profile?["characters"]?["pmc"]?["Info"]?["GameVersion"]?.GetValue<string>();
if (!string.IsNullOrEmpty(gameVersion))
{
return gameVersion.Equals("unheard_edition", StringComparison.OrdinalIgnoreCase);
}
return false;
}
private JsonObject CreatePocketItem(string parentId, string defaultPocketTpl)
{
return new JsonObject
{
["_id"] = new MongoId().ToString(),
["_tpl"] = defaultPocketTpl,
["parentId"] = parentId,
["slotId"] = "Pockets",
};
}
// Set slotId to hideout, parentId to sorting table & remove location so that the sorting table will automatically pick a location
private void MoveItemsToSortingTable(JsonArray items, string sortingTableId)
{
foreach (var item in items.OfType<JsonObject>())
{
if (
item.TryGetPropertyValue("slotId", out var slotNode)
&& slotNode is JsonValue slotNodeValue
&& slotNodeValue.TryGetValue<string>(out var slotId)
&& (
(
slotId.StartsWith("pocket", StringComparison.OrdinalIgnoreCase)
// Exclude the pcokets itself
&& !slotId.Equals("Pockets", StringComparison.OrdinalIgnoreCase)
)
// Special slots are also keyed to the pockets
|| slotId.StartsWith("SpecialSlot", StringComparison.OrdinalIgnoreCase)
)
)
{
item["slotId"] = "hideout";
item["parentId"] = sortingTableId;
item.Remove("location");
}
}
}
public override bool CanMigrate(JsonObject profile, IEnumerable<IProfileMigration> previouslyRanMigrations)
{
if (GetPmcPocketStatus(profile) != PocketStatus.Valid || GetScavPocketStatus(profile) != PocketStatus.Valid)
{
return true;
}
return false;
}
public override JsonObject? Migrate(JsonObject profile)
{
var pmcPocketStatus = GetPmcPocketStatus(profile);
var scavPocketStatus = GetScavPocketStatus(profile);
if (pmcPocketStatus != PocketStatus.Valid)
{
var items = profile["characters"]?["pmc"]?["Inventory"]?["items"] as JsonArray;
var pmcInventory = profile["characters"]?["pmc"]?["Inventory"] as JsonObject;
var pmcSortingTable = pmcInventory?["sortingTable"]?.GetValue<string>()!;
var pmcEquipment = pmcInventory?["equipment"]?.GetValue<string>();
var pmcPocketTpl = DEFAULT_POCKETS;
if (IsUnheardProfile(profile) || HasCompletedOldPatterns(profile))
{
pmcPocketTpl = UNHEARD_POCKETS;
}
if (pmcPocketStatus == PocketStatus.Missing)
{
if (items != null && pmcEquipment != null)
{
items.Add(CreatePocketItem(pmcEquipment, pmcPocketTpl));
MoveItemsToSortingTable(items, pmcSortingTable);
}
}
else if (pmcPocketStatus == PocketStatus.Invalid)
{
foreach (var item in items.OfType<JsonObject>())
{
if (
item.TryGetPropertyValue("slotId", out var slotNode)
&& slotNode is JsonValue slotNodeValue
&& slotNodeValue.TryGetValue<string>(out var slotId)
&& slotId == "Pockets"
)
{
item["_tpl"] = pmcPocketTpl;
MoveItemsToSortingTable(items, pmcSortingTable);
}
}
}
}
if (scavPocketStatus != PocketStatus.Valid)
{
var scavItems = profile["characters"]?["scav"]?["Inventory"]?["items"] as JsonArray;
var scavInventory = profile["characters"]?["scav"]?["Inventory"] as JsonObject;
var scavEquipment = scavInventory?["equipment"]?.GetValue<string>();
if (scavPocketStatus == PocketStatus.Missing)
{
if (scavItems != null && scavEquipment != null)
{
scavItems.Add(CreatePocketItem(scavEquipment, DEFAULT_POCKETS));
}
}
else if (scavPocketStatus == PocketStatus.Invalid)
{
foreach (var item in scavItems.OfType<JsonObject>())
{
if (
item.TryGetPropertyValue("slotId", out var slotNode)
&& slotNode is JsonValue slotNodeValue
&& slotNodeValue.TryGetValue<string>(out var slotId)
&& slotId == "Pockets"
)
{
item["_tpl"] = DEFAULT_POCKETS;
}
}
}
}
return base.Migrate(profile);
}
}