From f3ccab713ea31aaaa6039cf5de4e321eeea3c00e Mon Sep 17 00:00:00 2001 From: Lucca Faria Ferri Date: Mon, 19 Jun 2023 07:41:22 -0700 Subject: [PATCH] Game Mods - Json + Configuration migration - Changed the Game Mods config to a json file - Created a .ini migration for the json file - Better ANSI readability - Improved some methods --- src/DiIiS-NA/Core/Logging/AnsiTarget.cs | 166 +++++------ src/DiIiS-NA/Core/Logging/LogManager.cs | 9 +- src/DiIiS-NA/Core/Logging/Logger.cs | 4 +- src/DiIiS-NA/Core/MPQ/Data.cs | 2 +- src/DiIiS-NA/Core/MPQ/MPQPatchChain.cs | 6 +- .../CommandManager/Commands/GameCommand.cs | 18 ++ .../Commands/UnlockArtCommand.cs | 3 +- .../ActorSystem/Implementations/Boss.cs | 7 +- .../GSSystem/ActorSystem/Monster.cs | 9 +- .../GSSystem/GameSystem/QuestManager.cs | 2 +- .../GSSystem/ItemsSystem/ItemGenerator.cs | 5 +- .../GSSystem/ItemsSystem/LootManager.cs | 9 +- .../D3-GameServer/GSSystem/MapSystem/Scene.cs | 3 +- .../D3-GameServer/GSSystem/MapSystem/World.cs | 3 +- .../GSSystem/PlayerSystem/Player.cs | 36 ++- .../General/DrinkHealthPotion.cs | 5 +- .../PowerSystem/Payloads/DeathPayload.cs | 11 +- src/DiIiS-NA/D3-GameServer/GameModsConfig.cs | 257 ++++++++++++++++++ .../D3-GameServer/GameServerConfig.cs | 28 ++ src/DiIiS-NA/D3-GameServer/ParagonMod.cs | 16 ++ src/DiIiS-NA/Program.cs | 86 +++--- 21 files changed, 510 insertions(+), 175 deletions(-) create mode 100644 src/DiIiS-NA/D3-GameServer/CommandManager/Commands/GameCommand.cs create mode 100644 src/DiIiS-NA/D3-GameServer/GameModsConfig.cs create mode 100644 src/DiIiS-NA/D3-GameServer/ParagonMod.cs diff --git a/src/DiIiS-NA/Core/Logging/AnsiTarget.cs b/src/DiIiS-NA/Core/Logging/AnsiTarget.cs index bb93ea8..85f731e 100644 --- a/src/DiIiS-NA/Core/Logging/AnsiTarget.cs +++ b/src/DiIiS-NA/Core/Logging/AnsiTarget.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; @@ -24,12 +26,12 @@ public class AnsiTarget : LogTarget _table = new Table().Expand().ShowFooters().ShowHeaders().Border(TableBorder.Rounded); if (IncludeTimeStamps) - _table.AddColumn("Time"); + _table.AddColumn("Time").Centered(); _table - .AddColumn("Level") - .AddColumn("Logger") - .AddColumn("Message") - .AddColumn("Error"); + .AddColumn("Level").RightAligned() + .AddColumn("Message").Centered() + .AddColumn("Logger").LeftAligned() + .AddColumn("Error").RightAligned(); AnsiConsole.Live(_table).StartAsync(async ctx => { @@ -101,6 +103,15 @@ public class AnsiTarget : LogTarget } + private static Dictionary _replacements = new () + { + ["["] = "[[", + ["]"] = "]]", + ["$[[/]]$"] = "[/]", + ["$[["] = "[", + ["]]$"] = "]" + }; + /// /// Performs a cleanup on the target. /// All [ becomes [[, and ] becomes ]] (for ignoring ANSI codes) @@ -112,122 +123,81 @@ public class AnsiTarget : LogTarget /// /// /// - public static string Cleanup(string x) => Beautify(x.Replace("[", "[[").Replace("]", "]]").Replace("$[[/]]$", "[/]").Replace("$[[", "[").Replace("]]$", "]")); - + public static string Cleanup(string input) + { + if (string.IsNullOrEmpty(input)) return ""; + return Beautify(_replacements.Aggregate(input, (current, replacement) => current.Replace(replacement.Key, replacement.Value))); + } public override void LogMessage(Logger.Level level, string logger, string message) { - if (CancellationTokenSource.IsCancellationRequested) - return; + if (CancellationTokenSource.IsCancellationRequested) return; + try { - if (IncludeTimeStamps) - _table.AddRow( - new Markup(DateTime.Now.ToString(TimeStampFormat), GetStyleByLevel(level)), - new Markup(level.ToString(), GetStyleByLevel(level)).RightJustified(), - new Markup(logger, GetStyleByLevel(level)).LeftJustified(), - new Markup(Cleanup(message), GetStyleByLevel(level)).LeftJustified(), - new Markup("", new Style(foreground: Color.Green3_1)).Centered()); - else - _table.AddRow( - new Markup(level.ToString()).RightJustified(), - new Markup(logger, GetStyleByLevel(level)).LeftJustified(), - new Markup(Cleanup(message), GetStyleByLevel(level)).LeftJustified(), - new Markup("", new Style(foreground: Color.Green3_1)).Centered()); + AddRow(level, logger, message, ""); } catch (Exception ex) { - var regex = new Regex(@"\$\[.*?\]\$"); - var matches = regex.Matches(message); - foreach (Match match in matches) - { - message = message.Replace(match.Value, ""); - } - - if (IncludeTimeStamps) - { - _table.AddRow( - new Markup(DateTime.Now.ToString(TimeStampFormat), GetStyleByLevel(level)), - new Markup(level.ToString(), GetStyleByLevel(level)).RightJustified(), - new Markup(logger, GetStyleByLevel(level)).LeftJustified(), - new Markup(Cleanup(message), GetStyleByLevel(level)).LeftJustified(), - new Markup(ex.Message, new Style(foreground: Color.Red3_1)).Centered()); - } - else - { - _table.AddRow( - new Markup(level.ToString()).RightJustified(), - new Markup(logger, GetStyleByLevel(level)).LeftJustified(), - new Markup(Cleanup(message), GetStyleByLevel(level)).LeftJustified(), - new Markup(ex.Message, new Style(foreground: Color.Red3_1)).Centered()); - } + AddRow(level, logger, Cleanup(StripMarkup(message)), ex.Message, true); } } public override void LogException(Logger.Level level, string logger, string message, Exception exception) { - if (CancellationTokenSource.IsCancellationRequested) - return; + if (CancellationTokenSource.IsCancellationRequested) return; + try { - if (IncludeTimeStamps) - _table.AddRow( - new Markup(DateTime.Now.ToString(TimeStampFormat), GetStyleByLevel(level)), - new Markup(level.ToString(), GetStyleByLevel(level)).RightJustified(), - new Markup(logger, GetStyleByLevel(level)).LeftJustified(), - new Markup(Cleanup(message), GetStyleByLevel(level)).LeftJustified(), - new Markup( - $"[underline red3_1 on white]{exception.GetType().Name}[/]\n" + Cleanup(exception.Message), - new Style(foreground: Color.Red3_1)).Centered()); - else - _table.AddRow( - new Markup(level.ToString()).RightJustified(), - new Markup(logger, GetStyleByLevel(level)).LeftJustified(), - new Markup(message, GetStyleByLevel(level)).LeftJustified(), - new Markup( - $"[underline red3_1 on white]{exception.GetType().Name}[/]\n" + Cleanup(exception.Message), - new Style(foreground: Color.Red3_1)).Centered()); + AddRow(level, logger, message, $"[underline red3_1 on white]{exception.GetType().Name}[/]\n" + Cleanup(exception.Message), exFormat: true); } catch (Exception ex) { - var regex = new Regex(@"\$\[.*?\]\$"); - var matches = regex.Matches(message); - foreach (Match match in matches) - { - message = message.Replace(match.Value, ""); - } - - if (IncludeTimeStamps) - { - _table.AddRow( - new Markup(DateTime.Now.ToString(TimeStampFormat), GetStyleByLevel(level)), - new Markup(level.ToString(), GetStyleByLevel(level)).RightJustified(), - new Markup(logger, GetStyleByLevel(level)).LeftJustified(), - new Markup(Cleanup(message), GetStyleByLevel(level)).LeftJustified(), - new Markup(ex.Message, new Style(foreground: Color.Red3_1)).Centered()); - } - else - { - _table.AddRow( - new Markup(level.ToString()).RightJustified(), - new Markup(logger, GetStyleByLevel(level)).LeftJustified(), - new Markup(Cleanup(message), GetStyleByLevel(level)).LeftJustified(), - new Markup(ex.Message, new Style(foreground: Color.Red3_1)).Centered()); - } + AddRow(level, logger, Cleanup(StripMarkup(message)), ex.Message, true); } -} + } + + private void AddRow(Logger.Level level, string logger, string message, string exMessage, bool isError = false, + bool exFormat = false) + { + Style messageStyle = GetStyleByLevel(level); + Style exStyle = exFormat ? new Style(foreground: Color.Red3_1) : new Style(foreground: Color.Green3_1); + var colTimestamp = new Markup(DateTime.Now.ToString(TimeStampFormat), messageStyle).Centered(); + var colLevel = new Markup(level.ToString(), messageStyle).RightJustified(); + var colMessage = new Markup(Cleanup(message), messageStyle).Centered(); + var colLogger = new Markup(logger, new Style(messageStyle.Foreground, messageStyle.Background, messageStyle.Decoration + #if DEBUG + //, link = ... + #endif + )).LeftJustified(); + var colError = new Markup(isError ? exMessage : "", exStyle).RightJustified(); + if (IncludeTimeStamps) _table.AddRow(colTimestamp, colLevel, colMessage, colLogger, colError); + else _table.AddRow(colLevel, colMessage, colLogger, colError); + } + + private string StripMarkup(string message) + { + var regex = new Regex(@"\$\[.*?\]\$"); + var matches = regex.Matches(message); + foreach (Match match in matches) + { + message = message.Replace(match.Value, ""); + } + + return message; + } private static Style GetStyleByLevel(Logger.Level level) { return level switch { - Logger.Level.RenameAccountLog => new Style(Color.DarkSlateGray3),// - Logger.Level.ChatMessage => new Style(Color.DarkSlateGray2),// - Logger.Level.Debug => new Style(Color.Olive),// - Logger.Level.MethodTrace => new Style(Color.DarkOliveGreen1_1),// - Logger.Level.Trace => new Style(Color.BlueViolet),// - Logger.Level.Info => new Style(Color.White), - Logger.Level.Success => new Style(Color.Green3_1), - Logger.Level.Warn => new Style(Color.Yellow),// + Logger.Level.RenameAccountLog => new Style(Color.Gold1),// + Logger.Level.ChatMessage => new Style(Color.Plum2),// + Logger.Level.Debug => new Style(Color.Grey62),// + Logger.Level.MethodTrace => new Style(Color.Grey74, decoration: Decoration.Dim | Decoration.Italic),// + Logger.Level.Trace => new Style(Color.Grey82),// + Logger.Level.Info => new Style(Color.SteelBlue), + Logger.Level.Success => new Style(Color.DarkOliveGreen3_2), + Logger.Level.Warn => new Style(Color.DarkOrange),// Logger.Level.Error => new Style(Color.IndianRed1),// Logger.Level.Fatal => new Style(Color.Red3_1),// Logger.Level.PacketDump => new Style(Color.Maroon),// diff --git a/src/DiIiS-NA/Core/Logging/LogManager.cs b/src/DiIiS-NA/Core/Logging/LogManager.cs index dc3e7eb..6bca5c9 100644 --- a/src/DiIiS-NA/Core/Logging/LogManager.cs +++ b/src/DiIiS-NA/Core/Logging/LogManager.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Runtime.CompilerServices; namespace DiIiS_NA.Core.Logging { @@ -25,7 +26,7 @@ namespace DiIiS_NA.Core.Logging /// Creates and returns a logger named with declaring type. /// /// A instance. - public static Logger CreateLogger() + public static Logger CreateLogger([CallerFilePath] string filePath = "") { var frame = new StackFrame(1, false); // read stack frame. var name = frame.GetMethod().DeclaringType.Name; // get declaring type's name. @@ -33,7 +34,7 @@ namespace DiIiS_NA.Core.Logging if (name == null) // see if we got a name. throw new Exception("Error getting full name for declaring type."); - return CreateLogger(name); // return the newly created logger. + return CreateLogger(name, filePath); // return the newly created logger. } /// @@ -41,10 +42,10 @@ namespace DiIiS_NA.Core.Logging /// /// /// A instance. - public static Logger CreateLogger(string name) + public static Logger CreateLogger(string name, [CallerFilePath] string filePath = "") { if (!Loggers.ContainsKey(name)) // see if we already have instance for the given name. - Loggers.Add(name, new Logger(name)); // add it to dictionary of loggers. + Loggers.Add(name, new Logger(name, filePath)); // add it to dictionary of loggers. return Loggers[name]; // return the newly created logger. } diff --git a/src/DiIiS-NA/Core/Logging/Logger.cs b/src/DiIiS-NA/Core/Logging/Logger.cs index 8e3a012..0e46573 100644 --- a/src/DiIiS-NA/Core/Logging/Logger.cs +++ b/src/DiIiS-NA/Core/Logging/Logger.cs @@ -13,15 +13,17 @@ namespace DiIiS_NA.Core.Logging public class Logger { public string Name { get; protected set; } + public string FilePath { get; protected set; } /// /// A logger base type is used to create a logger instance. /// E.g. ConsoleTarget, FileTarget, etc. /// /// Logger name - public Logger(string name) + public Logger(string name, string filePath = null) { Name = name; + FilePath = filePath; } public enum Level diff --git a/src/DiIiS-NA/Core/MPQ/Data.cs b/src/DiIiS-NA/Core/MPQ/Data.cs index 2b46703..e0defcd 100644 --- a/src/DiIiS-NA/Core/MPQ/Data.cs +++ b/src/DiIiS-NA/Core/MPQ/Data.cs @@ -79,7 +79,7 @@ namespace DiIiS_NA.Core.MPQ public void Init() { - Logger.Info("Loading Diablo III Assets.."); + Logger.Info("Loading Diablo III Assets..."); DictSNOAccolade = Dicts.LoadAccolade(); DictSNOAct = Dicts.LoadActs(); DictSNOActor = Dicts.LoadActors(); diff --git a/src/DiIiS-NA/Core/MPQ/MPQPatchChain.cs b/src/DiIiS-NA/Core/MPQ/MPQPatchChain.cs index 3a9040b..0f5c1a7 100644 --- a/src/DiIiS-NA/Core/MPQ/MPQPatchChain.cs +++ b/src/DiIiS-NA/Core/MPQ/MPQPatchChain.cs @@ -30,11 +30,11 @@ namespace DiIiS_NA.Core.MPQ var mpqFile = MPQStorage.GetMPQFile(file); if (mpqFile == null) { - Logger.Error("Cannot find base MPQ file: {0}.", file); + Logger.Fatal("Cannot find base MPQ file: $[white on red]${0}$[/]$.", file); return; } this.BaseMPQFiles.Add(mpqFile); - Logger.Trace("Added MPQ storage: {0}.", file); + Logger.Debug($"Added MPQ storage: $[white underline]${file}$[/]$."); } this.PatchPattern = patchPattern; @@ -45,7 +45,7 @@ namespace DiIiS_NA.Core.MPQ this.Loaded = true; else { - Logger.Error("Required patch-chain version {0} is not satified (found version: {1}).", this.RequiredVersion, topMostMPQVersion); + Logger.Error("Required patch-chain version {0} is not satisfied (found version: {1}).", this.RequiredVersion, topMostMPQVersion); } } diff --git a/src/DiIiS-NA/D3-GameServer/CommandManager/Commands/GameCommand.cs b/src/DiIiS-NA/D3-GameServer/CommandManager/Commands/GameCommand.cs new file mode 100644 index 0000000..79e4638 --- /dev/null +++ b/src/DiIiS-NA/D3-GameServer/CommandManager/Commands/GameCommand.cs @@ -0,0 +1,18 @@ +using DiIiS_NA.Core.Logging; +using DiIiS_NA.D3_GameServer; +using DiIiS_NA.LoginServer.AccountsSystem; +using DiIiS_NA.LoginServer.Battle; + +namespace DiIiS_NA.GameServer.CommandManager; + +[CommandGroup("game", "Game Commands", Account.UserLevels.Admin, inGameOnly: false)] +public class GameCommand : CommandGroup +{ + private static readonly Logger Logger = LogManager.CreateLogger(); + [Command("reload-mods", "Reload all game mods file.", Account.UserLevels.Admin, inGameOnly: false)] + public void ReloadMods(string[] @params, BattleClient invokerClient) + { + GameModsConfig.ReloadSettings(); + invokerClient.SendServerWhisper("Game mods updated successfully!"); + } +} \ No newline at end of file diff --git a/src/DiIiS-NA/D3-GameServer/CommandManager/Commands/UnlockArtCommand.cs b/src/DiIiS-NA/D3-GameServer/CommandManager/Commands/UnlockArtCommand.cs index a24cd2a..25f5967 100644 --- a/src/DiIiS-NA/D3-GameServer/CommandManager/Commands/UnlockArtCommand.cs +++ b/src/DiIiS-NA/D3-GameServer/CommandManager/Commands/UnlockArtCommand.cs @@ -1,4 +1,5 @@ -using DiIiS_NA.GameServer.MessageSystem.Message.Definitions.Hireling; +using System; +using DiIiS_NA.GameServer.MessageSystem.Message.Definitions.Hireling; using DiIiS_NA.LoginServer.AccountsSystem; using DiIiS_NA.LoginServer.Battle; diff --git a/src/DiIiS-NA/D3-GameServer/GSSystem/ActorSystem/Implementations/Boss.cs b/src/DiIiS-NA/D3-GameServer/GSSystem/ActorSystem/Implementations/Boss.cs index 282a8f2..e5f26fc 100644 --- a/src/DiIiS-NA/D3-GameServer/GSSystem/ActorSystem/Implementations/Boss.cs +++ b/src/DiIiS-NA/D3-GameServer/GSSystem/ActorSystem/Implementations/Boss.cs @@ -4,6 +4,7 @@ using DiIiS_NA.GameServer.GSSystem.AISystem.Brains; using DiIiS_NA.GameServer.MessageSystem; using DiIiS_NA.Core.Logging; using DiIiS_NA.Core.MPQ.FileFormats; +using DiIiS_NA.D3_GameServer; namespace DiIiS_NA.GameServer.GSSystem.ActorSystem.Implementations { @@ -73,9 +74,9 @@ namespace DiIiS_NA.GameServer.GSSystem.ActorSystem.Implementations //this.Attributes[GameAttribute.Immune_To_Charm] = true; Attributes[GameAttributes.using_Bossbar] = true; Attributes[GameAttributes.InBossEncounter] = true; - Attributes[GameAttributes.Hitpoints_Max] *= GameServerConfig.Instance.BossHealthMultiplier; - Attributes[GameAttributes.Damage_Weapon_Min, 0] *= GameServerConfig.Instance.BossDamageMultiplier; - Attributes[GameAttributes.Damage_Weapon_Delta, 0] *= GameServerConfig.Instance.BossDamageMultiplier; + Attributes[GameAttributes.Hitpoints_Max] *= GameModsConfig.Instance.Boss.HealthMultiplier; + Attributes[GameAttributes.Damage_Weapon_Min, 0] *= GameModsConfig.Instance.Boss.DamageMultiplier; + Attributes[GameAttributes.Damage_Weapon_Delta, 0] *= GameModsConfig.Instance.Boss.DamageMultiplier; Attributes[GameAttributes.Hitpoints_Cur] = Attributes[GameAttributes.Hitpoints_Max_Total]; Attributes[GameAttributes.TeamID] = 10; diff --git a/src/DiIiS-NA/D3-GameServer/GSSystem/ActorSystem/Monster.cs b/src/DiIiS-NA/D3-GameServer/GSSystem/ActorSystem/Monster.cs index 6a1d9ad..3eec428 100644 --- a/src/DiIiS-NA/D3-GameServer/GSSystem/ActorSystem/Monster.cs +++ b/src/DiIiS-NA/D3-GameServer/GSSystem/ActorSystem/Monster.cs @@ -6,6 +6,7 @@ using GameBalance = DiIiS_NA.Core.MPQ.FileFormats.GameBalance; using DiIiS_NA.GameServer.GSSystem.ObjectsSystem; using DiIiS_NA.Core.Logging; using DiIiS_NA.Core.MPQ.FileFormats; +using DiIiS_NA.D3_GameServer; using DiIiS_NA.GameServer.GSSystem.TickerSystem; using DiIiS_NA.GameServer.MessageSystem; using DiIiS_NA.GameServer.Core.Types.SNO; @@ -96,26 +97,26 @@ namespace DiIiS_NA.GameServer.GSSystem.ActorSystem Attributes[GameAttributes.Hitpoints_Max] = (int)((int)monsterLevels.MonsterLevel[monsterLevel].HPMin + DiIiS_NA.Core.Helpers.Math.RandomHelper.Next(0, (int)monsterLevels.MonsterLevel[monsterLevel].HPDelta) * HpMultiplier * World.Game.HpModifier); Attributes[GameAttributes.Hitpoints_Max_Percent_Bonus_Multiplicative] = ((int)World.Game.ConnectedPlayers.Length + 1) * 1.5f; - Attributes[GameAttributes.Hitpoints_Max_Percent_Bonus_Multiplicative] *= GameServerConfig.Instance.RateMonsterHP; + Attributes[GameAttributes.Hitpoints_Max_Percent_Bonus_Multiplicative] *= GameModsConfig.Instance.Monster.HealthMultiplier; if (World.Game.ConnectedPlayers.Length > 1) Attributes[GameAttributes.Hitpoints_Max_Percent_Bonus_Multiplicative] = Attributes[GameAttributes.Hitpoints_Max_Percent_Bonus_Multiplicative];// / 2f; var hpMax = Attributes[GameAttributes.Hitpoints_Max]; var hpTotal = Attributes[GameAttributes.Hitpoints_Max_Total]; float damageMin = monsterLevels.MonsterLevel[World.Game.MonsterLevel].Dmg * DmgMultiplier;// * 0.5f; float damageDelta = damageMin; - Attributes[GameAttributes.Damage_Weapon_Min, 0] = damageMin * World.Game.DmgModifier * GameServerConfig.Instance.RateMonsterDMG; + Attributes[GameAttributes.Damage_Weapon_Min, 0] = damageMin * World.Game.DmgModifier * GameModsConfig.Instance.Monster.DamageMultiplier; Attributes[GameAttributes.Damage_Weapon_Delta, 0] = damageDelta; if (monsterLevel > 30) { Attributes[GameAttributes.Hitpoints_Max_Percent_Bonus_Multiplicative] = Attributes[GameAttributes.Hitpoints_Max_Percent_Bonus_Multiplicative];// * 0.5f; - Attributes[GameAttributes.Damage_Weapon_Min, 0] = damageMin * World.Game.DmgModifier * GameServerConfig.Instance.RateMonsterDMG;// * 0.2f; + Attributes[GameAttributes.Damage_Weapon_Min, 0] = damageMin * World.Game.DmgModifier * GameModsConfig.Instance.Monster.DamageMultiplier;// * 0.2f; Attributes[GameAttributes.Damage_Weapon_Delta, 0] = damageDelta; } if (monsterLevel > 60) { Attributes[GameAttributes.Hitpoints_Max_Percent_Bonus_Multiplicative] = Attributes[GameAttributes.Hitpoints_Max_Percent_Bonus_Multiplicative];// * 0.7f; - Attributes[GameAttributes.Damage_Weapon_Min, 0] = damageMin * World.Game.DmgModifier * GameServerConfig.Instance.RateMonsterDMG;// * 0.15f; + Attributes[GameAttributes.Damage_Weapon_Min, 0] = damageMin * World.Game.DmgModifier * GameModsConfig.Instance.Monster.DamageMultiplier;// * 0.15f; //this.Attributes[GameAttribute.Damage_Weapon_Delta, 0] = DamageDelta * 0.5f; } diff --git a/src/DiIiS-NA/D3-GameServer/GSSystem/GameSystem/QuestManager.cs b/src/DiIiS-NA/D3-GameServer/GSSystem/GameSystem/QuestManager.cs index ea1ac37..84af63e 100644 --- a/src/DiIiS-NA/D3-GameServer/GSSystem/GameSystem/QuestManager.cs +++ b/src/DiIiS-NA/D3-GameServer/GSSystem/GameSystem/QuestManager.cs @@ -270,7 +270,7 @@ namespace DiIiS_NA.D3_GameServer.GSSystem.GameSystem if (!Game.Empty) { RevealQuestProgress(); - if ((Game.CurrentActEnum != ActEnum.OpenWorld && GameServerConfig.Instance.AutoSaveQuests) || + if ((Game.CurrentActEnum != ActEnum.OpenWorld && GameModsConfig.Instance.Quest.AutoSave) || Quests[Game.CurrentQuest].Steps[Game.CurrentStep].Saveable) SaveQuestProgress(false); } diff --git a/src/DiIiS-NA/D3-GameServer/GSSystem/ItemsSystem/ItemGenerator.cs b/src/DiIiS-NA/D3-GameServer/GSSystem/ItemsSystem/ItemGenerator.cs index 81fabf9..7894bee 100644 --- a/src/DiIiS-NA/D3-GameServer/GSSystem/ItemsSystem/ItemGenerator.cs +++ b/src/DiIiS-NA/D3-GameServer/GSSystem/ItemsSystem/ItemGenerator.cs @@ -18,6 +18,7 @@ using DiIiS_NA.GameServer.Core.Types.TagMap; using DiIiS_NA.GameServer.MessageSystem; using DiIiS_NA.LoginServer.Toons; using DiIiS_NA.Core.Helpers.Math; +using DiIiS_NA.D3_GameServer; using DiIiS_NA.GameServer.GSSystem.PlayerSystem; namespace DiIiS_NA.GameServer.GSSystem.ItemsSystem @@ -1358,8 +1359,8 @@ namespace DiIiS_NA.GameServer.GSSystem.ItemsSystem private static void RandomSetUnidentified(Item item) => item.Unidentified = FastRandom.Instance.Chance(item.Name.Contains("unique", StringComparison.InvariantCultureIgnoreCase) || item.ItemDefinition.Quality is ItemTable.ItemQuality.Legendary or ItemTable.ItemQuality.Special or ItemTable.ItemQuality.Set - ? GameServerConfig.Instance.ChanceHighQualityUnidentified - : GameServerConfig.Instance.ChanceNormalUnidentified); + ? GameModsConfig.Instance.Items.UnidentifiedDropChances.HighQuality + : GameModsConfig.Instance.Items.UnidentifiedDropChances.NormalQuality); // Allows cooking a custom item. public static Item Cook(Player player, string name) diff --git a/src/DiIiS-NA/D3-GameServer/GSSystem/ItemsSystem/LootManager.cs b/src/DiIiS-NA/D3-GameServer/GSSystem/ItemsSystem/LootManager.cs index 30ddf39..5d85f2e 100644 --- a/src/DiIiS-NA/D3-GameServer/GSSystem/ItemsSystem/LootManager.cs +++ b/src/DiIiS-NA/D3-GameServer/GSSystem/ItemsSystem/LootManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using DiIiS_NA.D3_GameServer; namespace DiIiS_NA.GameServer.GSSystem.ItemsSystem { @@ -613,16 +614,16 @@ namespace DiIiS_NA.GameServer.GSSystem.ItemsSystem switch (MonsterQuality) { case 0: //Normal - return new List { 0.18f * GameServerConfig.Instance.RateChangeDrop }; + return new List { 0.18f * GameModsConfig.Instance.Rate.ChangeDrop }; case 1: //Champion - return new List { 1f, 1f, 1f, 1f, 0.75f * GameServerConfig.Instance.RateChangeDrop }; + return new List { 1f, 1f, 1f, 1f, 0.75f * GameModsConfig.Instance.Rate.ChangeDrop }; case 2: //Rare (Elite) case 4: //Unique return new List { 1f, 1f, 1f, 1f, 1f }; case 7: //Boss - return new List { 1f, 1f, 1f, 1f, 1f, 0.75f * GameServerConfig.Instance.RateChangeDrop, 0.4f * GameServerConfig.Instance.RateChangeDrop }; + return new List { 1f, 1f, 1f, 1f, 1f, 0.75f * GameModsConfig.Instance.Rate.ChangeDrop, 0.4f * GameModsConfig.Instance.Rate.ChangeDrop }; default: - return new List { 0.12f * GameServerConfig.Instance.RateChangeDrop }; + return new List { 0.12f * GameModsConfig.Instance.Rate.ChangeDrop }; } } diff --git a/src/DiIiS-NA/D3-GameServer/GSSystem/MapSystem/Scene.cs b/src/DiIiS-NA/D3-GameServer/GSSystem/MapSystem/Scene.cs index 5c5352f..8c23019 100644 --- a/src/DiIiS-NA/D3-GameServer/GSSystem/MapSystem/Scene.cs +++ b/src/DiIiS-NA/D3-GameServer/GSSystem/MapSystem/Scene.cs @@ -24,6 +24,7 @@ using System.Linq; using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; +using DiIiS_NA.D3_GameServer; using Actor = DiIiS_NA.GameServer.GSSystem.ActorSystem.Actor; namespace DiIiS_NA.GameServer.GSSystem.MapSystem @@ -549,7 +550,7 @@ namespace DiIiS_NA.GameServer.GSSystem.MapSystem SceneSNO = SceneSNO.Id, Transform = Transform, WorldID = World.GlobalID, - MiniMapVisibility = GameServerConfig.Instance.ForceMinimapVisibility + MiniMapVisibility = GameModsConfig.Instance.Minimap.ForceVisibility }; } diff --git a/src/DiIiS-NA/D3-GameServer/GSSystem/MapSystem/World.cs b/src/DiIiS-NA/D3-GameServer/GSSystem/MapSystem/World.cs index 168bbc8..185c0c5 100644 --- a/src/DiIiS-NA/D3-GameServer/GSSystem/MapSystem/World.cs +++ b/src/DiIiS-NA/D3-GameServer/GSSystem/MapSystem/World.cs @@ -10,6 +10,7 @@ using DiIiS_NA.Core.Helpers.Math; using DiIiS_NA.Core.Logging; using DiIiS_NA.Core.MPQ; using DiIiS_NA.Core.MPQ.FileFormats; +using DiIiS_NA.D3_GameServer; using DiIiS_NA.D3_GameServer.Core.Types.SNO; using DiIiS_NA.GameServer.Core.Types.Math; using DiIiS_NA.GameServer.Core.Types.QuadTrees; @@ -930,7 +931,7 @@ namespace DiIiS_NA.GameServer.GSSystem.MapSystem /// The position for drop. public void SpawnGold(Actor source, Player player, int Min = -1) { - int amount = (int)(LootManager.GetGoldAmount(player.Attributes[GameAttributes.Level]) * Game.GoldModifier * GameServerConfig.Instance.RateMoney); + int amount = (int)(LootManager.GetGoldAmount(player.Attributes[GameAttributes.Level]) * Game.GoldModifier * GameModsConfig.Instance.Rate.Money); if (Min != -1) amount += Min; var item = ItemGenerator.CreateGold(player, amount); // somehow the actual ammount is not shown on ground /raist. diff --git a/src/DiIiS-NA/D3-GameServer/GSSystem/PlayerSystem/Player.cs b/src/DiIiS-NA/D3-GameServer/GSSystem/PlayerSystem/Player.cs index b0191f5..238ed58 100644 --- a/src/DiIiS-NA/D3-GameServer/GSSystem/PlayerSystem/Player.cs +++ b/src/DiIiS-NA/D3-GameServer/GSSystem/PlayerSystem/Player.cs @@ -56,6 +56,7 @@ using DiIiS_NA.GameServer.MessageSystem.Message.Definitions.Pet; using DiIiS_NA.GameServer.MessageSystem.Message.Definitions.Game; using DiIiS_NA.GameServer.MessageSystem.Message.Definitions.Hireling; using DiIiS_NA.Core.Helpers.Hash; +using DiIiS_NA.D3_GameServer; using DiIiS_NA.GameServer.MessageSystem.Message.Definitions.Encounter; using DiIiS_NA.D3_GameServer.Core.Types.SNO; using DiIiS_NA.D3_GameServer.GSSystem.ActorSystem.Implementations.Artisans; @@ -2481,6 +2482,21 @@ public class Player : Actor, IMessageConsumer, IUpdateable public bool SpeedCheckDisabled = false; + public float StrengthMultiplier => ParagonLevel > 0 + ? GameModsConfig.Instance.Player.Multipliers.Strength.Paragon + : GameModsConfig.Instance.Player.Multipliers.Strength.Normal; + public float DexterityMultiplier => ParagonLevel > 0 + ? GameModsConfig.Instance.Player.Multipliers.Dexterity.Paragon + : GameModsConfig.Instance.Player.Multipliers.Dexterity.Normal; + + public float IntelligenceMultiplier => ParagonLevel > 0 + ? GameModsConfig.Instance.Player.Multipliers.Intelligence.Paragon + : GameModsConfig.Instance.Player.Multipliers.Intelligence.Normal; + + public float VitalityMultiplier => ParagonLevel > 0 + ? GameModsConfig.Instance.Player.Multipliers.Vitality.Paragon + : GameModsConfig.Instance.Player.Multipliers.Intelligence.Normal; + public static byte[] StringToByteArray(string hex) { return Enumerable.Range(0, hex.Length) @@ -2675,8 +2691,9 @@ public class Player : Actor, IMessageConsumer, IUpdateable Logger.WarnException(e, "questEvent()"); } } - // Reset resurrection charges on zone change - TODO: do not reset charges on reentering the same zone - Attributes[GameAttributes.Corpse_Resurrection_Charges] = GameServerConfig.Instance.ResurrectionCharges; + // Reset resurrection charges on zone change + // TODO: do not reset charges on reentering the same zone + Attributes[GameAttributes.Corpse_Resurrection_Charges] = GameModsConfig.Instance.Health.ResurrectionCharges; #if DEBUG Logger.Warn($"Player Location {Toon.Name}, Scene: {CurrentScene.SceneSNO.Name} SNO: {CurrentScene.SceneSNO.Id} LevelArea: {CurrentScene.Specification.SNOLevelAreas[0]}"); @@ -3995,8 +4012,8 @@ public class Player : Actor, IMessageConsumer, IUpdateable get { var baseStrength = 0.0f; - var multiplier = ParagonLevel > 0 ? GameServerConfig.Instance.StrengthParagonMultiplier : GameServerConfig.Instance.StrengthMultiplier; - baseStrength = Toon.HeroTable.CoreAttribute == GameBalance.PrimaryAttribute.Strength + var multiplier = StrengthMultiplier; + baseStrength = Toon.HeroTable.CoreAttribute == GameBalance.PrimaryAttribute.Strength ? Toon.HeroTable.Strength + (Level - 1) * 3 : Toon.HeroTable.Strength + (Level - 1); @@ -4011,8 +4028,7 @@ public class Player : Actor, IMessageConsumer, IUpdateable { get { - var multiplier = ParagonLevel > 0 ? GameServerConfig.Instance.DexterityParagonMultiplier : GameServerConfig.Instance.DexterityMultiplier; - + var multiplier = DexterityMultiplier; return Toon.HeroTable.CoreAttribute == GameBalance.PrimaryAttribute.Dexterity ? Toon.HeroTable.Dexterity + (Level - 1) * 3 * multiplier : Toon.HeroTable.Dexterity + (Level - 1) * multiplier; @@ -4022,7 +4038,7 @@ public class Player : Actor, IMessageConsumer, IUpdateable public float TotalDexterity => Attributes[GameAttributes.Dexterity] + Inventory.GetItemBonus(GameAttributes.Dexterity_Item); - public float Vitality => Toon.HeroTable.Vitality + (Level - 1) * 2 * (ParagonLevel > 0 ? GameServerConfig.Instance.VitalityParagonMultiplier : GameServerConfig.Instance.VitalityMultiplier); + public float Vitality => Toon.HeroTable.Vitality + (Level - 1) * 2 * (VitalityMultiplier); public float TotalVitality => Attributes[GameAttributes.Vitality] + Inventory.GetItemBonus(GameAttributes.Vitality_Item); @@ -4031,7 +4047,7 @@ public class Player : Actor, IMessageConsumer, IUpdateable { get { - var multiplier = ParagonLevel > 0 ? GameServerConfig.Instance.IntelligenceParagonMultiplier : GameServerConfig.Instance.IntelligenceMultiplier; + var multiplier = IntelligenceMultiplier; return Toon.HeroTable.CoreAttribute == GameBalance.PrimaryAttribute.Intelligence ? Toon.HeroTable.Intelligence + (Level - 1) * 3 * multiplier : Toon.HeroTable.Intelligence + (Level - 1) * multiplier; @@ -4090,7 +4106,7 @@ public class Player : Actor, IMessageConsumer, IUpdateable }, SkillSlotEverAssigned = 0x0F, //0xB4, PlaytimeTotal = Toon.TimePlayed, - WaypointFlags = GameServerConfig.Instance.UnlockAllWaypoints ? 0x0000ffff : World.Game.WaypointFlags, + WaypointFlags = GameModsConfig.Instance.Quest.UnlockAllWaypoints ? 0x0000ffff : World.Game.WaypointFlags, HirelingData = new HirelingSavedData() { HirelingInfos = HirelingInfo, @@ -5499,7 +5515,7 @@ public class Player : Actor, IMessageConsumer, IUpdateable { if (InGameClient.Game.ActiveNephalemTimer && InGameClient.Game.ActiveNephalemKilledMobs == false) { - InGameClient.Game.ActiveNephalemProgress += 15f * GameServerConfig.Instance.NephalemRiftProgressMultiplier; + InGameClient.Game.ActiveNephalemProgress += 15f * GameModsConfig.Instance.NephalemRift.ProgressMultiplier; foreach (var plr in InGameClient.Game.Players.Values) { plr.InGameClient.SendMessage(new FloatDataMessage(Opcodes.DunggeonFinderProgressGlyphPickUp) diff --git a/src/DiIiS-NA/D3-GameServer/GSSystem/PowerSystem/Implementations/General/DrinkHealthPotion.cs b/src/DiIiS-NA/D3-GameServer/GSSystem/PowerSystem/Implementations/General/DrinkHealthPotion.cs index 28b7709..8877e1e 100644 --- a/src/DiIiS-NA/D3-GameServer/GSSystem/PowerSystem/Implementations/General/DrinkHealthPotion.cs +++ b/src/DiIiS-NA/D3-GameServer/GSSystem/PowerSystem/Implementations/General/DrinkHealthPotion.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using DiIiS_NA.D3_GameServer; using DiIiS_NA.GameServer.GSSystem.PlayerSystem; using DiIiS_NA.GameServer.GSSystem.TickerSystem; using DiIiS_NA.LoginServer; @@ -12,8 +13,8 @@ namespace DiIiS_NA.GameServer.GSSystem.PowerSystem.Implementations.General public override IEnumerable Run() { if (User is not Player player) yield break; - player.AddPercentageHP(GameServerConfig.Instance.HealthPotionRestorePercentage); - AddBuff(player, player, new CooldownBuff(30211, TickTimer.WaitSeconds(player.World.Game, GameServerConfig.Instance.HealthPotionCooldown))); + player.AddPercentageHP(GameModsConfig.Instance.Health.PotionRestorePercentage); + AddBuff(player, player, new CooldownBuff(30211, TickTimer.WaitSeconds(player.World.Game, GameModsConfig.Instance.Health.PotionCooldown))); } } } diff --git a/src/DiIiS-NA/D3-GameServer/GSSystem/PowerSystem/Payloads/DeathPayload.cs b/src/DiIiS-NA/D3-GameServer/GSSystem/PowerSystem/Payloads/DeathPayload.cs index 720170d..33cb72f 100644 --- a/src/DiIiS-NA/D3-GameServer/GSSystem/PowerSystem/Payloads/DeathPayload.cs +++ b/src/DiIiS-NA/D3-GameServer/GSSystem/PowerSystem/Payloads/DeathPayload.cs @@ -14,6 +14,7 @@ using DiIiS_NA.GameServer.GSSystem.PowerSystem.Implementations; using DiIiS_NA.GameServer.MessageSystem.Message.Definitions.Effect; using DiIiS_NA.GameServer.MessageSystem.Message.Definitions.Combat; using DiIiS_NA.Core.Helpers.Math; +using DiIiS_NA.D3_GameServer; using DiIiS_NA.LoginServer.Toons; using DiIiS_NA.GameServer.Core.Types.TagMap; using DiIiS_NA.GameServer.GSSystem.GeneratorsSystem; @@ -425,7 +426,7 @@ namespace DiIiS_NA.GameServer.GSSystem.PowerSystem.Payloads { grantedExp = (int)(grantedExp * rangedPlayer.World.Game.XpModifier); - float tempExp = grantedExp * GameServerConfig.Instance.RateExp; + float tempExp = grantedExp * GameModsConfig.Instance.Rate.Experience; rangedPlayer.UpdateExp(Math.Max((int)tempExp, 1)); var a = (int)rangedPlayer.Attributes[GameAttributes.Experience_Bonus]; @@ -636,13 +637,13 @@ namespace DiIiS_NA.GameServer.GSSystem.PowerSystem.Payloads Target.World.Game.ActiveNephalemTimer && Target.World.Game.ActiveNephalemKilledMobs == false) { Target.World.Game.ActiveNephalemProgress += - GameServerConfig.Instance.NephalemRiftProgressMultiplier * (Target.Quality + 1); + GameModsConfig.Instance.NephalemRift.ProgressMultiplier * (Target.Quality + 1); Player master = null; foreach (var plr3 in Target.World.Game.Players.Values) { if (plr3.PlayerIndex == 0) master = plr3; - if (GameServerConfig.Instance.NephalemRiftAutoFinish && Target.World.Monsters.Count(s => !s.Dead) <= GameServerConfig.Instance.NephalemRiftAutoFinishThreshold) Target.World.Game.ActiveNephalemProgress = 651; + if (GameModsConfig.Instance.NephalemRift.AutoFinish && Target.World.Monsters.Count(s => !s.Dead) <= GameModsConfig.Instance.NephalemRift.AutoFinishThreshold) Target.World.Game.ActiveNephalemProgress = 651; plr3.InGameClient.SendMessage(new SimpleMessage(Opcodes.KillCounterRefresh)); plr3.InGameClient.SendMessage(new FloatDataMessage(Opcodes.DungeonFinderProgressMessage) { @@ -711,7 +712,7 @@ namespace DiIiS_NA.GameServer.GSSystem.PowerSystem.Payloads } - if (Target.Quality > 1 || FastRandom.Instance.Chance(GameServerConfig.Instance.NephalemRiftOrbsChance)) + if (Target.Quality > 1 || FastRandom.Instance.Chance(GameModsConfig.Instance.NephalemRift.OrbsChance)) { //spawn spheres for mining indicator for (int i = 0; i < Target.Quality + 1; i++) @@ -938,7 +939,7 @@ namespace DiIiS_NA.GameServer.GSSystem.PowerSystem.Payloads // if seed is less than the drop rate, drop the item if (seed < rate * (1f + lootSpawnPlayer.Attributes[GameAttributes.Magic_Find]) - * GameServerConfig.Instance.RateDrop) + * GameModsConfig.Instance.Rate.Drop) { //Logger.Debug("rate: {0}", rate); var lootQuality = Target.World.Game.IsHardcore diff --git a/src/DiIiS-NA/D3-GameServer/GameModsConfig.cs b/src/DiIiS-NA/D3-GameServer/GameModsConfig.cs new file mode 100644 index 0000000..2330201 --- /dev/null +++ b/src/DiIiS-NA/D3-GameServer/GameModsConfig.cs @@ -0,0 +1,257 @@ +using System; +using System.Dynamic; +using System.IO; +using DiIiS_NA; +using DiIiS_NA.Core.Logging; +using DiIiS_NA.GameServer; +using Newtonsoft.Json; + +namespace DiIiS_NA.D3_GameServer; + +public class RateConfig +{ + public float Experience { get; set; } = 1; + public float Money { get; set; } = 1; + public float Drop { get; set; } = 1; + public float ChangeDrop { get; set; } = 1; +} + +public class HealthConfig +{ + public float PotionRestorePercentage { get; set; } = 60f; + public float PotionCooldown { get; set; } = 30f; + public int ResurrectionCharges { get; set; } = 3; +} + +public class HealthDamageMultiplier +{ + public float HealthMultiplier { get; set; } = 1; + public float DamageMultiplier { get; set; } = 1; +} + +public class QuestConfig +{ + public bool AutoSave { get; set; } = false; + public bool UnlockAllWaypoints { get; set; } = false; +} + +public class PlayerMultiplierConfig +{ + public ParagonConfig Strength { get; set; } = new(1f); + public ParagonConfig Dexterity { get; set; } = new(1f); + public ParagonConfig Intelligence { get; set; } = new(1f); + public ParagonConfig Vitality { get; set; } = new(1f); +} +public class PlayerConfig +{ + public PlayerMultiplierConfig Multipliers = new(); +} + +public class ItemsConfig +{ + public UnidentifiedDrop UnidentifiedDropChances { get; set; } = new(); +} + +public class UnidentifiedDrop +{ + public float HighQuality { get; set; } = 30f; + public float NormalQuality { get; set; } = 5f; +} + +public class MinimapConfig +{ + public bool ForceVisibility { get; set; } = false; +} + +public class NephalemRiftConfig +{ + public float ProgressMultiplier { get; set; } = 1f; + public bool AutoFinish { get; set; } = false; + public int AutoFinishThreshold { get; set; } = 2; + public float OrbsChance { get; set; } = 0f; +} + +public class GameModsConfig +{ + public RateConfig Rate { get; set; } = new(); + public HealthConfig Health { get; set; } = new(); + public HealthDamageMultiplier Monster { get; set; } = new(); + public HealthDamageMultiplier Boss { get; set; } = new(); + public QuestConfig Quest { get; set; } = new(); + public PlayerConfig Player { get; set; } = new(); + public ItemsConfig Items { get; set; } = new(); + public MinimapConfig Minimap { get; set; } = new(); + public NephalemRiftConfig NephalemRift { get; set; } = new(); + + private static readonly Logger Logger = LogManager.CreateLogger(); + + public GameModsConfig() {} + + static GameModsConfig() + { + CreateInstance(); + } + + public static void ReloadSettings() + { + CreateInstance(reload: true); + } + + private static readonly object InstanceCreationLock = new(); + public static GameModsConfig Instance { get; private set; } + + private static void CreateInstance(bool reload = false) + { + lock (InstanceCreationLock) + { + if (reload && File.Exists("config.mods.json")) File.Delete("config.mods.json"); + if (reload || !File.Exists("config.mods.json")) + { + Instance = CreateDefaultFile(); + } + else + { + var content = File.ReadAllText("config.mods.json"); + if (content.TryFromJson(out GameModsConfig config, out Exception ex)) + { + Logger.Success("Game mods loaded successfully!"); + Instance = config; + return; + } + + Logger.Fatal("An error occured whilst loading $[white on red]$config.mods.json$[/]$ file. Please verify if the file is correct. Delete the file and try again."); + Program.Shutdown(ex); + } + } + } + + private static GameModsConfig CreateDefaultFile() + { + var migration = GameServerConfig.Instance; + Logger.Info("$[blue]$Migrating mods configuration file...$[/]$"); + GameModsConfig content = new() + { +#pragma warning disable CS0618 + Rate = + { + Experience = migration.RateExp, + Money = migration.RateMoney, + ChangeDrop = migration.RateChangeDrop, + Drop = migration.RateDrop + }, + Health = + { + ResurrectionCharges = migration.ResurrectionCharges, + PotionCooldown = migration.HealthPotionCooldown, + PotionRestorePercentage = migration.HealthPotionRestorePercentage + }, + Monster = + { + HealthMultiplier = migration.RateMonsterHP, + DamageMultiplier = migration.RateMonsterDMG + }, + Boss = + { + HealthMultiplier = migration.BossHealthMultiplier, + DamageMultiplier = migration.BossDamageMultiplier + }, + Quest = + { + AutoSave = migration.AutoSaveQuests, + UnlockAllWaypoints = migration.UnlockAllWaypoints + }, + Player = + { + Multipliers = + { + Strength = new(migration.StrengthMultiplier, migration.StrengthParagonMultiplier), + Dexterity = new(migration.DexterityMultiplier, migration.DexterityParagonMultiplier), + Intelligence = new(migration.IntelligenceMultiplier, migration.IntelligenceParagonMultiplier), + Vitality = new(migration.VitalityMultiplier, migration.VitalityParagonMultiplier) + } + }, + Items = + { + UnidentifiedDropChances = + { + HighQuality = migration.ChanceHighQualityUnidentified, + NormalQuality = migration.ChanceNormalUnidentified + } + }, + Minimap = + { + ForceVisibility = migration.ForceMinimapVisibility + }, + NephalemRift = + { + AutoFinish = migration.NephalemRiftAutoFinish, + AutoFinishThreshold = migration.NephalemRiftAutoFinishThreshold, + OrbsChance = migration.NephalemRiftAutoFinishThreshold, + ProgressMultiplier = migration.NephalemRiftProgressMultiplier + } +#pragma warning restore CS0618 + }; + File.WriteAllText("config.mods.json", content.ToJson()); + + if (Program.Build == 30 && Program.Stage < 6) + { + Logger.Success( + "$[underline]$Migration is complete!$[/]$ - All game mods migrated from $[white]$config.ini$[/]$ to $[white]$config.mods.json$[/]$."); + } + + return content; + } +} + +public static class JsonExtensions +{ + private const bool Indented = true; + + public static string ToJson(this object obj, Formatting? formatting = null) + { + return JsonConvert.SerializeObject(obj, formatting ?? (Indented ? Formatting.Indented : Formatting.None)); + } + + public static bool TryFromJson(this string obj, out T value) + where T: class, new() + { + try + { + value = obj.FromJson(); + return true; + } + catch (Exception ex) + { + value = default; + return false; + } + } + + public static bool TryFromJson(this string obj, out T value, out Exception exception) + where T: class, new() + { + try + { + value = obj.FromJson(); + exception = null; + return true; + } + catch (Exception ex) + { + value = default; + exception = ex; + return false; + } + } + + public static T FromJson(this string obj) + where T: class, new() + { + return JsonConvert.DeserializeObject(obj); + } + + public static dynamic FromJsonDynamic(this string obj) + { + return obj.FromJson(); + } +} \ No newline at end of file diff --git a/src/DiIiS-NA/D3-GameServer/GameServerConfig.cs b/src/DiIiS-NA/D3-GameServer/GameServerConfig.cs index 646c442..cdbb2e2 100644 --- a/src/DiIiS-NA/D3-GameServer/GameServerConfig.cs +++ b/src/DiIiS-NA/D3-GameServer/GameServerConfig.cs @@ -71,6 +71,7 @@ namespace DiIiS_NA.GameServer /// /// Rate of experience gain. /// + [Obsolete("Use GameModsConfig instead.")] public float RateExp { get => GetFloat(nameof(RateExp), 1); @@ -80,6 +81,7 @@ namespace DiIiS_NA.GameServer /// /// Rate of gold gain. /// + [Obsolete("Use GameModsConfig instead.")] public float RateMoney { get => GetFloat(nameof(RateMoney), 1); @@ -89,12 +91,14 @@ namespace DiIiS_NA.GameServer /// /// Rate of item drop. /// + [Obsolete("Use GameModsConfig instead.")] public float RateDrop { get => GetFloat(nameof(RateDrop), 1); set => Set(nameof(RateDrop), value); } + [Obsolete("Use GameModsConfig instead.")] public float RateChangeDrop { get => GetFloat(nameof(RateChangeDrop), 1); @@ -104,6 +108,7 @@ namespace DiIiS_NA.GameServer /// /// Rate of monster's HP. /// + [Obsolete("Use GameModsConfig instead.")] public float RateMonsterHP { get => GetFloat(nameof(RateMonsterHP), 1); @@ -113,6 +118,7 @@ namespace DiIiS_NA.GameServer /// /// Rate of monster's damage. /// + [Obsolete("Use GameModsConfig instead.")] public float RateMonsterDMG { get => GetFloat(nameof(RateMonsterDMG), 1); @@ -122,6 +128,7 @@ namespace DiIiS_NA.GameServer /// /// Percentage that a unique, legendary, set or special item created is unidentified /// + [Obsolete("Use GameModsConfig instead.")] public float ChanceHighQualityUnidentified { get => GetFloat(nameof(ChanceHighQualityUnidentified), 30f); @@ -131,6 +138,7 @@ namespace DiIiS_NA.GameServer /// /// Percentage that a normal item created is unidentified /// + [Obsolete("Use GameModsConfig instead.")] public float ChanceNormalUnidentified { get => GetFloat(nameof(ChanceNormalUnidentified), 5f); @@ -140,6 +148,7 @@ namespace DiIiS_NA.GameServer /// /// Resurrection charges on changing worlds /// + [Obsolete("Use GameModsConfig instead.")] public int ResurrectionCharges { get => GetInt(nameof(ResurrectionCharges), 3); @@ -149,6 +158,7 @@ namespace DiIiS_NA.GameServer /// /// Boss Health Multiplier /// + [Obsolete("Use GameModsConfig instead.")] public float BossHealthMultiplier { get => GetFloat(nameof(BossHealthMultiplier), 6f); @@ -158,6 +168,7 @@ namespace DiIiS_NA.GameServer /// /// Boss Damage Multiplier /// + [Obsolete("Use GameModsConfig instead.")] public float BossDamageMultiplier { get => GetFloat(nameof(BossDamageMultiplier), 3f); @@ -167,6 +178,7 @@ namespace DiIiS_NA.GameServer /// /// Whether to bypass the quest's settings of "Saveable" to TRUE (unless in OpenWorld) /// + [Obsolete("Use GameModsConfig instead.")] public bool AutoSaveQuests { get => GetBoolean(nameof(AutoSaveQuests), false); @@ -176,6 +188,7 @@ namespace DiIiS_NA.GameServer /// /// Progress gained when killing a monster in Nephalem Rifts /// + [Obsolete("Use GameModsConfig instead.")] public float NephalemRiftProgressMultiplier { get => GetFloat(nameof(NephalemRiftProgressMultiplier), 1f); @@ -185,6 +198,7 @@ namespace DiIiS_NA.GameServer /// /// How much a health potion heals in percentage /// + [Obsolete("Use GameModsConfig instead.")] public float HealthPotionRestorePercentage { get => GetFloat(nameof(HealthPotionRestorePercentage), 60f); @@ -194,6 +208,7 @@ namespace DiIiS_NA.GameServer /// /// Cooldown (in seconds) to use a health potion again. /// + [Obsolete("Use GameModsConfig instead.")] public float HealthPotionCooldown { get => GetFloat(nameof(HealthPotionCooldown), 30f); @@ -203,6 +218,7 @@ namespace DiIiS_NA.GameServer /// /// Unlocks all waypoints in the campaign. /// + [Obsolete("Use GameModsConfig instead.")] public bool UnlockAllWaypoints { get => GetBoolean(nameof(UnlockAllWaypoints), false); @@ -212,6 +228,7 @@ namespace DiIiS_NA.GameServer /// /// Strength multiplier when you're not a paragon. /// + [Obsolete("Use GameModsConfig instead.")] public float StrengthMultiplier { get => GetFloat(nameof(StrengthMultiplier), 1f); @@ -221,6 +238,7 @@ namespace DiIiS_NA.GameServer /// /// Strength multiplier when you're a paragon. /// + [Obsolete("Use GameModsConfig instead.")] public float StrengthParagonMultiplier { get => GetFloat(nameof(StrengthParagonMultiplier), 1f); @@ -230,6 +248,7 @@ namespace DiIiS_NA.GameServer /// /// Dexterity multiplier when you're not a paragon. /// + [Obsolete("Use GameModsConfig instead.")] public float DexterityMultiplier { get => GetFloat(nameof(DexterityMultiplier), 1f); @@ -239,6 +258,7 @@ namespace DiIiS_NA.GameServer /// /// Dexterity multiplier when you're a paragon. /// + [Obsolete("Use GameModsConfig instead.")] public float DexterityParagonMultiplier { get => GetFloat(nameof(DexterityParagonMultiplier), 1f); @@ -248,6 +268,7 @@ namespace DiIiS_NA.GameServer /// /// Intelligence multiplier when you're not a paragon. /// + [Obsolete("Use GameModsConfig instead.")] public float IntelligenceMultiplier { get => GetFloat(nameof(IntelligenceMultiplier), 1f); @@ -257,6 +278,7 @@ namespace DiIiS_NA.GameServer /// /// Intelligence multiplier when you're a paragon. /// + [Obsolete("Use GameModsConfig instead.")] public float IntelligenceParagonMultiplier { get => GetFloat(nameof(IntelligenceParagonMultiplier), 1f); @@ -266,6 +288,7 @@ namespace DiIiS_NA.GameServer /// /// Vitality multiplier when you're not a paragon. /// + [Obsolete("Use GameModsConfig instead.")] public float VitalityMultiplier { get => GetFloat(nameof(VitalityMultiplier), 1f); @@ -275,6 +298,7 @@ namespace DiIiS_NA.GameServer /// /// Vitality multiplier when you're a paragon. /// + [Obsolete("Use GameModsConfig instead.")] public float VitalityParagonMultiplier { get => GetFloat(nameof(VitalityParagonMultiplier), 1f); @@ -284,6 +308,7 @@ namespace DiIiS_NA.GameServer /// /// Auto finishes nephalem rift when there's or less monsters left. /// + [Obsolete("Use GameModsConfig instead.")] public bool NephalemRiftAutoFinish { get => GetBoolean(nameof(NephalemRiftAutoFinish), false); @@ -293,6 +318,7 @@ namespace DiIiS_NA.GameServer /// /// If is enabled, this is the threshold. /// + [Obsolete("Use GameModsConfig instead.")] public int NephalemRiftAutoFinishThreshold { get => GetInt(nameof(NephalemRiftAutoFinishThreshold), 2); @@ -302,6 +328,7 @@ namespace DiIiS_NA.GameServer /// /// Nephalem Rifts chance of spawning a orb. /// + [Obsolete("Use GameModsConfig instead.")] public float NephalemRiftOrbsChance { get => GetFloat(nameof(NephalemRiftOrbsChance), 0f); @@ -311,6 +338,7 @@ namespace DiIiS_NA.GameServer /// /// Forces the game to reveal all the map. /// + [Obsolete("Use GameModsConfig instead.")] public bool ForceMinimapVisibility { get => GetBoolean(nameof(ForceMinimapVisibility), false); diff --git a/src/DiIiS-NA/D3-GameServer/ParagonMod.cs b/src/DiIiS-NA/D3-GameServer/ParagonMod.cs new file mode 100644 index 0000000..7732c58 --- /dev/null +++ b/src/DiIiS-NA/D3-GameServer/ParagonMod.cs @@ -0,0 +1,16 @@ +public class ParagonConfig +{ + public T Normal { get; set; } + public T Paragon { get; set; } + + public ParagonConfig() {} + public ParagonConfig(T defaultValue) : this(defaultValue, defaultValue) + { + } + + public ParagonConfig(T normal, T paragon) + { + Normal = normal; + Paragon = paragon; + } +} \ No newline at end of file diff --git a/src/DiIiS-NA/Program.cs b/src/DiIiS-NA/Program.cs index f945d21..1458034 100644 --- a/src/DiIiS-NA/Program.cs +++ b/src/DiIiS-NA/Program.cs @@ -33,6 +33,7 @@ using System.Security.Permissions; using System.Threading; using System.Threading.Tasks; using DiIiS_NA.Core.Extensions; +using DiIiS_NA.D3_GameServer; using Spectre.Console; using Environment = System.Environment; @@ -65,11 +66,19 @@ namespace DiIiS_NA public static string RestServerIp = RestConfig.Instance.IP; public static string PublicGameServerIp = DiIiS_NA.GameServer.NATConfig.Instance.PublicIP; - public static int Build => 30; - public static int Stage => 2; + public const int Build = 30; + public const int Stage = 3; public static TypeBuildEnum TypeBuild => TypeBuildEnum.Beta; private static bool DiabloCoreEnabled = DiIiS_NA.GameServer.GameServerConfig.Instance.CoreActive; + private static readonly CancellationTokenSource CancellationTokenSource = new(); + public static readonly CancellationToken Token = CancellationTokenSource.Token; + public static void Cancel() => CancellationTokenSource.Cancel(); + public static void CancelAfter(TimeSpan span) => CancellationTokenSource.CancelAfter(span); + public static bool IsCancellationRequested() => CancellationTokenSource.IsCancellationRequested; + + public void MergeCancellationWith(params CancellationToken[] tokens) => + CancellationTokenSource.CreateLinkedTokenSource(tokens); static void WriteBanner() { void RightTextRule(string text, string ruleStyle) => AnsiConsole.Write(new Rule(text).RuleStyle(ruleStyle)); @@ -104,7 +113,7 @@ namespace DiIiS_NA if (!DiabloCoreEnabled) Logger.Warning("Diablo III Core is $[red]$disabled$[/]$."); #endif - + var mod = GameModsConfig.Instance; #pragma warning disable CS4014 Task.Run(async () => #pragma warning restore CS4014 @@ -126,6 +135,9 @@ namespace DiIiS_NA $"Memory: {totalMemory:0.000} GB | " + $"CPU Time: {cpuTime.ToSmallText()} | " + $"Uptime: {uptime.ToSmallText()}"; + + if (IsCancellationRequested()) + text = "SHUTTING DOWN: " + text; if (SetTitle(text)) await Task.Delay(1000); else @@ -242,15 +254,15 @@ namespace DiIiS_NA IChannel boundChannel = await serverBootstrap.BindAsync(loginConfig.Port); - Logger.Info( - "$[bold red3_1]$Tip:$[/]$ graceful shutdown with $[red3_1]$CTRL+C$[/]$ or $[red3_1]$!q[uit]$[/]$ or $[red3_1]$!exit$[/]$."); - Logger.Info("$[bold red3_1]$" + - "Tip:$[/]$ SNO breakdown with $[red3_1]$!sno$[/]$ $[red3_1]$$[/]$."); - while (true) + Logger.Info("$[bold deeppink4]$Gracefully$[/]$ shutdown with $[red3_1]$CTRL+C$[/]$ or $[deeppink4]$!q[uit]$[/]$."); + while (!IsCancellationRequested()) { var line = Console.ReadLine(); if (line is null or "!q" or "!quit" or "!exit") + { break; + } + if (line is "!cls" or "!clear" or "cls" or "clear") { AnsiConsole.Clear(); @@ -272,9 +284,11 @@ namespace DiIiS_NA if (PlayerManager.OnlinePlayers.Count > 0) { + Logger.Success("Gracefully shutting down..."); Logger.Info( $"Server is shutting down in 1 minute, $[blue]${PlayerManager.OnlinePlayers.Count} players$[/]$ are still online."); PlayerManager.SendWhisper("Server is shutting down in 1 minute."); + await Task.Delay(TimeSpan.FromMinutes(1)); } @@ -292,35 +306,39 @@ namespace DiIiS_NA } } - private static void Shutdown(Exception exception = null) + private static bool _shuttingDown = false; + public static void Shutdown(Exception exception = null) { - // if (!IsTargetEnabled("ansi")) + if (_shuttingDown) return; + _shuttingDown = true; + if (!IsCancellationRequested()) + Cancel(); + + AnsiTarget.StopIfRunning(IsTargetEnabled("ansi")); + if (exception != null) { - AnsiTarget.StopIfRunning(IsTargetEnabled("ansi")); - if (exception != null) - { - AnsiConsole.WriteLine("An unhandled exception occured at initialization. Please report this to the developers."); - AnsiConsole.WriteException(exception); - } - AnsiConsole.Progress().Start(ctx => - { - var task = ctx.AddTask("[darkred_1]Shutting down[/] [white]in[/] [red underline]10 seconds[/]"); - for (int i = 1; i < 11; i++) - { - task.Description = $"[darkred_1]Shutting down[/] [white]in[/] [red underline]{11 - i} seconds[/]"; - for (int j = 0; j < 10; j++) - { - task.Increment(1); - Thread.Sleep(100); - - } - } - - task.Description = $"[darkred_1]Shutting down[/]"; - - task.StopTask(); - }); + AnsiConsole.WriteLine( + "An unhandled exception occured at initialization. Please report this to the developers."); + AnsiConsole.WriteException(exception); } + + AnsiConsole.Progress().Start(ctx => + { + var task = ctx.AddTask("[darkred_1]Shutting down[/] [white]in[/] [red underline]10 seconds[/]"); + for (int i = 1; i < 11; i++) + { + task.Description = $"[darkred_1]Shutting down[/] [white]in[/] [red underline]{11 - i} seconds[/]"; + for (int j = 0; j < 10; j++) + { + task.Increment(1); + Thread.Sleep(100); + } + } + + task.Description = $"[darkred_1]Shutting down now.[/]"; + task.StopTask(); + }); + Environment.Exit(exception is null ? 0 : -1); }