0
0
mirror of https://github.com/sp-tarkov/modules.git synced 2025-02-13 03:30:44 -05:00

Add handling of trader service requirements (!86)

This is primarily handled by the server, but the client does have precautionary code in it

Co-authored-by: DrakiaXYZ <565558+TheDgtl@users.noreply.github.com>
Reviewed-on: SPT-AKI/Modules#86
Co-authored-by: DrakiaXYZ <drakiaxyz@noreply.dev.sp-tarkov.com>
Co-committed-by: DrakiaXYZ <drakiaxyz@noreply.dev.sp-tarkov.com>
This commit is contained in:
DrakiaXYZ 2024-02-25 23:08:20 +00:00 committed by chomp
parent 9328c4c956
commit f7ded0abf3
2 changed files with 78 additions and 4 deletions

View File

@ -17,5 +17,20 @@ namespace Aki.SinglePlayer.Utils.TraderServices
[JsonProperty("itemsToReceive")] [JsonProperty("itemsToReceive")]
public MongoID[] ItemsToReceive { get; set; } public MongoID[] ItemsToReceive { get; set; }
[JsonProperty("requirements")]
public TraderServiceRequirementsModel Requirements { get; set; }
}
public class TraderServiceRequirementsModel
{
[JsonProperty("completedQuests")]
public string[] CompletedQuests { get; set; }
[JsonProperty("standings")]
public Dictionary<string, float> Standings { get; set; }
[JsonProperty("side")]
public ESideType Side { get; set; }
} }
} }

View File

@ -1,12 +1,18 @@
using Aki.Common.Http; using Aki.Common.Http;
using Comfort.Common; using Comfort.Common;
using EFT; using EFT;
using EFT.Quests;
using HarmonyLib;
using Newtonsoft.Json; using Newtonsoft.Json;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine; using UnityEngine;
using static BackendConfigSettingsClass; using static BackendConfigSettingsClass;
using TraderServiceClass = GClass1794; using TraderServiceClass = GClass1794;
using QuestDictClass = GClass2133<string>;
using StandingListClass = GClass2135<float>;
namespace Aki.SinglePlayer.Utils.TraderServices namespace Aki.SinglePlayer.Utils.TraderServices
{ {
@ -34,10 +40,12 @@ namespace Aki.SinglePlayer.Utils.TraderServices
private Dictionary<ETraderServiceType, Dictionary<string, bool>> _servicePurchased { get; set; } private Dictionary<ETraderServiceType, Dictionary<string, bool>> _servicePurchased { get; set; }
private HashSet<string> _cachedTraders = new HashSet<string>(); private HashSet<string> _cachedTraders = new HashSet<string>();
private FieldInfo _playerQuestControllerField;
public TraderServicesManager() public TraderServicesManager()
{ {
_servicePurchased = new Dictionary<ETraderServiceType, Dictionary<string, bool>>(); _servicePurchased = new Dictionary<ETraderServiceType, Dictionary<string, bool>>();
_playerQuestControllerField = AccessTools.Field(typeof(Player), "_questController");
} }
public void Clear() public void Clear()
@ -90,8 +98,28 @@ namespace Aki.SinglePlayer.Utils.TraderServices
SubServices = new Dictionary<string, int>() SubServices = new Dictionary<string, int>()
}; };
// Convert our format to the backend settings format and store it // Convert our format to the backend settings format
serviceData = new ServiceData(traderService); serviceData = new ServiceData(traderService);
// Populate requirements if provided
if (traderServiceModel.Requirements != null)
{
if (traderServiceModel.Requirements.Standings != null)
{
serviceData.TraderServiceRequirements.Standings = new StandingListClass();
serviceData.TraderServiceRequirements.Standings.AddRange(traderServiceModel.Requirements.Standings);
// BSG has a bug in their code, we _need_ to initialize this if Standings isn't null
serviceData.TraderServiceRequirements.CompletedQuests = new QuestDictClass();
}
if (traderServiceModel.Requirements.CompletedQuests != null)
{
serviceData.TraderServiceRequirements.CompletedQuests = new QuestDictClass();
serviceData.TraderServiceRequirements.CompletedQuests.Concat(traderServiceModel.Requirements.CompletedQuests);
}
}
servicesData[serviceData.ServiceType] = serviceData; servicesData[serviceData.ServiceType] = serviceData;
} }
} }
@ -108,16 +136,47 @@ namespace Aki.SinglePlayer.Utils.TraderServices
continue; continue;
} }
// TODO: We should probably actually calculate this? var IsServiceAvailable = this.IsServiceAvailable(player, servicesDataPair.Value.TraderServiceRequirements);
var CanAfford = true;
// Check whether we've purchased this service yet // Check whether we've purchased this service yet
var traderService = servicesDataPair.Key; var traderService = servicesDataPair.Key;
var WasPurchasedInThisRaid = IsServicePurchased(traderService, traderId); var WasPurchasedInThisRaid = IsServicePurchased(traderService, traderId);
traderInfo.SetServiceAvailability(traderService, CanAfford, WasPurchasedInThisRaid); traderInfo.SetServiceAvailability(traderService, IsServiceAvailable, WasPurchasedInThisRaid);
} }
} }
private bool IsServiceAvailable(Player player, ServiceRequirements requirements)
{
// Handle standing requirements
if (requirements.Standings != null)
{
foreach (var entry in requirements.Standings)
{
if (!player.Profile.TradersInfo.ContainsKey(entry.Key) ||
player.Profile.TradersInfo[entry.Key].Standing < entry.Value)
{
return false;
}
}
}
// Handle quest requirements
if (requirements.CompletedQuests != null)
{
AbstractQuestControllerClass questController = _playerQuestControllerField.GetValue(player) as AbstractQuestControllerClass;
foreach (string questId in requirements.CompletedQuests)
{
var conditional = questController.Quests.GetConditional(questId);
if (conditional == null || conditional.QuestStatus != EQuestStatus.Success)
{
return false;
}
}
}
return true;
}
public void AfterPurchaseTraderService(ETraderServiceType serviceType, AbstractQuestControllerClass questController, string subServiceId = null) public void AfterPurchaseTraderService(ETraderServiceType serviceType, AbstractQuestControllerClass questController, string subServiceId = null)
{ {
GameWorld gameWorld = Singleton<GameWorld>.Instance; GameWorld gameWorld = Singleton<GameWorld>.Instance;