From 4dec2b4e7b8cf7dcbd1957db82e0582a666c2a9d Mon Sep 17 00:00:00 2001 From: Chomp Date: Sun, 12 Jan 2025 19:44:23 +0000 Subject: [PATCH 1/9] Implemented `QuestHelper` methods --- Core/Helpers/QuestHelper.cs | 51 ++++++++++++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/Core/Helpers/QuestHelper.cs b/Core/Helpers/QuestHelper.cs index 1b8c1f27..4234afa4 100644 --- a/Core/Helpers/QuestHelper.cs +++ b/Core/Helpers/QuestHelper.cs @@ -5,6 +5,8 @@ using Core.Models.Eft.Hideout; using Core.Models.Eft.ItemEvent; using Core.Models.Eft.Quests; using Core.Models.Enums; +using Core.Models.Spt.Config; +using Core.Servers; using Core.Services; using Core.Utils; using ILogger = Core.Models.Utils.ILogger; @@ -19,19 +21,26 @@ public class QuestHelper private readonly DatabaseService _databaseService; private readonly QuestConditionHelper _questConditionHelper; private readonly ProfileHelper _profileHelper; + private readonly LocalisationService _localisationService; + private readonly QuestConfig _questConfig; public QuestHelper( ILogger logger, TimeUtil timeUtil, DatabaseService databaseService, QuestConditionHelper questConditionHelper, - ProfileHelper profileHelper) + ProfileHelper profileHelper, + LocalisationService localisationService, + ConfigServer configServer) { _logger = logger; _timeUtil = timeUtil; _databaseService = databaseService; _questConditionHelper = questConditionHelper; _profileHelper = profileHelper; + _localisationService = localisationService; + + _questConfig = configServer.GetConfig(ConfigTypes.QUEST); } /// @@ -53,7 +62,30 @@ public class QuestHelper /// true if player level is greater than or equal to quest public bool DoesPlayerLevelFulfilCondition(double playerLevel, QuestCondition condition) { - throw new System.NotImplementedException(); + if (condition.ConditionType == "Level") + { + var conditionValue = double.Parse(condition.Value.ToString()); + switch (condition.CompareMethod) + { + case ">=": + return playerLevel >= conditionValue; + case ">": + return playerLevel > conditionValue; + case "<": + return playerLevel < conditionValue; + case "<=": + return playerLevel <= conditionValue; + case "=": + return playerLevel == conditionValue; + default: + _logger.Error( + _localisationService.GetText("quest-unable_to_find_compare_condition", condition.CompareMethod)); + + return false; + } + } + + return true; } /// @@ -189,7 +221,20 @@ public class QuestHelper */ public bool QuestIsForOtherSide(string playerSide, string questId) { - throw new NotImplementedException(); + var isUsec = playerSide.ToLower() == "usec"; + if (isUsec && _questConfig.BearOnlyQuests.Contains(questId)) + { + // Player is usec and quest is bear only, skip + return true; + } + + if (!isUsec && _questConfig.UsecOnlyQuests.Contains(questId)) + { + // Player is bear and quest is usec only, skip + return true; + } + + return false; } /** From 7b1d9e9d7147fd812f05ad6bf52f2eaabcb6a082 Mon Sep 17 00:00:00 2001 From: Chomp Date: Sun, 12 Jan 2025 20:01:57 +0000 Subject: [PATCH 2/9] Implemented `DialogueController` functionality --- Core/Callbacks/DialogueCallbacks.cs | 2 +- Core/Controllers/DialogueController.cs | 66 +++++++++++++++++++++-- Core/Models/Eft/Common/Tables/BotBase.cs | 4 +- Core/Models/Eft/Profile/UserDialogInfo.cs | 7 ++- 4 files changed, 70 insertions(+), 9 deletions(-) diff --git a/Core/Callbacks/DialogueCallbacks.cs b/Core/Callbacks/DialogueCallbacks.cs index b6180142..b1fc5ab8 100644 --- a/Core/Callbacks/DialogueCallbacks.cs +++ b/Core/Callbacks/DialogueCallbacks.cs @@ -1,4 +1,4 @@ -using Core.Annotations; +using Core.Annotations; using Core.Controllers; using Core.DI; using Core.Models.Eft.Common; diff --git a/Core/Controllers/DialogueController.cs b/Core/Controllers/DialogueController.cs index cd7d0e55..aff29f75 100644 --- a/Core/Controllers/DialogueController.cs +++ b/Core/Controllers/DialogueController.cs @@ -1,14 +1,28 @@ using Core.Annotations; +using Core.Helpers; using Core.Models.Eft.Dialog; using Core.Models.Eft.HttpResponse; using Core.Models.Eft.Profile; using Core.Models.Enums; +using Core.Servers; +using static System.Runtime.InteropServices.JavaScript.JSType; namespace Core.Controllers; [Injectable] public class DialogueController { + private readonly DialogueHelper _dialogueHelper; + private readonly SaveServer _saveServer; + + public DialogueController( + DialogueHelper dialogueHelper, + SaveServer saveServer) + { + _dialogueHelper = dialogueHelper; + _saveServer = saveServer; + } + /// /// /// @@ -47,7 +61,13 @@ public class DialogueController /// list of dialogs public List GenerateDialogueList(string sessionId) { - throw new NotImplementedException(); + var data = new List(); + foreach (var dialogueId in _dialogueHelper.GetDialogsForProfile(sessionId)) + { + data.Add(GetDialogueInfo(dialogueId.Key, sessionId)); + } + + return data; } /// @@ -60,7 +80,20 @@ public class DialogueController string dialogueId, string sessionId) { - throw new NotImplementedException(); + var dialogs = _dialogueHelper.GetDialogsForProfile(sessionId); + var dialogue = dialogs.GetValueOrDefault(dialogueId); + + var result = new DialogueInfo { + Id = dialogueId, + Type = dialogue.Type ?? MessageType.NPC_TRADER, + Message = _dialogueHelper.GetMessagePreview(dialogue), + New = dialogue.New, + AttachmentsNew = dialogue.AttachmentsNew, + Pinned = dialogue.Pinned, + Users = GetDialogueUsers(dialogue, dialogue.Type.Value, sessionId), + }; + + return result; } /// @@ -71,11 +104,36 @@ public class DialogueController /// Player id /// UserDialogInfo list public List GetDialogueUsers( - Dialogue dialogue, + Dialogue dialog, MessageType messageType, string sessionId) { - throw new NotImplementedException(); + var profile = _saveServer.GetProfile(sessionId); + + // User to user messages are special in that they need the player to exist in them, add if they don't + if ( + messageType == MessageType.USER_MESSAGE && + !dialog.Users.Any((userDialog) => userDialog.Id == profile.CharacterData.PmcData.SessionId)) + { + // nullguard + dialog.Users ??= []; + + dialog.Users.Add( new UserDialogInfo + { + Id = profile.CharacterData.PmcData.SessionId, + Aid = profile.CharacterData.PmcData.Aid, + Info = new UserDialogDetails + { + Level = profile.CharacterData.PmcData.Info.Level, + Nickname = profile.CharacterData.PmcData.Info.Nickname, + Side = profile.CharacterData.PmcData.Info.Side, + MemberCategory = profile.CharacterData.PmcData.Info.MemberCategory, + SelectedMemberCategory = profile.CharacterData.PmcData.Info.SelectedMemberCategory, + }, + }); + } + + return dialog.Users; } /// diff --git a/Core/Models/Eft/Common/Tables/BotBase.cs b/Core/Models/Eft/Common/Tables/BotBase.cs index 20c16a29..c283291f 100644 --- a/Core/Models/Eft/Common/Tables/BotBase.cs +++ b/Core/Models/Eft/Common/Tables/BotBase.cs @@ -14,7 +14,7 @@ public class BotBase [JsonPropertyName("aid")] [JsonConverter(typeof(StringToNumberFactoryConverter))] - public double? Aid { get; set; } + public int? Aid { get; set; } /** SPT property - use to store player id - TODO - move to AID ( account id as guid of choice) */ [JsonPropertyName("sessionId")] @@ -153,7 +153,7 @@ public class Info public bool? HasCoopExtension { get; set; } public bool? HasPveGame { get; set; } public string? Voice { get; set; } - public double? Level { get; set; } + public int? Level { get; set; } public double? Experience { get; set; } [JsonConverter(typeof(StringToNumberFactoryConverter))] public long? RegistrationDate { get; set; } diff --git a/Core/Models/Eft/Profile/UserDialogInfo.cs b/Core/Models/Eft/Profile/UserDialogInfo.cs index 030cc515..21fa6bd6 100644 --- a/Core/Models/Eft/Profile/UserDialogInfo.cs +++ b/Core/Models/Eft/Profile/UserDialogInfo.cs @@ -1,10 +1,13 @@ -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; using Core.Models.Enums; namespace Core.Models.Eft.Profile; public class UserDialogInfo { + /// + /// _id + /// [JsonPropertyName("_id")] public string? Id { get; set; } @@ -31,4 +34,4 @@ public class UserDialogDetails [JsonPropertyName("SelectedMemberCategory")] public MemberCategory? SelectedMemberCategory { get; set; } -} \ No newline at end of file +} From 5f98f16cd52aa555e66bd7be84bee91f17d26c76 Mon Sep 17 00:00:00 2001 From: Chomp Date: Sun, 12 Jan 2025 20:03:44 +0000 Subject: [PATCH 3/9] Stubbed out `GetCurrentGroup` --- Core/Controllers/GameController.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Core/Controllers/GameController.cs b/Core/Controllers/GameController.cs index 38b8d18a..1bb8a546 100644 --- a/Core/Controllers/GameController.cs +++ b/Core/Controllers/GameController.cs @@ -252,7 +252,10 @@ public class GameController public CurrentGroupResponse GetCurrentGroup(string sessionId) { - throw new NotImplementedException(); + return new CurrentGroupResponse + { + Squad = [] + }; } From 85050f69c2b71a76ae454824931e3b01c28f3908 Mon Sep 17 00:00:00 2001 From: Chomp Date: Sun, 12 Jan 2025 20:06:24 +0000 Subject: [PATCH 4/9] Implemented `GetValidGameVersion` and `GetServer` --- Core/Controllers/GameController.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/Core/Controllers/GameController.cs b/Core/Controllers/GameController.cs index 1bb8a546..85b681cb 100644 --- a/Core/Controllers/GameController.cs +++ b/Core/Controllers/GameController.cs @@ -241,7 +241,15 @@ public class GameController /// public List GetServer(string sessionId) { - throw new NotImplementedException(); + return + [ + new ServerDetails + { + Ip = _httpConfig.BackendIp, + Port = _httpConfig.BackendPort + } + + ]; } /// @@ -266,7 +274,11 @@ public class GameController /// public CheckVersionResponse GetValidGameVersion(string sessionId) { - throw new NotImplementedException(); + return new CheckVersionResponse + { + IsValid = true, + LatestVersion = _coreConfig.CompatibleTarkovVersion + }; } /// From 85482b34d7a214106f53c142a0de03c95d806f31 Mon Sep 17 00:00:00 2001 From: Chomp Date: Sun, 12 Jan 2025 20:19:48 +0000 Subject: [PATCH 5/9] Implemented `GetFriendList()` --- Core/Controllers/DialogueController.cs | 34 +++++++++++++++++-- .../Eft/Profile/SearchFriendResponse.cs | 27 +++------------ 2 files changed, 37 insertions(+), 24 deletions(-) diff --git a/Core/Controllers/DialogueController.cs b/Core/Controllers/DialogueController.cs index aff29f75..b19095f3 100644 --- a/Core/Controllers/DialogueController.cs +++ b/Core/Controllers/DialogueController.cs @@ -1,11 +1,11 @@ using Core.Annotations; using Core.Helpers; +using Core.Helpers.Dialogue; using Core.Models.Eft.Dialog; using Core.Models.Eft.HttpResponse; using Core.Models.Eft.Profile; using Core.Models.Enums; using Core.Servers; -using static System.Runtime.InteropServices.JavaScript.JSType; namespace Core.Controllers; @@ -13,13 +13,17 @@ namespace Core.Controllers; public class DialogueController { private readonly DialogueHelper _dialogueHelper; + private readonly ProfileHelper _profileHelper; private readonly SaveServer _saveServer; + private readonly List _dialogueChatBots; public DialogueController( DialogueHelper dialogueHelper, + ProfileHelper profileHelper, SaveServer saveServer) { _dialogueHelper = dialogueHelper; + _profileHelper = profileHelper; _saveServer = saveServer; } @@ -49,7 +53,33 @@ public class DialogueController /// GetFriendListDataResponse public GetFriendListDataResponse GetFriendList(string sessionId) { - throw new NotImplementedException(); + // Add all chatbots to the friends list + var friends = _dialogueChatBots.Select((bot) => bot.GetChatBot()).ToList(); + + // Add any friends the user has after the chatbots + var profile = _profileHelper.GetFullProfile(sessionId); + if (profile?.FriendProfileIds is not null) + { + foreach (var friendId in profile.FriendProfileIds) { + var friendProfile = _profileHelper.GetChatRoomMemberFromSessionId(friendId); + if (friendProfile is not null) + { + friends.Add(new UserDialogInfo + { + Id = friendProfile.Id, + Aid = friendProfile.Aid, + Info = friendProfile.Info, + } ); + } + } + } + + return new GetFriendListDataResponse + { + Friends = friends, + Ignore = [], + InIgnoreList = [] + }; } /// diff --git a/Core/Models/Eft/Profile/SearchFriendResponse.cs b/Core/Models/Eft/Profile/SearchFriendResponse.cs index dcf9c1f9..5de6d376 100644 --- a/Core/Models/Eft/Profile/SearchFriendResponse.cs +++ b/Core/Models/Eft/Profile/SearchFriendResponse.cs @@ -1,35 +1,18 @@ using System.Text.Json.Serialization; -using Core.Models.Enums; namespace Core.Models.Eft.Profile; +/// +/// Identical to `UserDialogInfo` +/// public class SearchFriendResponse { [JsonPropertyName("_id")] public string? Id { get; set; } [JsonPropertyName("aid")] - public double? Aid { get; set; } + public int? Aid { get; set; } [JsonPropertyName("Info")] - public FriendInfo? Info { get; set; } -} - -// NOTE: Renamed from `Info` because of a name collision. -public class FriendInfo -{ - [JsonPropertyName("Nickname")] - public string? Nickname { get; set; } - - [JsonPropertyName("Side")] - public string? Side { get; set; } - - [JsonPropertyName("Level")] - public double? Level { get; set; } - - [JsonPropertyName("MemberCategory")] - public MemberCategory? MemberCategory { get; set; } - - [JsonPropertyName("SelectedMemberCategory")] - public MemberCategory? SelectedMemberCategory { get; set; } + public UserDialogDetails? Info { get; set; } } From 42ed7683c920cd1f1b28dbf9bc945dbbfa12b6c0 Mon Sep 17 00:00:00 2001 From: Chomp Date: Sun, 12 Jan 2025 20:35:07 +0000 Subject: [PATCH 6/9] Implemented `QuestConditionHelper` --- Core/Helpers/QuestConditionHelper.cs | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/Core/Helpers/QuestConditionHelper.cs b/Core/Helpers/QuestConditionHelper.cs index d31764b6..6a4f43cb 100644 --- a/Core/Helpers/QuestConditionHelper.cs +++ b/Core/Helpers/QuestConditionHelper.cs @@ -1,4 +1,4 @@ -using Core.Annotations; +using Core.Annotations; using Core.Models.Eft.Common.Tables; namespace Core.Helpers; @@ -10,28 +10,28 @@ public class QuestConditionHelper List questConditions, Func> furtherFilter = null) { - throw new NotImplementedException(); + return FilterConditions(questConditions, "Quest", furtherFilter); } public List GetLevelConditions( List questConditions, Func> furtherFilter = null) { - throw new NotImplementedException(); + return FilterConditions(questConditions, "Level", furtherFilter); } public List GetLoyaltyConditions( List questConditions, Func> furtherFilter = null) { - throw new NotImplementedException(); + return FilterConditions(questConditions, "TraderLoyalty", furtherFilter); } public List GetStandingConditions( List questConditions, Func> furtherFilter = null) { - throw new NotImplementedException(); + return FilterConditions(questConditions, "TraderStanding", furtherFilter); } protected List FilterConditions( @@ -39,6 +39,15 @@ public class QuestConditionHelper string questType, Func> furtherFilter = null) { - throw new NotImplementedException(); + var filteredQuests = questConditions.Where((c) => { + if (c.ConditionType == questType) + { + // return true or run the passed in function + return furtherFilter is null || furtherFilter(c).Any(); + } + return false; + }).ToList(); + + return filteredQuests; } } From 5e7b4e35385be86461d9eb8e36a89212b618e88b Mon Sep 17 00:00:00 2001 From: CWX <91418059+CWXDEV@users.noreply.github.com> Date: Sun, 12 Jan 2025 20:41:00 +0000 Subject: [PATCH 7/9] more progress (#45) * ignore nulls in json * Implement ProfileActivityService * add temp beta release * implement customizationController * comment out logging * implement gameStart --- Core/Callbacks/ClientLogCallbacks.cs | 12 +++ Core/Controllers/CustomizationController.cs | 98 ++++++++++----------- Core/Controllers/GameController.cs | 71 +++++++++++---- Core/Controllers/PrestigeController.cs | 13 ++- Core/Controllers/TraderController.cs | 3 +- Core/Models/Eft/Game/GameConfigResponse.cs | 6 +- Core/Servers/Http/SptHttpListener.cs | 4 +- Core/Services/PostDbLoadService.cs | 2 +- Core/Services/ProfileActivityService.cs | 35 +++++++- Core/Utils/JsonUtil.cs | 1 + 10 files changed, 167 insertions(+), 78 deletions(-) diff --git a/Core/Callbacks/ClientLogCallbacks.cs b/Core/Callbacks/ClientLogCallbacks.cs index f1a46793..1a2c46f7 100644 --- a/Core/Callbacks/ClientLogCallbacks.cs +++ b/Core/Callbacks/ClientLogCallbacks.cs @@ -55,6 +55,18 @@ public class ClientLogCallbacks public string ReleaseNotes() { var data = _configServer.GetConfig(ConfigTypes.CORE).Release; + data.BetaDisclaimerText = "BetaDisclaimerText"; + data.BetaDisclaimerAcceptText = "BetaDisclaimerAcceptText"; + data.ServerModsLoadedText = "ServerModsLoadedText"; + data.ClientModsLoadedText = "clientModsLoadedText"; + data.ClientModsLoadedDebugText = "clientModsLoadedDebugText"; + data.ClientModsLoadedDebugText = "clientModsLoadedDebugText"; + data.IllegalPluginsExceptionText = "IllegalPluginsExceptionText"; + data.ReleaseSummaryText = "ReleaseSummaryText"; + data.IsBeta = false; + data.IsModdable = true; + data.IsModded = false; + // data.betaDisclaimerText = ProgramStatics.MODS // ? this.localisationService.getText("release-beta-disclaimer-mods-enabled") diff --git a/Core/Controllers/CustomizationController.cs b/Core/Controllers/CustomizationController.cs index cdcc8071..0615194c 100644 --- a/Core/Controllers/CustomizationController.cs +++ b/Core/Controllers/CustomizationController.cs @@ -57,10 +57,10 @@ public class CustomizationController { var pmcData = _profileHelper.GetPmcProfile(sessionId); var clothing = _databaseService.GetCustomization(); - var suits = _databaseService.GetTrader(traderId).Suits; + var suits = _databaseService.GetTrader(traderId)?.Suits; - var matchingSuits = suits.Where(s => clothing.ContainsKey(s.SuiteId)).ToList(); - matchingSuits = matchingSuits?.Where(s => clothing[s.SuiteId].Properties.Side.Contains(pmcData.Info.Side)).ToList(); + var matchingSuits = suits.Where(s => clothing.ContainsKey(s?.SuiteId)).ToList(); + matchingSuits = matchingSuits?.Where(s => clothing[s?.SuiteId].Properties.Side.Contains(pmcData?.Info?.Side)).ToList(); if (matchingSuits == null) throw new Exception(_localisationService.GetText("customisation-unable_to_get_trader_suits", traderId)); @@ -83,21 +83,21 @@ public class CustomizationController { var output = _eventOutputHolder.GetOutput(sessionId); - var traderOffer = GetTraderClothingOffer(sessionId, buyClothingRequest.Offer); + var traderOffer = GetTraderClothingOffer(sessionId, buyClothingRequest?.Offer); if (traderOffer == null) { _logger.Error(_localisationService.GetText("customisation-unable_to_find_suit_by_id", buyClothingRequest.Offer)); return output; } - var suitId = traderOffer.SuiteId; + var suitId = traderOffer?.SuiteId; if (OutfitAlreadyPurchased(suitId, sessionId)) { var suitDetails = _databaseService.GetCustomization()[suitId]; _logger.Error(_localisationService.GetText("customisation-item_already_purchased", new { - ItemId = suitDetails.Id, - ItemName = suitDetails.Name, + ItemId = suitDetails?.Id, + ItemName = suitDetails?.Name, })); } @@ -111,7 +111,7 @@ public class CustomizationController private Suit GetTraderClothingOffer(string sessionId, string? offerId) { - var foundSuit = GetAllTraderSuits(sessionId).FirstOrDefault(s => s.Id == offerId); + var foundSuit = GetAllTraderSuits(sessionId).FirstOrDefault(s => s?.Id == offerId); if (foundSuit == null) throw new Exception(_localisationService.GetText("customisation-unable_to_find_suit_with_id", offerId)); @@ -143,61 +143,61 @@ public class CustomizationController /// Client response private void PayForClothingItem(string sessionId, PmcData pmcData, PaymentItemForClothing paymentItemDetails, ItemEventRouterResponse output) { - var inventoryItem = pmcData.Inventory.Items.FirstOrDefault(x => x.Id == paymentItemDetails.Id); + var inventoryItem = pmcData?.Inventory?.Items?.FirstOrDefault(x => x?.Id == paymentItemDetails?.Id); if (inventoryItem == null) { _logger.Error(_localisationService.GetText("customisation-unable_to_find_clothing_item_in_inventory", paymentItemDetails.Id)); return; } - if (paymentItemDetails.Del != null) + if (paymentItemDetails?.Del != null) { - output.ProfileChanges[sessionId].Items.DeletedItems.Add(new Product + output?.ProfileChanges[sessionId]?.Items?.DeletedItems?.Add(new Product { - Id = inventoryItem.Id, - Template = inventoryItem.Template, - ParentId = inventoryItem.ParentId, - SlotId = inventoryItem.SlotId, - Location = (ItemLocation)inventoryItem.Location, - Upd = inventoryItem.Upd + Id = inventoryItem?.Id, + Template = inventoryItem?.Template, + ParentId = inventoryItem?.ParentId, + SlotId = inventoryItem?.SlotId, + Location = (ItemLocation)inventoryItem?.Location, + Upd = inventoryItem?.Upd }); - pmcData.Inventory.Items.Remove(inventoryItem); + pmcData?.Inventory?.Items?.Remove(inventoryItem); } - if (inventoryItem.Upd == null) + if (inventoryItem?.Upd == null) inventoryItem.Upd = new() { StackObjectsCount = 1 }; - if (inventoryItem.Upd.StackObjectsCount == null) + if (inventoryItem?.Upd?.StackObjectsCount == null) inventoryItem.Upd.StackObjectsCount = 1; - if (inventoryItem.Upd.StackObjectsCount == paymentItemDetails.Count) + if (inventoryItem?.Upd?.StackObjectsCount == paymentItemDetails?.Count) { - output.ProfileChanges[sessionId].Items.DeletedItems.Add(new Product + output?.ProfileChanges[sessionId]?.Items?.DeletedItems?.Add(new Product { - Id = inventoryItem.Id, - Template = inventoryItem.Template, - ParentId = inventoryItem.ParentId, - SlotId = inventoryItem.SlotId, - Location = (ItemLocation)inventoryItem.Location, - Upd = inventoryItem.Upd + Id = inventoryItem?.Id, + Template = inventoryItem?.Template, + ParentId = inventoryItem?.ParentId, + SlotId = inventoryItem?.SlotId, + Location = (ItemLocation)inventoryItem?.Location, + Upd = inventoryItem?.Upd }); - pmcData.Inventory.Items.Remove(inventoryItem); + pmcData?.Inventory?.Items?.Remove(inventoryItem); return; } - if (inventoryItem.Upd.StackObjectsCount > paymentItemDetails.Count) + if (inventoryItem.Upd.StackObjectsCount > paymentItemDetails?.Count) { - inventoryItem.Upd.StackObjectsCount -= paymentItemDetails.Count; - output.ProfileChanges[sessionId].Items.ChangedItems.Add(new Product + inventoryItem.Upd.StackObjectsCount -= paymentItemDetails?.Count; + output?.ProfileChanges[sessionId]?.Items?.ChangedItems.Add(new Product { - Id = inventoryItem.Id, - Template = inventoryItem.Template, - ParentId = inventoryItem.ParentId, - SlotId = inventoryItem.SlotId, - Location = (ItemLocation)inventoryItem.Location, - Upd = inventoryItem.Upd + Id = inventoryItem?.Id, + Template = inventoryItem?.Template, + ParentId = inventoryItem?.ParentId, + SlotId = inventoryItem?.SlotId, + Location = (ItemLocation)inventoryItem?.Location, + Upd = inventoryItem?.Upd }); } } @@ -291,7 +291,7 @@ public class CustomizationController throw new Exception($"Unknown game edition given from profile {profile}"); } - var prestigeLevel = profile.CharacterData.PmcData.Info.PrestigeLevel; + var prestigeLevel = profile?.CharacterData?.PmcData?.Info?.PrestigeLevel; if (prestigeLevel != null) { if (prestigeLevel >= 1) @@ -324,10 +324,10 @@ public class CustomizationController /// private string GetGameEdition(SptProfile profile) { - var edition = profile.CharacterData.PmcData.Info.GameVersion; + var edition = profile?.CharacterData?.PmcData?.Info?.GameVersion; if (edition == null) { - var launcherEdition = profile.ProfileInfo.Edition; + var launcherEdition = profile?.ProfileInfo?.Edition; switch (launcherEdition.ToLower()) { case "edge of darkness": @@ -351,18 +351,18 @@ public class CustomizationController /// public ItemEventRouterResponse SetClothing(string sessionId, CustomizationSetRequest request, PmcData pmcData) { - foreach (var customisation in request.Customizations) + foreach (var customisation in request?.Customizations) { switch (customisation.Id) { case "dogTag": - pmcData.Customization.DogTag = customisation.Id; + pmcData.Customization.DogTag = customisation?.Id; break; case "suite": ApplyClothingItemToProfile(customisation, pmcData); break; default: - _logger.Error($"Unhandled customisation type: {customisation.Type}"); + _logger.Error($"Unhandled customisation type: {customisation?.Type}"); break; } } @@ -377,24 +377,24 @@ public class CustomizationController /// Profile to update private void ApplyClothingItemToProfile(CustomizationSetOption customisation, PmcData pmcData) { - var dbSuit = _databaseService.GetCustomization()[customisation.Id]; + var dbSuit = _databaseService.GetCustomization()[customisation?.Id]; if (dbSuit == null) { - _logger.Error($"Unable to find suit customisation id: {customisation.Id}, cannot apply clothing to player profile: {pmcData.Id}"); + _logger.Error($"Unable to find suit customisation id: {customisation?.Id}, cannot apply clothing to player profile: {pmcData.Id}"); return; } if (dbSuit.Parent == "5cd944d01388ce000a659df9") { - pmcData.Customization.Body = dbSuit.Properties.Body; - pmcData.Customization.Hands = dbSuit.Properties.Hands; + pmcData.Customization.Body = dbSuit?.Properties?.Body; + pmcData.Customization.Hands = dbSuit?.Properties?.Hands; return; } if (dbSuit.Parent == "5cd944ca1388ce03a44dc2a4") { - pmcData.Customization.Feet = dbSuit.Properties.Feet; + pmcData.Customization.Feet = dbSuit?.Properties?.Feet; } } } diff --git a/Core/Controllers/GameController.cs b/Core/Controllers/GameController.cs index 85b681cb..c2bcdcaf 100644 --- a/Core/Controllers/GameController.cs +++ b/Core/Controllers/GameController.cs @@ -20,7 +20,9 @@ public class GameController private readonly ILogger _logger; private readonly ConfigServer _configServer; private readonly DatabaseService _databaseService; + private readonly TimeUtil _timeUtil; + // private readonly PreSptModLoader _preSptModLoader; private readonly HttpServerHelper _httpServerHelper; private readonly InventoryHelper _inventoryHelper; @@ -39,7 +41,7 @@ public class GameController private readonly ProfileActivityService _profileActivityService; private readonly ApplicationContext _applicationContext; private readonly ICloner _cloner; - + private readonly CoreConfig _coreConfig; private readonly HttpConfig _httpConfig; private readonly RagfairConfig _ragfairConfig; @@ -68,7 +70,7 @@ public class GameController ProfileActivityService profileActivityService, ApplicationContext applicationContext, ICloner cloner - ) + ) { _logger = logger; _configServer = configServer; @@ -98,7 +100,7 @@ public class GameController _hideoutConfig = configServer.GetConfig(ConfigTypes.HIDEOUT); _botConfig = configServer.GetConfig(ConfigTypes.BOT); } - + /// /// Handle client/game/start /// @@ -110,9 +112,9 @@ public class GameController { // Store client start time in app context _applicationContext.AddValue(ContextVariableType.CLIENT_START_TIMESTAMP, $"{sessionId}_{startTimeStampMs}"); - + _profileActivityService.SetActivityTimestamp(sessionId); - + // repeatableQuests are stored by in profile.Quests due to the responses of the client (e.g. Quests in // offraidData). Since we don't want to clutter the Quests list, we need to remove all completed (failed or // successful) repeatable quests. We also have to remove the Counters from the repeatableQuests @@ -124,7 +126,7 @@ public class GameController if (fullProfile.SptData.Migrations == null) fullProfile.SptData.Migrations = new(); - + if (fullProfile.FriendProfileIds == null) fullProfile.FriendProfileIds = new(); @@ -132,12 +134,12 @@ public class GameController { _inventoryHelper.ValidateInventoryUsesMongoIds(fullProfile.CharacterData.PmcData.Inventory.Items); Migrate39xProfile(fullProfile); - + // flag as migrated fullProfile.SptData.Migrations.Add("39x", _timeUtil.GetTimeStamp()); _logger.Success($"Migration of 3.9.x profile: {fullProfile.ProfileInfo.Username} completed successfully"); } - + // with our method of converting type from array for this prop, we *might* not need this? // if (Array.isArray(fullProfile.characters.pmc.WishList)) { // fullProfile.characters.pmc.WishList = {}; @@ -146,17 +148,17 @@ public class GameController // if (Array.isArray(fullProfile.characters.scav.WishList)) { // fullProfile.characters.scav.WishList = {}; // } - + if (fullProfile.DialogueRecords != null) _profileFixerService.CheckForAndFixDialogueAttachments(fullProfile); - + _logger.Debug($"Started game with session {sessionId} {fullProfile.ProfileInfo.Username}"); var pmcProfile = fullProfile.CharacterData.PmcData; if (_coreConfig.Fixes.FixProfileBreakingInventoryItemIssues) _profileFixerService.FixProfileBreakingInventoryItemIssues(pmcProfile); - + if (pmcProfile.Health != null) UpdateProfileHealthValues(pmcProfile); @@ -165,7 +167,7 @@ public class GameController SendPraporGiftsToNewProfiles(pmcProfile); _profileFixerService.CheckForOrphanedModdedItems(sessionId, fullProfile); } - + _profileFixerService.CheckForAndRemoveInvalidTraders(fullProfile); _profileFixerService.CheckForAndFixPmcProfileIssues(pmcProfile); @@ -175,7 +177,7 @@ public class GameController _hideoutHelper.SetHideoutImprovementsToCompleted(pmcProfile); _hideoutHelper.UnlockHideoutWallInProfile(pmcProfile); } - + LogProfileDetails(fullProfile); SaveActiveModsToProfile(fullProfile); @@ -184,10 +186,10 @@ public class GameController AddPlayerToPmcNames(pmcProfile); CheckForAndRemoveUndefinedDialogues(fullProfile); } - + if (pmcProfile.Skills.Common != null) WarnOnActiveBotReloadSkill(pmcProfile); - + _seasonalEventService.GivePlayerSeasonalGifts(sessionId); } } @@ -214,7 +216,41 @@ public class GameController /// public GameConfigResponse GetGameConfig(string sessionId) { - throw new NotImplementedException(); + var profile = _profileHelper.GetPmcProfile(sessionId); + var gameTime = profile?.Stats?.Eft?.OverallCounters?.Items.FirstOrDefault(c => + c.Key.Contains("LifeTime") && + c.Key.Contains("Pmc")).Value ?? 0D; + + var config = new GameConfigResponse + { + Languages = _databaseService.GetLocales().Languages, + IsNdaFree = false, + IsReportAvailable = false, + IsTwitchEventMember = false, + Language = "en", + Aid = profile.Aid, + Taxonomy = 6, + ActiveProfileId = sessionId, + Backend = new() + { + Lobby = _httpServerHelper.GetBackendUrl(), + Trading = _httpServerHelper.GetBackendUrl(), + Messaging = _httpServerHelper.GetBackendUrl(), + Main = _httpServerHelper.GetBackendUrl(), + RagFair = _httpServerHelper.GetBackendUrl() + }, + UseProtobuf = false, + UtcTime = _timeUtil.GetTimeStamp(), + TotalInGame = gameTime, + SessionMode = "pve", + PurchasedGames = new() + { + IsEftPurchased = true, + IsArenaPurchased = false + } + }; + + return config; } /// @@ -257,7 +293,6 @@ public class GameController /// /// /// - public CurrentGroupResponse GetCurrentGroup(string sessionId) { return new CurrentGroupResponse @@ -265,7 +300,7 @@ public class GameController Squad = [] }; } - + /// /// Handle client/checkVersion diff --git a/Core/Controllers/PrestigeController.cs b/Core/Controllers/PrestigeController.cs index 301fa273..9cd9bf15 100644 --- a/Core/Controllers/PrestigeController.cs +++ b/Core/Controllers/PrestigeController.cs @@ -1,12 +1,23 @@ using Core.Annotations; using Core.Models.Eft.Common; using Core.Models.Eft.Common.Tables; +using Core.Services; namespace Core.Controllers; [Injectable] public class PrestigeController { + private DatabaseService _databaseService; + + public PrestigeController + ( + DatabaseService databaseService + ) + { + _databaseService = databaseService; + } + /// /// Handle /client/prestige/list /// @@ -17,7 +28,7 @@ public class PrestigeController string sessionId, EmptyRequestData info) { - throw new NotImplementedException(); + return _databaseService.GetTemplates().Prestige; } /// diff --git a/Core/Controllers/TraderController.cs b/Core/Controllers/TraderController.cs index 46290be5..3bcfa3a8 100644 --- a/Core/Controllers/TraderController.cs +++ b/Core/Controllers/TraderController.cs @@ -35,7 +35,8 @@ public class TraderController /// Return a list of all traders public List GetAllTraders(string sessionId) { - throw new NotImplementedException(); + var traders = new List(); + } /// diff --git a/Core/Models/Eft/Game/GameConfigResponse.cs b/Core/Models/Eft/Game/GameConfigResponse.cs index d70b8b56..1d3f7b39 100644 --- a/Core/Models/Eft/Game/GameConfigResponse.cs +++ b/Core/Models/Eft/Game/GameConfigResponse.cs @@ -5,7 +5,7 @@ namespace Core.Models.Eft.Game; public class GameConfigResponse { [JsonPropertyName("aid")] - public int? Aid { get; set; } + public double? Aid { get; set; } [JsonPropertyName("lang")] public string? Language { get; set; } @@ -33,7 +33,7 @@ public class GameConfigResponse /** Total in game time */ [JsonPropertyName("totalInGame")] - public int? TotalInGame { get; set; } + public double? TotalInGame { get; set; } [JsonPropertyName("reportAvailable")] public bool? IsReportAvailable { get; set; } @@ -73,4 +73,4 @@ public class Backend [JsonPropertyName("RagFair")] public string? RagFair { get; set; } -} \ No newline at end of file +} diff --git a/Core/Servers/Http/SptHttpListener.cs b/Core/Servers/Http/SptHttpListener.cs index 125973c7..d5e07533 100644 --- a/Core/Servers/Http/SptHttpListener.cs +++ b/Core/Servers/Http/SptHttpListener.cs @@ -120,7 +120,7 @@ public class SptHttpListener : IHttpListener if (IsDebugRequest(req)) { // Send only raw response without transformation SendJson(resp, output, sessionID); - Console.WriteLine($"Response: {output}"); + // Console.WriteLine($"Response: {output}"); // TODO: this.logRequest(req, output); return; } @@ -133,7 +133,7 @@ public class SptHttpListener : IHttpListener // No serializer can handle the request (majority of requests dont), zlib the output and send response back SendZlibJson(resp, output, sessionID); } - Console.WriteLine($"Response: {output}"); + // Console.WriteLine($"Response: {output}"); // TODO: this.LogRequest(req, output); } diff --git a/Core/Services/PostDbLoadService.cs b/Core/Services/PostDbLoadService.cs index d19d5cda..2085e276 100644 --- a/Core/Services/PostDbLoadService.cs +++ b/Core/Services/PostDbLoadService.cs @@ -7,7 +7,7 @@ public class PostDbLoadService { public void PerformPostDbLoadActions() { - throw new NotImplementedException(); + // throw new NotImplementedException(); } protected void AdjustMinReserveRaiderSpawnChance() diff --git a/Core/Services/ProfileActivityService.cs b/Core/Services/ProfileActivityService.cs index 89364a7d..f00fb0d0 100644 --- a/Core/Services/ProfileActivityService.cs +++ b/Core/Services/ProfileActivityService.cs @@ -1,10 +1,19 @@ using Core.Annotations; +using Core.Utils; namespace Core.Services; [Injectable(InjectionType.Singleton)] public class ProfileActivityService { + private TimeUtil _timeUtil; + private Dictionary profileActivityTimestamps = new(); + + public ProfileActivityService(TimeUtil timeUtil) + { + _timeUtil = timeUtil; + } + /** * Was the requested profile active in the last requested minutes * @param sessionId Profile to check @@ -13,7 +22,13 @@ public class ProfileActivityService */ public bool ActiveWithinLastMinutes(string sessionId, int minutes) { - throw new NotImplementedException(); + var currentTimestamp = _timeUtil.GetTimeStamp(); + var storedActivityTimestamp = profileActivityTimestamps[sessionId]; + + if (storedActivityTimestamp != null) + return false; + + return currentTimestamp - storedActivityTimestamp < minutes * 60; } /** @@ -23,7 +38,21 @@ public class ProfileActivityService */ public List GetActiveProfileIdsWithinMinutes(int minutes) { - throw new NotImplementedException(); + var currentTimestamp = _timeUtil.GetTimeStamp(); + var result = new List(); + + foreach (var activity in profileActivityTimestamps ?? new()) + { + var lastActivityTimestamp = activity.Value; + if (lastActivityTimestamp == null) + continue; + + // Profile was active in last x minutes, add to return list + if (currentTimestamp - lastActivityTimestamp < minutes * 60) + result.Add(activity.Key); + } + + return result; } /** @@ -32,6 +61,6 @@ public class ProfileActivityService */ public void SetActivityTimestamp(string sessionId) { - throw new NotImplementedException(); + profileActivityTimestamps[sessionId] = _timeUtil.GetTimeStamp(); } } diff --git a/Core/Utils/JsonUtil.cs b/Core/Utils/JsonUtil.cs index 368771eb..c0df1639 100644 --- a/Core/Utils/JsonUtil.cs +++ b/Core/Utils/JsonUtil.cs @@ -13,6 +13,7 @@ public class JsonUtil private static readonly JsonSerializerOptions jsonSerializerOptionsNoIndent = new() { WriteIndented = false, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, UnmappedMemberHandling = JsonUnmappedMemberHandling.Disallow, Converters = { From 045c1a5eb5c9365d74841eeb843e22955709fed3 Mon Sep 17 00:00:00 2001 From: Chomp Date: Sun, 12 Jan 2025 21:06:05 +0000 Subject: [PATCH 8/9] More implementation of `QuestHelper` made `QId` consistent --- Core/Controllers/TraderController.cs | 2 +- Core/Helpers/QuestHelper.cs | 44 +++++++++++++++++-- Core/Models/Eft/Common/Tables/BotBase.cs | 4 +- Core/Models/Eft/Common/Tables/Quest.cs | 9 ++-- .../Eft/Common/Tables/RepeatableQuests.cs | 4 +- 5 files changed, 52 insertions(+), 11 deletions(-) diff --git a/Core/Controllers/TraderController.cs b/Core/Controllers/TraderController.cs index 3bcfa3a8..ac409434 100644 --- a/Core/Controllers/TraderController.cs +++ b/Core/Controllers/TraderController.cs @@ -36,7 +36,7 @@ public class TraderController public List GetAllTraders(string sessionId) { var traders = new List(); - + return traders; } /// diff --git a/Core/Helpers/QuestHelper.cs b/Core/Helpers/QuestHelper.cs index 4234afa4..e234181e 100644 --- a/Core/Helpers/QuestHelper.cs +++ b/Core/Helpers/QuestHelper.cs @@ -496,11 +496,49 @@ public class QuestHelper /** * Add all quests to a profile with the provided statuses * @param pmcProfile profile to update - * @param statuses statuses quests should have + * @param statuses statuses quests should have added to profile */ public void AddAllQuestsToProfile(PmcData pmcProfile, List statuses) { - throw new NotImplementedException(); + // Iterate over all quests in db + var quests = _databaseService.GetQuests(); + foreach (var (key, questData) in quests) { + // Quest from db matches quests in profile, skip + if (pmcProfile.Quests.Any((x) => x.QId == questData.Id)) + { + continue; + } + + // Create dict of status to add to quest in profile + var statusesDict = new Dictionary(); + foreach (var status in statuses) + { + statusesDict.Add(status, _timeUtil.GetTimeStamp()); + } + + var questRecordToAdd = new QuestStatus { + QId = key, + StartTime = _timeUtil.GetTimeStamp(), + Status = statuses[^1], // Get last status in list as currently active status + StatusTimers = statusesDict, + CompletedConditions = [], + AvailableAfter = 0, + }; + + // Check if the quest already exists in the profile + var existingQuest = pmcProfile.Quests.FirstOrDefault(x => x.QId == key); + if (existingQuest != null) + { + // Update existing quest + existingQuest.Status = questRecordToAdd.Status; + existingQuest.StatusTimers = questRecordToAdd.StatusTimers; + } + else + { + // Add new quest to the profile + pmcProfile.Quests.Add(questRecordToAdd); + } + } } public void FindAndRemoveQuestFromArrayIfExists(string questId, List quests) @@ -623,7 +661,7 @@ public class QuestHelper if (conditionToFulfil.AvailableAfter > 0) { // Compare current time to unlock time for previous quest - prerequisiteQuest.StatusTimers.TryGetValue(prerequisiteQuest.Status.ToString(), out var previousQuestCompleteTime); + prerequisiteQuest.StatusTimers.TryGetValue(prerequisiteQuest.Status.Value, out var previousQuestCompleteTime); var unlockTime = previousQuestCompleteTime + conditionToFulfil.AvailableAfter; if (unlockTime > _timeUtil.GetTimeStamp()) { diff --git a/Core/Models/Eft/Common/Tables/BotBase.cs b/Core/Models/Eft/Common/Tables/BotBase.cs index c283291f..cc1913ce 100644 --- a/Core/Models/Eft/Common/Tables/BotBase.cs +++ b/Core/Models/Eft/Common/Tables/BotBase.cs @@ -57,7 +57,7 @@ public class BotBase public Hideout? Hideout { get; set; } [JsonPropertyName("Quests")] - public List? Quests { get; set; } + public List? Quests { get; set; } [JsonPropertyName("TradersInfo")] public Dictionary? TradersInfo { get; set; } @@ -710,7 +710,7 @@ public class Quests public QuestStatusEnum? Status { get; set; } [JsonPropertyName("statusTimers")] - public Dictionary? StatusTimers { get; set; } + public Dictionary? StatusTimers { get; set; } /** Property does not exist in live profile data, but is used by ProfileChanges.questsStatus when sent to client */ [JsonPropertyName("completedConditions")] diff --git a/Core/Models/Eft/Common/Tables/Quest.cs b/Core/Models/Eft/Common/Tables/Quest.cs index 57f70325..d72e87aa 100644 --- a/Core/Models/Eft/Common/Tables/Quest.cs +++ b/Core/Models/Eft/Common/Tables/Quest.cs @@ -130,6 +130,9 @@ public class Quest public double ChangeStandingCost { get; set; } } +/// +/// Same as BotBase.Quests +/// public class QuestStatus { [JsonPropertyName("id")] @@ -139,16 +142,16 @@ public class QuestStatus public string? Uid { get; set; } [JsonPropertyName("qid")] - public string? Qid { get; set; } + public string? QId { get; set; } [JsonPropertyName("startTime")] public double? StartTime { get; set; } [JsonPropertyName("status")] - public double? Status { get; set; } + public QuestStatusEnum? Status { get; set; } [JsonPropertyName("statusTimers")] - public Dictionary? StatusTimers { get; set; } + public Dictionary? StatusTimers { get; set; } [JsonPropertyName("completedConditions")] public List? CompletedConditions { get; set; } diff --git a/Core/Models/Eft/Common/Tables/RepeatableQuests.cs b/Core/Models/Eft/Common/Tables/RepeatableQuests.cs index 97aa009f..ad215c08 100644 --- a/Core/Models/Eft/Common/Tables/RepeatableQuests.cs +++ b/Core/Models/Eft/Common/Tables/RepeatableQuests.cs @@ -1,4 +1,4 @@ -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; namespace Core.Models.Eft.Common.Tables; @@ -56,7 +56,7 @@ public class RepeatableQuestStatus public string? Uid { get; set; } [JsonPropertyName("qid")] - public string? Qid { get; set; } + public string? QId { get; set; } [JsonPropertyName("startTime")] public long? StartTime { get; set; } From 2408452b4dce68bc3dd624fb6f96090c482a9d30 Mon Sep 17 00:00:00 2001 From: Chomp Date: Sun, 12 Jan 2025 21:07:28 +0000 Subject: [PATCH 9/9] Added `QuestStatusEnum` to `JsonSerializerOptions` --- Core/Utils/JsonUtil.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Core/Utils/JsonUtil.cs b/Core/Utils/JsonUtil.cs index c0df1639..0ec9f79b 100644 --- a/Core/Utils/JsonUtil.cs +++ b/Core/Utils/JsonUtil.cs @@ -22,7 +22,8 @@ public class JsonUtil new EftEnumConverter(), new EftEnumConverter(), new EftEnumConverter(), - new EftEnumConverter() + new EftEnumConverter(), + new EftEnumConverter() } }; private static readonly JsonSerializerOptions jsonSerializerOptionsIndented = new(jsonSerializerOptionsNoIndent)