d2e2f04c93
* Fix exception sometimes thrown on save
- Switch back from File.Rename to File.Move, as Rename is throwing exceptions on some users systems
* Change BTR skin to tarcola during Christmas event
* Added comment
* Remove unused using
* Add wipe Response model
* formatting and add Wipe Endpoint to V2
* Format Style Fixes
* Merge pull request #669 from sp-tarkov/Assembly-ref-validation
Validate core assembly reference when loading mods
* removed zombies from customs and interchange + increased infection across other maps that have zombie kill quests
* Don't apply hostility changes to maps without zombies during halloween
`ReplaceBotHostiltiy` has optional map whitelist param
* Updated hostility values for maps with infection:
bosses = hostile to player not to pmc bots
followers = hostile to player not to pmc bots
pmcs = hostile to player + always hostile to scavs
scavs = hostile to player and pmc bots
raiders = hostile to player and pmc bots
Adjusted infection rates to just maps with zombie kill quests
* Format Style Fixes
* Added missing values for event bosses
* Format Style Fixes
* Added missing values for `ravangezryachiyevent`
Fixed preset typo `bossTagillaAgro`
* Format Style Fixes
* Flagged `Night of The Cult` as halloween quest
* Fixed incorrect logic
* Enabled `Night of The Cult` bosses to spawn
* Format Style Fixes
* Addd a new ReleaseCheckService to notify users of updates (#670)
* Addd a new ReleaseCheckService to notify users of updates
- Pulls the latest release from GitHub API to compare the tag against the users current SPT version
- Runs at the very end of the startup process to avoid being pushed off screen by mod logging
- Only notifies of patch version increments, not major or minor increments
- Links the release notes so users can Ctrl+Click to open directly to the upgrade page
- Is run on its own thread, and discards all errors, so as to not impact users without an internet connection or ability to access GitHub
* Formatting
* Use record for the ReleaseInformation class
---------
Co-authored-by: DrakiaXYZ <565558+TheDgtl@users.noreply.github.com>
* ProfileDataService changes:
Added `ClearProfileData()`
Replaced filepath access with `Path.Combine`
Reduced various sources of duplication
* Adjusted `Goons` spawn chance to 20% across `Customs/Lighthouse/Woods/Shoreline`
* Account for compound items in DialogHelper.GetMessageItemContents
* Generate weapon/armor price based on the child item price total
* Added halloween event bosses to april event
* Flagged infected spawns as `ForceSpawn` and ``
* Add migration for invalid pockets
* Default assign IEnumerable
* Post raid effect fixes:
When exiting raid with severe muscle pain, prevent client instructing server to add mild muscle pain
When exiting a raid with effect that has a timer, decrease timer value by amount of time spent in raid
* Updated nuget packages
* Fixed player scav not having correct HP values on limbs #642
* Remove unused record
* Revert "Updated nuget packages"
This reverts commit f6d9d461a6.
* Added `IMP mine detector` to reward and flea blacklist
* Fixed weapon builds not overwriting existing #654
Cleaned up `SaveWeaponBuild` and `SaveEquipmentBuild`
---------
Co-authored-by: DrakiaXYZ <565558+TheDgtl@users.noreply.github.com>
Co-authored-by: Chomp <27521899+chompDev@users.noreply.github.com>
Co-authored-by: Chomp <dev@dev.sp-tarkov.com>
Co-authored-by: CWX <CWXDEV@outlook.com>
Co-authored-by: sp-tarkov-bot <singleplayertarkov@gmail.com>
Co-authored-by: Cj <161484149+CJ-SPT@users.noreply.github.com>
Co-authored-by: Tyfon <29051038+tyfon7@users.noreply.github.com>
Co-authored-by: Archangel <jesse@archangel.wtf>
280 lines
9.2 KiB
C#
280 lines
9.2 KiB
C#
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);
|
|
}
|
|
}
|