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 = {