diff --git a/Libraries/Core/Helpers/HideoutHelper.cs b/Libraries/Core/Helpers/HideoutHelper.cs
index 6f0ec933..e20703af 100644
--- a/Libraries/Core/Helpers/HideoutHelper.cs
+++ b/Libraries/Core/Helpers/HideoutHelper.cs
@@ -49,7 +49,7 @@ public class HideoutHelper(
///
///
///
- public bool IsProductionType(Productive productive)
+ public bool IsProductionType(Production Production)
{
throw new NotImplementedException();
}
@@ -189,7 +189,7 @@ public class HideoutHelper(
throw new NotImplementedException();
}
- protected void FlagCultistCircleCraftAsComplete(Productive production)
+ protected void FlagCultistCircleCraftAsComplete(Production Production)
{
throw new NotImplementedException();
}
@@ -342,7 +342,7 @@ public class HideoutHelper(
protected void UpdateBitcoinFarm(
PmcData playerProfile,
- Productive btcProduction,
+ Production btcProduction,
int btcFarmCGs,
bool isGeneratorOn)
{
@@ -431,7 +431,7 @@ public class HideoutHelper(
throw new NotImplementedException();
}
- public bool IsProduction(Productive productive)
+ public bool IsProduction(Production Production)
{
throw new NotImplementedException();
}
diff --git a/Libraries/Core/Models/Eft/Common/Tables/BotBase.cs b/Libraries/Core/Models/Eft/Common/Tables/BotBase.cs
index 152180ca..5828618e 100644
--- a/Libraries/Core/Models/Eft/Common/Tables/BotBase.cs
+++ b/Libraries/Core/Models/Eft/Common/Tables/BotBase.cs
@@ -545,7 +545,7 @@ public record HideoutImprovement
public long? ImproveCompleteTimestamp { get; set; }
}
-public record Productive
+public record Production // use this instead of productive and scavcase
{
public List? Products { get; set; }
@@ -592,17 +592,7 @@ public record Productive
// Craft is cultist circle sacrifice
[JsonPropertyName("sptIsCultistCircle")]
public bool? SptIsCultistCircle { get; set; }
-}
-
-public record Production : Productive
-{
- public string? RecipeId { get; set; }
- public int? SkipTime { get; set; }
- public int? ProductionTime { get; set; }
-}
-
-public record ScavCase : Productive
-{
+
public string? RecipeId { get; set; }
}
diff --git a/Libraries/Core/Models/Eft/ItemEvent/ItemEventRouterBase.cs b/Libraries/Core/Models/Eft/ItemEvent/ItemEventRouterBase.cs
index c313f44b..b8ff7d7b 100644
--- a/Libraries/Core/Models/Eft/ItemEvent/ItemEventRouterBase.cs
+++ b/Libraries/Core/Models/Eft/ItemEvent/ItemEventRouterBase.cs
@@ -54,7 +54,7 @@ public record ProfileChange
public ItemChanges? Items { get; set; }
[JsonPropertyName("production")]
- public Dictionary? Production { get; set; }
+ public Dictionary? Production { get; set; }
/** Hideout area improvement id */
[JsonPropertyName("improvements")]
diff --git a/Libraries/Core/Routers/EventOutputHolder.cs b/Libraries/Core/Routers/EventOutputHolder.cs
index 6a433c66..469ddc02 100644
--- a/Libraries/Core/Routers/EventOutputHolder.cs
+++ b/Libraries/Core/Routers/EventOutputHolder.cs
@@ -1,7 +1,9 @@
using SptCommon.Annotations;
using Core.Helpers;
+using Core.Models.Eft.Common;
using Core.Models.Eft.Common.Tables;
using Core.Models.Eft.ItemEvent;
+using Core.Models.Utils;
using Core.Utils;
using Core.Utils.Cloners;
@@ -10,18 +12,22 @@ namespace Core.Routers;
[Injectable]
public class EventOutputHolder
{
+ protected ISptLogger _logger;
protected ProfileHelper _profileHelper;
protected TimeUtil _timeUtil;
protected ICloner _cloner;
protected Dictionary _outputStore = new();
+ protected Dictionary> _clientActiveSessionStorage = new();
public EventOutputHolder(
+ ISptLogger logger,
ProfileHelper profileHelper,
TimeUtil timeUtil,
ICloner cloner
)
{
+ _logger = logger;
_profileHelper = profileHelper;
_timeUtil = timeUtil;
_cloner = cloner;
@@ -46,37 +52,149 @@ public class EventOutputHolder
{
var pmcProfile = _profileHelper.GetPmcProfile(sessionId);
- _outputStore.Add(sessionId, new ItemEventRouterResponse
- {
- ProfileChanges = new Dictionary()
+ _outputStore.Add(
+ sessionId,
+ new ItemEventRouterResponse
{
+ ProfileChanges = new Dictionary()
{
- sessionId, new ProfileChange
{
- Id = sessionId,
- Experience = pmcProfile.Info.Experience,
- Quests = [],
- RagFairOffers = [],
- WeaponBuilds = [],
- EquipmentBuilds = [],
- Items = new ItemChanges(){ NewItems = [], ChangedItems = [], DeletedItems = []},
- Production = new Dictionary(),
- Improvements = new Dictionary(),
- Skills = new Skills{ Common = [], Mastering = [], Points = 0},
- Health = _cloner.Clone(pmcProfile.Health),
- TraderRelations = new Dictionary(),
- RecipeUnlocked = {},
- QuestsStatus = []
+ sessionId, new ProfileChange
+ {
+ Id = sessionId,
+ Experience = pmcProfile.Info.Experience,
+ Quests = [],
+ RagFairOffers = [],
+ WeaponBuilds = [],
+ EquipmentBuilds = [],
+ Items = new ItemChanges { NewItems = [], ChangedItems = [], DeletedItems = [] },
+ Production = new Dictionary(),
+ Improvements = new Dictionary(),
+ Skills = new Skills { Common = [], Mastering = [], Points = 0 },
+ Health = _cloner.Clone(pmcProfile.Health),
+ TraderRelations = new Dictionary(),
+ RecipeUnlocked = { },
+ QuestsStatus = []
+ }
}
- }
- },
- Warnings = {}
- });
+ },
+ Warnings = { }
+ }
+ );
}
public void UpdateOutputProperties(string sessionId)
{
- throw new NotImplementedException();
+ PmcData pmcData = _profileHelper.GetPmcProfile(sessionId);
+ ProfileChange profileChanges = _outputStore[sessionId].ProfileChanges[sessionId];
+
+ profileChanges.Experience = pmcData.Info.Experience;
+ profileChanges.Health = _cloner.Clone(pmcData.Health);
+ profileChanges.Skills.Common = _cloner.Clone(pmcData.Skills.Common); // Always send skills for Item event route response
+ profileChanges.Skills.Mastering = _cloner.Clone(pmcData.Skills.Mastering);
+
+ // Clone productions to ensure we preseve the profile jsons data
+ profileChanges.Production = GetProductionsFromProfileAndFlagComplete(
+ _cloner.Clone(pmcData.Hideout.Production),
+ sessionId
+ );
+ profileChanges.Improvements = _cloner.Clone(GetImprovementsFromProfileAndFlagComplete(pmcData));
+ profileChanges.TraderRelations = ConstructTraderRelations(pmcData.TradersInfo);
+
+ ResetMoneyTransferLimit(pmcData.MoneyTransferLimitData);
+ profileChanges.MoneyTransferLimitData = pmcData.MoneyTransferLimitData;
+
+ // Fixes container craft from water collector not resetting after collection + removed completed normal crafts
+ CleanUpCompleteCraftsInProfile(pmcData.Hideout.Production);
+ }
+
+ private void CleanUpCompleteCraftsInProfile(Dictionary? productions)
+ {
+ foreach (var production in productions)
+ {
+ if ((production.Value.SptIsComplete ?? false) && (production.Value.SptIsContinuous ?? false))
+ {
+ // Water collector / Bitcoin etc
+ production.Value.SptIsComplete = false;
+ production.Value.Progress = 0;
+ production.Value.StartTimestamp = _timeUtil.GetTimeStamp().ToString();
+ }
+ else if (!production.Value.InProgress ?? false)
+ {
+ // Normal completed craft, delete
+ productions.Remove(production.Key);
+ }
+ }
+ }
+
+ private Dictionary? GetImprovementsFromProfileAndFlagComplete(PmcData pmcData)
+ {
+ foreach (var improvementKey in pmcData.Hideout.Improvements)
+ {
+ var improvement = pmcData.Hideout.Improvements[improvementKey.Key];
+
+ // Skip completed
+ if (improvement.Completed ?? false)
+ {
+ continue;
+ }
+
+ if (improvement.ImproveCompleteTimestamp < _timeUtil.GetTimeStamp())
+ {
+ improvement.Completed = true;
+ }
+ }
+
+ return pmcData.Hideout.Improvements;
+ }
+
+ private Dictionary? GetProductionsFromProfileAndFlagComplete(Dictionary? productions, string sessionId)
+ {
+ foreach (var production in productions)
+ {
+ if (production.Value is null)
+ {
+ // Could be cancelled production, skip item to save processing
+ continue;
+ }
+
+ // Complete and is Continuous e.g. water collector
+ if ((production.Value.SptIsComplete ?? false) && (production.Value.SptIsContinuous ?? false))
+ {
+ continue;
+ }
+
+ // Skip completed
+ if (!production.Value.InProgress ?? false)
+ {
+ continue;
+ }
+
+ // Client informed of craft, remove from data returned
+ Dictionary? storageForSessionId = null;
+ if (!_clientActiveSessionStorage.TryGetValue(sessionId, out storageForSessionId))
+ {
+ _clientActiveSessionStorage.Add(sessionId, new Dictionary());
+ storageForSessionId = _clientActiveSessionStorage[sessionId];
+ }
+
+ // Ensure we don't inform client of production again
+ if (storageForSessionId[production.Key])
+ {
+ productions.Remove(production.Key);
+
+ continue;
+ }
+
+ // Flag started craft as having been seen by client so it won't happen subsequent times
+ if (production.Value.Progress > 0 && !storageForSessionId[production.Key])
+ {
+ storageForSessionId[production.Key] = true;
+ }
+ }
+
+ // Return undefined if there's no crafts to send to client to match live behaviour
+ return productions.Keys.Count > 0 ? productions : null;
}
private void ResetMoneyTransferLimit(MoneyTransferLimits limit)
@@ -90,13 +208,16 @@ public class EventOutputHolder
private Dictionary ConstructTraderRelations(Dictionary traderData)
{
- return traderData.ToDictionary(trader => trader.Key, trader => new TraderData()
- {
- SalesSum = trader.Value.SalesSum,
- Disabled = trader.Value.Disabled,
- Loyalty = trader.Value.LoyaltyLevel,
- Standing = trader.Value.Standing,
- Unlocked = trader.Value.Unlocked,
- });
+ return traderData.ToDictionary(
+ trader => trader.Key,
+ trader => new TraderData()
+ {
+ SalesSum = trader.Value.SalesSum,
+ Disabled = trader.Value.Disabled,
+ Loyalty = trader.Value.LoyaltyLevel,
+ Standing = trader.Value.Standing,
+ Unlocked = trader.Value.Unlocked,
+ }
+ );
}
}