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
This commit is contained in:
Lucca Faria Ferri 2023-06-19 07:41:22 -07:00
parent 56dd6194a1
commit f3ccab713e
21 changed files with 510 additions and 175 deletions

View File

@ -1,5 +1,7 @@
using System; using System;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -24,12 +26,12 @@ public class AnsiTarget : LogTarget
_table = new Table().Expand().ShowFooters().ShowHeaders().Border(TableBorder.Rounded); _table = new Table().Expand().ShowFooters().ShowHeaders().Border(TableBorder.Rounded);
if (IncludeTimeStamps) if (IncludeTimeStamps)
_table.AddColumn("Time"); _table.AddColumn("Time").Centered();
_table _table
.AddColumn("Level") .AddColumn("Level").RightAligned()
.AddColumn("Logger") .AddColumn("Message").Centered()
.AddColumn("Message") .AddColumn("Logger").LeftAligned()
.AddColumn("Error"); .AddColumn("Error").RightAligned();
AnsiConsole.Live(_table).StartAsync(async ctx => AnsiConsole.Live(_table).StartAsync(async ctx =>
{ {
@ -101,6 +103,15 @@ public class AnsiTarget : LogTarget
} }
private static Dictionary<string, string> _replacements = new ()
{
["["] = "[[",
["]"] = "]]",
["$[[/]]$"] = "[/]",
["$[["] = "[",
["]]$"] = "]"
};
/// <summary> /// <summary>
/// Performs a cleanup on the target. /// Performs a cleanup on the target.
/// All [ becomes [[, and ] becomes ]] (for ignoring ANSI codes) /// All [ becomes [[, and ] becomes ]] (for ignoring ANSI codes)
@ -112,82 +123,58 @@ public class AnsiTarget : LogTarget
/// </summary> /// </summary>
/// <param name="x"></param> /// <param name="x"></param>
/// <returns></returns> /// <returns></returns>
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) public override void LogMessage(Logger.Level level, string logger, string message)
{ {
if (CancellationTokenSource.IsCancellationRequested) if (CancellationTokenSource.IsCancellationRequested) return;
return;
try try
{ {
if (IncludeTimeStamps) AddRow(level, logger, message, "");
_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());
} }
catch (Exception ex) catch (Exception ex)
{ {
var regex = new Regex(@"\$\[.*?\]\$"); AddRow(level, logger, Cleanup(StripMarkup(message)), ex.Message, true);
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());
}
} }
} }
public override void LogException(Logger.Level level, string logger, string message, Exception exception) public override void LogException(Logger.Level level, string logger, string message, Exception exception)
{ {
if (CancellationTokenSource.IsCancellationRequested) if (CancellationTokenSource.IsCancellationRequested) return;
return;
try try
{ {
if (IncludeTimeStamps) AddRow(level, logger, message, $"[underline red3_1 on white]{exception.GetType().Name}[/]\n" + Cleanup(exception.Message), exFormat: true);
_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());
} }
catch (Exception ex) catch (Exception ex)
{
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 regex = new Regex(@"\$\[.*?\]\$");
var matches = regex.Matches(message); var matches = regex.Matches(message);
@ -196,38 +183,21 @@ public class AnsiTarget : LogTarget
message = message.Replace(match.Value, ""); message = message.Replace(match.Value, "");
} }
if (IncludeTimeStamps) return message;
{
_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());
}
}
} }
private static Style GetStyleByLevel(Logger.Level level) private static Style GetStyleByLevel(Logger.Level level)
{ {
return level switch return level switch
{ {
Logger.Level.RenameAccountLog => new Style(Color.DarkSlateGray3),// Logger.Level.RenameAccountLog => new Style(Color.Gold1),//
Logger.Level.ChatMessage => new Style(Color.DarkSlateGray2),// Logger.Level.ChatMessage => new Style(Color.Plum2),//
Logger.Level.Debug => new Style(Color.Olive),// Logger.Level.Debug => new Style(Color.Grey62),//
Logger.Level.MethodTrace => new Style(Color.DarkOliveGreen1_1),// Logger.Level.MethodTrace => new Style(Color.Grey74, decoration: Decoration.Dim | Decoration.Italic),//
Logger.Level.Trace => new Style(Color.BlueViolet),// Logger.Level.Trace => new Style(Color.Grey82),//
Logger.Level.Info => new Style(Color.White), Logger.Level.Info => new Style(Color.SteelBlue),
Logger.Level.Success => new Style(Color.Green3_1), Logger.Level.Success => new Style(Color.DarkOliveGreen3_2),
Logger.Level.Warn => new Style(Color.Yellow),// Logger.Level.Warn => new Style(Color.DarkOrange),//
Logger.Level.Error => new Style(Color.IndianRed1),// Logger.Level.Error => new Style(Color.IndianRed1),//
Logger.Level.Fatal => new Style(Color.Red3_1),// Logger.Level.Fatal => new Style(Color.Red3_1),//
Logger.Level.PacketDump => new Style(Color.Maroon),// Logger.Level.PacketDump => new Style(Color.Maroon),//

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.CompilerServices;
namespace DiIiS_NA.Core.Logging namespace DiIiS_NA.Core.Logging
{ {
@ -25,7 +26,7 @@ namespace DiIiS_NA.Core.Logging
/// Creates and returns a logger named with declaring type. /// Creates and returns a logger named with declaring type.
/// </summary> /// </summary>
/// <returns>A <see cref="Logger"/> instance.</returns> /// <returns>A <see cref="Logger"/> instance.</returns>
public static Logger CreateLogger() public static Logger CreateLogger([CallerFilePath] string filePath = "")
{ {
var frame = new StackFrame(1, false); // read stack frame. var frame = new StackFrame(1, false); // read stack frame.
var name = frame.GetMethod().DeclaringType.Name; // get declaring type's name. 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. if (name == null) // see if we got a name.
throw new Exception("Error getting full name for declaring type."); 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.
} }
/// <summary> /// <summary>
@ -41,10 +42,10 @@ namespace DiIiS_NA.Core.Logging
/// </summary> /// </summary>
/// <param name="name"></param> /// <param name="name"></param>
/// <returns>A <see cref="Logger"/> instance.</returns> /// <returns>A <see cref="Logger"/> instance.</returns>
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. 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. return Loggers[name]; // return the newly created logger.
} }

View File

@ -13,15 +13,17 @@ namespace DiIiS_NA.Core.Logging
public class Logger public class Logger
{ {
public string Name { get; protected set; } public string Name { get; protected set; }
public string FilePath { get; protected set; }
/// <summary> /// <summary>
/// A logger base type is used to create a logger instance. /// A logger base type is used to create a logger instance.
/// E.g. ConsoleTarget, FileTarget, etc. /// E.g. ConsoleTarget, FileTarget, etc.
/// </summary> /// </summary>
/// <param name="name">Logger name</param> /// <param name="name">Logger name</param>
public Logger(string name) public Logger(string name, string filePath = null)
{ {
Name = name; Name = name;
FilePath = filePath;
} }
public enum Level public enum Level

View File

@ -79,7 +79,7 @@ namespace DiIiS_NA.Core.MPQ
public void Init() public void Init()
{ {
Logger.Info("Loading Diablo III Assets.."); Logger.Info("Loading Diablo III Assets...");
DictSNOAccolade = Dicts.LoadAccolade(); DictSNOAccolade = Dicts.LoadAccolade();
DictSNOAct = Dicts.LoadActs(); DictSNOAct = Dicts.LoadActs();
DictSNOActor = Dicts.LoadActors(); DictSNOActor = Dicts.LoadActors();

View File

@ -30,11 +30,11 @@ namespace DiIiS_NA.Core.MPQ
var mpqFile = MPQStorage.GetMPQFile(file); var mpqFile = MPQStorage.GetMPQFile(file);
if (mpqFile == null) 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; return;
} }
this.BaseMPQFiles.Add(mpqFile); this.BaseMPQFiles.Add(mpqFile);
Logger.Trace("Added MPQ storage: {0}.", file); Logger.Debug($"Added MPQ storage: $[white underline]${file}$[/]$.");
} }
this.PatchPattern = patchPattern; this.PatchPattern = patchPattern;
@ -45,7 +45,7 @@ namespace DiIiS_NA.Core.MPQ
this.Loaded = true; this.Loaded = true;
else 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);
} }
} }

View File

@ -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!");
}
}

View File

@ -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.AccountsSystem;
using DiIiS_NA.LoginServer.Battle; using DiIiS_NA.LoginServer.Battle;

View File

@ -4,6 +4,7 @@ using DiIiS_NA.GameServer.GSSystem.AISystem.Brains;
using DiIiS_NA.GameServer.MessageSystem; using DiIiS_NA.GameServer.MessageSystem;
using DiIiS_NA.Core.Logging; using DiIiS_NA.Core.Logging;
using DiIiS_NA.Core.MPQ.FileFormats; using DiIiS_NA.Core.MPQ.FileFormats;
using DiIiS_NA.D3_GameServer;
namespace DiIiS_NA.GameServer.GSSystem.ActorSystem.Implementations 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; //this.Attributes[GameAttribute.Immune_To_Charm] = true;
Attributes[GameAttributes.using_Bossbar] = true; Attributes[GameAttributes.using_Bossbar] = true;
Attributes[GameAttributes.InBossEncounter] = true; Attributes[GameAttributes.InBossEncounter] = true;
Attributes[GameAttributes.Hitpoints_Max] *= GameServerConfig.Instance.BossHealthMultiplier; Attributes[GameAttributes.Hitpoints_Max] *= GameModsConfig.Instance.Boss.HealthMultiplier;
Attributes[GameAttributes.Damage_Weapon_Min, 0] *= GameServerConfig.Instance.BossDamageMultiplier; Attributes[GameAttributes.Damage_Weapon_Min, 0] *= GameModsConfig.Instance.Boss.DamageMultiplier;
Attributes[GameAttributes.Damage_Weapon_Delta, 0] *= GameServerConfig.Instance.BossDamageMultiplier; Attributes[GameAttributes.Damage_Weapon_Delta, 0] *= GameModsConfig.Instance.Boss.DamageMultiplier;
Attributes[GameAttributes.Hitpoints_Cur] = Attributes[GameAttributes.Hitpoints_Max_Total]; Attributes[GameAttributes.Hitpoints_Cur] = Attributes[GameAttributes.Hitpoints_Max_Total];
Attributes[GameAttributes.TeamID] = 10; Attributes[GameAttributes.TeamID] = 10;

View File

@ -6,6 +6,7 @@ using GameBalance = DiIiS_NA.Core.MPQ.FileFormats.GameBalance;
using DiIiS_NA.GameServer.GSSystem.ObjectsSystem; using DiIiS_NA.GameServer.GSSystem.ObjectsSystem;
using DiIiS_NA.Core.Logging; using DiIiS_NA.Core.Logging;
using DiIiS_NA.Core.MPQ.FileFormats; using DiIiS_NA.Core.MPQ.FileFormats;
using DiIiS_NA.D3_GameServer;
using DiIiS_NA.GameServer.GSSystem.TickerSystem; using DiIiS_NA.GameServer.GSSystem.TickerSystem;
using DiIiS_NA.GameServer.MessageSystem; using DiIiS_NA.GameServer.MessageSystem;
using DiIiS_NA.GameServer.Core.Types.SNO; 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] = (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] = ((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) if (World.Game.ConnectedPlayers.Length > 1)
Attributes[GameAttributes.Hitpoints_Max_Percent_Bonus_Multiplicative] = Attributes[GameAttributes.Hitpoints_Max_Percent_Bonus_Multiplicative];// / 2f; Attributes[GameAttributes.Hitpoints_Max_Percent_Bonus_Multiplicative] = Attributes[GameAttributes.Hitpoints_Max_Percent_Bonus_Multiplicative];// / 2f;
var hpMax = Attributes[GameAttributes.Hitpoints_Max]; var hpMax = Attributes[GameAttributes.Hitpoints_Max];
var hpTotal = Attributes[GameAttributes.Hitpoints_Max_Total]; var hpTotal = Attributes[GameAttributes.Hitpoints_Max_Total];
float damageMin = monsterLevels.MonsterLevel[World.Game.MonsterLevel].Dmg * DmgMultiplier;// * 0.5f; float damageMin = monsterLevels.MonsterLevel[World.Game.MonsterLevel].Dmg * DmgMultiplier;// * 0.5f;
float damageDelta = damageMin; 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; Attributes[GameAttributes.Damage_Weapon_Delta, 0] = damageDelta;
if (monsterLevel > 30) if (monsterLevel > 30)
{ {
Attributes[GameAttributes.Hitpoints_Max_Percent_Bonus_Multiplicative] = Attributes[GameAttributes.Hitpoints_Max_Percent_Bonus_Multiplicative];// * 0.5f; 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; Attributes[GameAttributes.Damage_Weapon_Delta, 0] = damageDelta;
} }
if (monsterLevel > 60) if (monsterLevel > 60)
{ {
Attributes[GameAttributes.Hitpoints_Max_Percent_Bonus_Multiplicative] = Attributes[GameAttributes.Hitpoints_Max_Percent_Bonus_Multiplicative];// * 0.7f; 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; //this.Attributes[GameAttribute.Damage_Weapon_Delta, 0] = DamageDelta * 0.5f;
} }

View File

@ -270,7 +270,7 @@ namespace DiIiS_NA.D3_GameServer.GSSystem.GameSystem
if (!Game.Empty) if (!Game.Empty)
{ {
RevealQuestProgress(); 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) Quests[Game.CurrentQuest].Steps[Game.CurrentStep].Saveable)
SaveQuestProgress(false); SaveQuestProgress(false);
} }

View File

@ -18,6 +18,7 @@ using DiIiS_NA.GameServer.Core.Types.TagMap;
using DiIiS_NA.GameServer.MessageSystem; using DiIiS_NA.GameServer.MessageSystem;
using DiIiS_NA.LoginServer.Toons; using DiIiS_NA.LoginServer.Toons;
using DiIiS_NA.Core.Helpers.Math; using DiIiS_NA.Core.Helpers.Math;
using DiIiS_NA.D3_GameServer;
using DiIiS_NA.GameServer.GSSystem.PlayerSystem; using DiIiS_NA.GameServer.GSSystem.PlayerSystem;
namespace DiIiS_NA.GameServer.GSSystem.ItemsSystem namespace DiIiS_NA.GameServer.GSSystem.ItemsSystem
@ -1358,8 +1359,8 @@ namespace DiIiS_NA.GameServer.GSSystem.ItemsSystem
private static void RandomSetUnidentified(Item item) => item.Unidentified = private static void RandomSetUnidentified(Item item) => item.Unidentified =
FastRandom.Instance.Chance(item.Name.Contains("unique", StringComparison.InvariantCultureIgnoreCase) FastRandom.Instance.Chance(item.Name.Contains("unique", StringComparison.InvariantCultureIgnoreCase)
|| item.ItemDefinition.Quality is ItemTable.ItemQuality.Legendary or ItemTable.ItemQuality.Special or ItemTable.ItemQuality.Set || item.ItemDefinition.Quality is ItemTable.ItemQuality.Legendary or ItemTable.ItemQuality.Special or ItemTable.ItemQuality.Set
? GameServerConfig.Instance.ChanceHighQualityUnidentified ? GameModsConfig.Instance.Items.UnidentifiedDropChances.HighQuality
: GameServerConfig.Instance.ChanceNormalUnidentified); : GameModsConfig.Instance.Items.UnidentifiedDropChances.NormalQuality);
// Allows cooking a custom item. // Allows cooking a custom item.
public static Item Cook(Player player, string name) public static Item Cook(Player player, string name)

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using DiIiS_NA.D3_GameServer;
namespace DiIiS_NA.GameServer.GSSystem.ItemsSystem namespace DiIiS_NA.GameServer.GSSystem.ItemsSystem
{ {
@ -613,16 +614,16 @@ namespace DiIiS_NA.GameServer.GSSystem.ItemsSystem
switch (MonsterQuality) switch (MonsterQuality)
{ {
case 0: //Normal case 0: //Normal
return new List<float> { 0.18f * GameServerConfig.Instance.RateChangeDrop }; return new List<float> { 0.18f * GameModsConfig.Instance.Rate.ChangeDrop };
case 1: //Champion case 1: //Champion
return new List<float> { 1f, 1f, 1f, 1f, 0.75f * GameServerConfig.Instance.RateChangeDrop }; return new List<float> { 1f, 1f, 1f, 1f, 0.75f * GameModsConfig.Instance.Rate.ChangeDrop };
case 2: //Rare (Elite) case 2: //Rare (Elite)
case 4: //Unique case 4: //Unique
return new List<float> { 1f, 1f, 1f, 1f, 1f }; return new List<float> { 1f, 1f, 1f, 1f, 1f };
case 7: //Boss case 7: //Boss
return new List<float> { 1f, 1f, 1f, 1f, 1f, 0.75f * GameServerConfig.Instance.RateChangeDrop, 0.4f * GameServerConfig.Instance.RateChangeDrop }; return new List<float> { 1f, 1f, 1f, 1f, 1f, 0.75f * GameModsConfig.Instance.Rate.ChangeDrop, 0.4f * GameModsConfig.Instance.Rate.ChangeDrop };
default: default:
return new List<float> { 0.12f * GameServerConfig.Instance.RateChangeDrop }; return new List<float> { 0.12f * GameModsConfig.Instance.Rate.ChangeDrop };
} }
} }

View File

@ -24,6 +24,7 @@ using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using DiIiS_NA.D3_GameServer;
using Actor = DiIiS_NA.GameServer.GSSystem.ActorSystem.Actor; using Actor = DiIiS_NA.GameServer.GSSystem.ActorSystem.Actor;
namespace DiIiS_NA.GameServer.GSSystem.MapSystem namespace DiIiS_NA.GameServer.GSSystem.MapSystem
@ -549,7 +550,7 @@ namespace DiIiS_NA.GameServer.GSSystem.MapSystem
SceneSNO = SceneSNO.Id, SceneSNO = SceneSNO.Id,
Transform = Transform, Transform = Transform,
WorldID = World.GlobalID, WorldID = World.GlobalID,
MiniMapVisibility = GameServerConfig.Instance.ForceMinimapVisibility MiniMapVisibility = GameModsConfig.Instance.Minimap.ForceVisibility
}; };
} }

View File

@ -10,6 +10,7 @@ using DiIiS_NA.Core.Helpers.Math;
using DiIiS_NA.Core.Logging; using DiIiS_NA.Core.Logging;
using DiIiS_NA.Core.MPQ; using DiIiS_NA.Core.MPQ;
using DiIiS_NA.Core.MPQ.FileFormats; using DiIiS_NA.Core.MPQ.FileFormats;
using DiIiS_NA.D3_GameServer;
using DiIiS_NA.D3_GameServer.Core.Types.SNO; using DiIiS_NA.D3_GameServer.Core.Types.SNO;
using DiIiS_NA.GameServer.Core.Types.Math; using DiIiS_NA.GameServer.Core.Types.Math;
using DiIiS_NA.GameServer.Core.Types.QuadTrees; using DiIiS_NA.GameServer.Core.Types.QuadTrees;
@ -930,7 +931,7 @@ namespace DiIiS_NA.GameServer.GSSystem.MapSystem
/// <param name="position">The position for drop.</param> /// <param name="position">The position for drop.</param>
public void SpawnGold(Actor source, Player player, int Min = -1) 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) if (Min != -1)
amount += Min; amount += Min;
var item = ItemGenerator.CreateGold(player, amount); // somehow the actual ammount is not shown on ground /raist. var item = ItemGenerator.CreateGold(player, amount); // somehow the actual ammount is not shown on ground /raist.

View File

@ -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.Game;
using DiIiS_NA.GameServer.MessageSystem.Message.Definitions.Hireling; using DiIiS_NA.GameServer.MessageSystem.Message.Definitions.Hireling;
using DiIiS_NA.Core.Helpers.Hash; using DiIiS_NA.Core.Helpers.Hash;
using DiIiS_NA.D3_GameServer;
using DiIiS_NA.GameServer.MessageSystem.Message.Definitions.Encounter; using DiIiS_NA.GameServer.MessageSystem.Message.Definitions.Encounter;
using DiIiS_NA.D3_GameServer.Core.Types.SNO; using DiIiS_NA.D3_GameServer.Core.Types.SNO;
using DiIiS_NA.D3_GameServer.GSSystem.ActorSystem.Implementations.Artisans; using DiIiS_NA.D3_GameServer.GSSystem.ActorSystem.Implementations.Artisans;
@ -2481,6 +2482,21 @@ public class Player : Actor, IMessageConsumer, IUpdateable
public bool SpeedCheckDisabled = false; 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) public static byte[] StringToByteArray(string hex)
{ {
return Enumerable.Range(0, hex.Length) return Enumerable.Range(0, hex.Length)
@ -2675,8 +2691,9 @@ public class Player : Actor, IMessageConsumer, IUpdateable
Logger.WarnException(e, "questEvent()"); Logger.WarnException(e, "questEvent()");
} }
} }
// Reset resurrection charges on zone change - TODO: do not reset charges on reentering the same zone // Reset resurrection charges on zone change
Attributes[GameAttributes.Corpse_Resurrection_Charges] = GameServerConfig.Instance.ResurrectionCharges; // TODO: do not reset charges on reentering the same zone
Attributes[GameAttributes.Corpse_Resurrection_Charges] = GameModsConfig.Instance.Health.ResurrectionCharges;
#if DEBUG #if DEBUG
Logger.Warn($"Player Location {Toon.Name}, Scene: {CurrentScene.SceneSNO.Name} SNO: {CurrentScene.SceneSNO.Id} LevelArea: {CurrentScene.Specification.SNOLevelAreas[0]}"); Logger.Warn($"Player Location {Toon.Name}, Scene: {CurrentScene.SceneSNO.Name} SNO: {CurrentScene.SceneSNO.Id} LevelArea: {CurrentScene.Specification.SNOLevelAreas[0]}");
@ -3995,7 +4012,7 @@ public class Player : Actor, IMessageConsumer, IUpdateable
get get
{ {
var baseStrength = 0.0f; var baseStrength = 0.0f;
var multiplier = ParagonLevel > 0 ? GameServerConfig.Instance.StrengthParagonMultiplier : GameServerConfig.Instance.StrengthMultiplier; var multiplier = StrengthMultiplier;
baseStrength = Toon.HeroTable.CoreAttribute == GameBalance.PrimaryAttribute.Strength baseStrength = Toon.HeroTable.CoreAttribute == GameBalance.PrimaryAttribute.Strength
? Toon.HeroTable.Strength + (Level - 1) * 3 ? Toon.HeroTable.Strength + (Level - 1) * 3
: Toon.HeroTable.Strength + (Level - 1); : Toon.HeroTable.Strength + (Level - 1);
@ -4011,8 +4028,7 @@ public class Player : Actor, IMessageConsumer, IUpdateable
{ {
get get
{ {
var multiplier = ParagonLevel > 0 ? GameServerConfig.Instance.DexterityParagonMultiplier : GameServerConfig.Instance.DexterityMultiplier; var multiplier = DexterityMultiplier;
return Toon.HeroTable.CoreAttribute == GameBalance.PrimaryAttribute.Dexterity return Toon.HeroTable.CoreAttribute == GameBalance.PrimaryAttribute.Dexterity
? Toon.HeroTable.Dexterity + (Level - 1) * 3 * multiplier ? Toon.HeroTable.Dexterity + (Level - 1) * 3 * multiplier
: Toon.HeroTable.Dexterity + (Level - 1) * multiplier; : Toon.HeroTable.Dexterity + (Level - 1) * multiplier;
@ -4022,7 +4038,7 @@ public class Player : Actor, IMessageConsumer, IUpdateable
public float TotalDexterity => public float TotalDexterity =>
Attributes[GameAttributes.Dexterity] + Inventory.GetItemBonus(GameAttributes.Dexterity_Item); 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 => public float TotalVitality =>
Attributes[GameAttributes.Vitality] + Inventory.GetItemBonus(GameAttributes.Vitality_Item); Attributes[GameAttributes.Vitality] + Inventory.GetItemBonus(GameAttributes.Vitality_Item);
@ -4031,7 +4047,7 @@ public class Player : Actor, IMessageConsumer, IUpdateable
{ {
get get
{ {
var multiplier = ParagonLevel > 0 ? GameServerConfig.Instance.IntelligenceParagonMultiplier : GameServerConfig.Instance.IntelligenceMultiplier; var multiplier = IntelligenceMultiplier;
return Toon.HeroTable.CoreAttribute == GameBalance.PrimaryAttribute.Intelligence return Toon.HeroTable.CoreAttribute == GameBalance.PrimaryAttribute.Intelligence
? Toon.HeroTable.Intelligence + (Level - 1) * 3 * multiplier ? Toon.HeroTable.Intelligence + (Level - 1) * 3 * multiplier
: Toon.HeroTable.Intelligence + (Level - 1) * multiplier; : Toon.HeroTable.Intelligence + (Level - 1) * multiplier;
@ -4090,7 +4106,7 @@ public class Player : Actor, IMessageConsumer, IUpdateable
}, },
SkillSlotEverAssigned = 0x0F, //0xB4, SkillSlotEverAssigned = 0x0F, //0xB4,
PlaytimeTotal = Toon.TimePlayed, PlaytimeTotal = Toon.TimePlayed,
WaypointFlags = GameServerConfig.Instance.UnlockAllWaypoints ? 0x0000ffff : World.Game.WaypointFlags, WaypointFlags = GameModsConfig.Instance.Quest.UnlockAllWaypoints ? 0x0000ffff : World.Game.WaypointFlags,
HirelingData = new HirelingSavedData() HirelingData = new HirelingSavedData()
{ {
HirelingInfos = HirelingInfo, HirelingInfos = HirelingInfo,
@ -5499,7 +5515,7 @@ public class Player : Actor, IMessageConsumer, IUpdateable
{ {
if (InGameClient.Game.ActiveNephalemTimer && InGameClient.Game.ActiveNephalemKilledMobs == false) 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) foreach (var plr in InGameClient.Game.Players.Values)
{ {
plr.InGameClient.SendMessage(new FloatDataMessage(Opcodes.DunggeonFinderProgressGlyphPickUp) plr.InGameClient.SendMessage(new FloatDataMessage(Opcodes.DunggeonFinderProgressGlyphPickUp)

View File

@ -1,4 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using DiIiS_NA.D3_GameServer;
using DiIiS_NA.GameServer.GSSystem.PlayerSystem; using DiIiS_NA.GameServer.GSSystem.PlayerSystem;
using DiIiS_NA.GameServer.GSSystem.TickerSystem; using DiIiS_NA.GameServer.GSSystem.TickerSystem;
using DiIiS_NA.LoginServer; using DiIiS_NA.LoginServer;
@ -12,8 +13,8 @@ namespace DiIiS_NA.GameServer.GSSystem.PowerSystem.Implementations.General
public override IEnumerable<TickTimer> Run() public override IEnumerable<TickTimer> Run()
{ {
if (User is not Player player) yield break; if (User is not Player player) yield break;
player.AddPercentageHP(GameServerConfig.Instance.HealthPotionRestorePercentage); player.AddPercentageHP(GameModsConfig.Instance.Health.PotionRestorePercentage);
AddBuff(player, player, new CooldownBuff(30211, TickTimer.WaitSeconds(player.World.Game, GameServerConfig.Instance.HealthPotionCooldown))); AddBuff(player, player, new CooldownBuff(30211, TickTimer.WaitSeconds(player.World.Game, GameModsConfig.Instance.Health.PotionCooldown)));
} }
} }
} }

View File

@ -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.Effect;
using DiIiS_NA.GameServer.MessageSystem.Message.Definitions.Combat; using DiIiS_NA.GameServer.MessageSystem.Message.Definitions.Combat;
using DiIiS_NA.Core.Helpers.Math; using DiIiS_NA.Core.Helpers.Math;
using DiIiS_NA.D3_GameServer;
using DiIiS_NA.LoginServer.Toons; using DiIiS_NA.LoginServer.Toons;
using DiIiS_NA.GameServer.Core.Types.TagMap; using DiIiS_NA.GameServer.Core.Types.TagMap;
using DiIiS_NA.GameServer.GSSystem.GeneratorsSystem; using DiIiS_NA.GameServer.GSSystem.GeneratorsSystem;
@ -425,7 +426,7 @@ namespace DiIiS_NA.GameServer.GSSystem.PowerSystem.Payloads
{ {
grantedExp = (int)(grantedExp * rangedPlayer.World.Game.XpModifier); 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)); rangedPlayer.UpdateExp(Math.Max((int)tempExp, 1));
var a = (int)rangedPlayer.Attributes[GameAttributes.Experience_Bonus]; 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.ActiveNephalemTimer && Target.World.Game.ActiveNephalemKilledMobs == false)
{ {
Target.World.Game.ActiveNephalemProgress += Target.World.Game.ActiveNephalemProgress +=
GameServerConfig.Instance.NephalemRiftProgressMultiplier * (Target.Quality + 1); GameModsConfig.Instance.NephalemRift.ProgressMultiplier * (Target.Quality + 1);
Player master = null; Player master = null;
foreach (var plr3 in Target.World.Game.Players.Values) foreach (var plr3 in Target.World.Game.Players.Values)
{ {
if (plr3.PlayerIndex == 0) if (plr3.PlayerIndex == 0)
master = plr3; 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 SimpleMessage(Opcodes.KillCounterRefresh));
plr3.InGameClient.SendMessage(new FloatDataMessage(Opcodes.DungeonFinderProgressMessage) 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 //spawn spheres for mining indicator
for (int i = 0; i < Target.Quality + 1; i++) 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 is less than the drop rate, drop the item
if (seed < rate * (1f if (seed < rate * (1f
+ lootSpawnPlayer.Attributes[GameAttributes.Magic_Find]) + lootSpawnPlayer.Attributes[GameAttributes.Magic_Find])
* GameServerConfig.Instance.RateDrop) * GameModsConfig.Instance.Rate.Drop)
{ {
//Logger.Debug("rate: {0}", rate); //Logger.Debug("rate: {0}", rate);
var lootQuality = Target.World.Game.IsHardcore var lootQuality = Target.World.Game.IsHardcore

View File

@ -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<float> Strength { get; set; } = new(1f);
public ParagonConfig<float> Dexterity { get; set; } = new(1f);
public ParagonConfig<float> Intelligence { get; set; } = new(1f);
public ParagonConfig<float> 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<T>(this string obj, out T value)
where T: class, new()
{
try
{
value = obj.FromJson<T>();
return true;
}
catch (Exception ex)
{
value = default;
return false;
}
}
public static bool TryFromJson<T>(this string obj, out T value, out Exception exception)
where T: class, new()
{
try
{
value = obj.FromJson<T>();
exception = null;
return true;
}
catch (Exception ex)
{
value = default;
exception = ex;
return false;
}
}
public static T FromJson<T>(this string obj)
where T: class, new()
{
return JsonConvert.DeserializeObject<T>(obj);
}
public static dynamic FromJsonDynamic(this string obj)
{
return obj.FromJson<ExpandoObject>();
}
}

View File

@ -71,6 +71,7 @@ namespace DiIiS_NA.GameServer
/// <summary> /// <summary>
/// Rate of experience gain. /// Rate of experience gain.
/// </summary> /// </summary>
[Obsolete("Use GameModsConfig instead.")]
public float RateExp public float RateExp
{ {
get => GetFloat(nameof(RateExp), 1); get => GetFloat(nameof(RateExp), 1);
@ -80,6 +81,7 @@ namespace DiIiS_NA.GameServer
/// <summary> /// <summary>
/// Rate of gold gain. /// Rate of gold gain.
/// </summary> /// </summary>
[Obsolete("Use GameModsConfig instead.")]
public float RateMoney public float RateMoney
{ {
get => GetFloat(nameof(RateMoney), 1); get => GetFloat(nameof(RateMoney), 1);
@ -89,12 +91,14 @@ namespace DiIiS_NA.GameServer
/// <summary> /// <summary>
/// Rate of item drop. /// Rate of item drop.
/// </summary> /// </summary>
[Obsolete("Use GameModsConfig instead.")]
public float RateDrop public float RateDrop
{ {
get => GetFloat(nameof(RateDrop), 1); get => GetFloat(nameof(RateDrop), 1);
set => Set(nameof(RateDrop), value); set => Set(nameof(RateDrop), value);
} }
[Obsolete("Use GameModsConfig instead.")]
public float RateChangeDrop public float RateChangeDrop
{ {
get => GetFloat(nameof(RateChangeDrop), 1); get => GetFloat(nameof(RateChangeDrop), 1);
@ -104,6 +108,7 @@ namespace DiIiS_NA.GameServer
/// <summary> /// <summary>
/// Rate of monster's HP. /// Rate of monster's HP.
/// </summary> /// </summary>
[Obsolete("Use GameModsConfig instead.")]
public float RateMonsterHP public float RateMonsterHP
{ {
get => GetFloat(nameof(RateMonsterHP), 1); get => GetFloat(nameof(RateMonsterHP), 1);
@ -113,6 +118,7 @@ namespace DiIiS_NA.GameServer
/// <summary> /// <summary>
/// Rate of monster's damage. /// Rate of monster's damage.
/// </summary> /// </summary>
[Obsolete("Use GameModsConfig instead.")]
public float RateMonsterDMG public float RateMonsterDMG
{ {
get => GetFloat(nameof(RateMonsterDMG), 1); get => GetFloat(nameof(RateMonsterDMG), 1);
@ -122,6 +128,7 @@ namespace DiIiS_NA.GameServer
/// <summary> /// <summary>
/// Percentage that a unique, legendary, set or special item created is unidentified /// Percentage that a unique, legendary, set or special item created is unidentified
/// </summary> /// </summary>
[Obsolete("Use GameModsConfig instead.")]
public float ChanceHighQualityUnidentified public float ChanceHighQualityUnidentified
{ {
get => GetFloat(nameof(ChanceHighQualityUnidentified), 30f); get => GetFloat(nameof(ChanceHighQualityUnidentified), 30f);
@ -131,6 +138,7 @@ namespace DiIiS_NA.GameServer
/// <summary> /// <summary>
/// Percentage that a normal item created is unidentified /// Percentage that a normal item created is unidentified
/// </summary> /// </summary>
[Obsolete("Use GameModsConfig instead.")]
public float ChanceNormalUnidentified public float ChanceNormalUnidentified
{ {
get => GetFloat(nameof(ChanceNormalUnidentified), 5f); get => GetFloat(nameof(ChanceNormalUnidentified), 5f);
@ -140,6 +148,7 @@ namespace DiIiS_NA.GameServer
/// <summary> /// <summary>
/// Resurrection charges on changing worlds /// Resurrection charges on changing worlds
/// </summary> /// </summary>
[Obsolete("Use GameModsConfig instead.")]
public int ResurrectionCharges public int ResurrectionCharges
{ {
get => GetInt(nameof(ResurrectionCharges), 3); get => GetInt(nameof(ResurrectionCharges), 3);
@ -149,6 +158,7 @@ namespace DiIiS_NA.GameServer
/// <summary> /// <summary>
/// Boss Health Multiplier /// Boss Health Multiplier
/// </summary> /// </summary>
[Obsolete("Use GameModsConfig instead.")]
public float BossHealthMultiplier public float BossHealthMultiplier
{ {
get => GetFloat(nameof(BossHealthMultiplier), 6f); get => GetFloat(nameof(BossHealthMultiplier), 6f);
@ -158,6 +168,7 @@ namespace DiIiS_NA.GameServer
/// <summary> /// <summary>
/// Boss Damage Multiplier /// Boss Damage Multiplier
/// </summary> /// </summary>
[Obsolete("Use GameModsConfig instead.")]
public float BossDamageMultiplier public float BossDamageMultiplier
{ {
get => GetFloat(nameof(BossDamageMultiplier), 3f); get => GetFloat(nameof(BossDamageMultiplier), 3f);
@ -167,6 +178,7 @@ namespace DiIiS_NA.GameServer
/// <summary> /// <summary>
/// Whether to bypass the quest's settings of "Saveable" to TRUE (unless in OpenWorld) /// Whether to bypass the quest's settings of "Saveable" to TRUE (unless in OpenWorld)
/// </summary> /// </summary>
[Obsolete("Use GameModsConfig instead.")]
public bool AutoSaveQuests public bool AutoSaveQuests
{ {
get => GetBoolean(nameof(AutoSaveQuests), false); get => GetBoolean(nameof(AutoSaveQuests), false);
@ -176,6 +188,7 @@ namespace DiIiS_NA.GameServer
/// <summary> /// <summary>
/// Progress gained when killing a monster in Nephalem Rifts /// Progress gained when killing a monster in Nephalem Rifts
/// </summary> /// </summary>
[Obsolete("Use GameModsConfig instead.")]
public float NephalemRiftProgressMultiplier public float NephalemRiftProgressMultiplier
{ {
get => GetFloat(nameof(NephalemRiftProgressMultiplier), 1f); get => GetFloat(nameof(NephalemRiftProgressMultiplier), 1f);
@ -185,6 +198,7 @@ namespace DiIiS_NA.GameServer
/// <summary> /// <summary>
/// How much a health potion heals in percentage /// How much a health potion heals in percentage
/// </summary> /// </summary>
[Obsolete("Use GameModsConfig instead.")]
public float HealthPotionRestorePercentage public float HealthPotionRestorePercentage
{ {
get => GetFloat(nameof(HealthPotionRestorePercentage), 60f); get => GetFloat(nameof(HealthPotionRestorePercentage), 60f);
@ -194,6 +208,7 @@ namespace DiIiS_NA.GameServer
/// <summary> /// <summary>
/// Cooldown (in seconds) to use a health potion again. /// Cooldown (in seconds) to use a health potion again.
/// </summary> /// </summary>
[Obsolete("Use GameModsConfig instead.")]
public float HealthPotionCooldown public float HealthPotionCooldown
{ {
get => GetFloat(nameof(HealthPotionCooldown), 30f); get => GetFloat(nameof(HealthPotionCooldown), 30f);
@ -203,6 +218,7 @@ namespace DiIiS_NA.GameServer
/// <summary> /// <summary>
/// Unlocks all waypoints in the campaign. /// Unlocks all waypoints in the campaign.
/// </summary> /// </summary>
[Obsolete("Use GameModsConfig instead.")]
public bool UnlockAllWaypoints public bool UnlockAllWaypoints
{ {
get => GetBoolean(nameof(UnlockAllWaypoints), false); get => GetBoolean(nameof(UnlockAllWaypoints), false);
@ -212,6 +228,7 @@ namespace DiIiS_NA.GameServer
/// <summary> /// <summary>
/// Strength multiplier when you're not a paragon. /// Strength multiplier when you're not a paragon.
/// </summary> /// </summary>
[Obsolete("Use GameModsConfig instead.")]
public float StrengthMultiplier public float StrengthMultiplier
{ {
get => GetFloat(nameof(StrengthMultiplier), 1f); get => GetFloat(nameof(StrengthMultiplier), 1f);
@ -221,6 +238,7 @@ namespace DiIiS_NA.GameServer
/// <summary> /// <summary>
/// Strength multiplier when you're a paragon. /// Strength multiplier when you're a paragon.
/// </summary> /// </summary>
[Obsolete("Use GameModsConfig instead.")]
public float StrengthParagonMultiplier public float StrengthParagonMultiplier
{ {
get => GetFloat(nameof(StrengthParagonMultiplier), 1f); get => GetFloat(nameof(StrengthParagonMultiplier), 1f);
@ -230,6 +248,7 @@ namespace DiIiS_NA.GameServer
/// <summary> /// <summary>
/// Dexterity multiplier when you're not a paragon. /// Dexterity multiplier when you're not a paragon.
/// </summary> /// </summary>
[Obsolete("Use GameModsConfig instead.")]
public float DexterityMultiplier public float DexterityMultiplier
{ {
get => GetFloat(nameof(DexterityMultiplier), 1f); get => GetFloat(nameof(DexterityMultiplier), 1f);
@ -239,6 +258,7 @@ namespace DiIiS_NA.GameServer
/// <summary> /// <summary>
/// Dexterity multiplier when you're a paragon. /// Dexterity multiplier when you're a paragon.
/// </summary> /// </summary>
[Obsolete("Use GameModsConfig instead.")]
public float DexterityParagonMultiplier public float DexterityParagonMultiplier
{ {
get => GetFloat(nameof(DexterityParagonMultiplier), 1f); get => GetFloat(nameof(DexterityParagonMultiplier), 1f);
@ -248,6 +268,7 @@ namespace DiIiS_NA.GameServer
/// <summary> /// <summary>
/// Intelligence multiplier when you're not a paragon. /// Intelligence multiplier when you're not a paragon.
/// </summary> /// </summary>
[Obsolete("Use GameModsConfig instead.")]
public float IntelligenceMultiplier public float IntelligenceMultiplier
{ {
get => GetFloat(nameof(IntelligenceMultiplier), 1f); get => GetFloat(nameof(IntelligenceMultiplier), 1f);
@ -257,6 +278,7 @@ namespace DiIiS_NA.GameServer
/// <summary> /// <summary>
/// Intelligence multiplier when you're a paragon. /// Intelligence multiplier when you're a paragon.
/// </summary> /// </summary>
[Obsolete("Use GameModsConfig instead.")]
public float IntelligenceParagonMultiplier public float IntelligenceParagonMultiplier
{ {
get => GetFloat(nameof(IntelligenceParagonMultiplier), 1f); get => GetFloat(nameof(IntelligenceParagonMultiplier), 1f);
@ -266,6 +288,7 @@ namespace DiIiS_NA.GameServer
/// <summary> /// <summary>
/// Vitality multiplier when you're not a paragon. /// Vitality multiplier when you're not a paragon.
/// </summary> /// </summary>
[Obsolete("Use GameModsConfig instead.")]
public float VitalityMultiplier public float VitalityMultiplier
{ {
get => GetFloat(nameof(VitalityMultiplier), 1f); get => GetFloat(nameof(VitalityMultiplier), 1f);
@ -275,6 +298,7 @@ namespace DiIiS_NA.GameServer
/// <summary> /// <summary>
/// Vitality multiplier when you're a paragon. /// Vitality multiplier when you're a paragon.
/// </summary> /// </summary>
[Obsolete("Use GameModsConfig instead.")]
public float VitalityParagonMultiplier public float VitalityParagonMultiplier
{ {
get => GetFloat(nameof(VitalityParagonMultiplier), 1f); get => GetFloat(nameof(VitalityParagonMultiplier), 1f);
@ -284,6 +308,7 @@ namespace DiIiS_NA.GameServer
/// <summary> /// <summary>
/// Auto finishes nephalem rift when there's <see cref="NephalemRiftAutoFinishThreshold"></see> or less monsters left. /// Auto finishes nephalem rift when there's <see cref="NephalemRiftAutoFinishThreshold"></see> or less monsters left.
/// </summary> /// </summary>
[Obsolete("Use GameModsConfig instead.")]
public bool NephalemRiftAutoFinish public bool NephalemRiftAutoFinish
{ {
get => GetBoolean(nameof(NephalemRiftAutoFinish), false); get => GetBoolean(nameof(NephalemRiftAutoFinish), false);
@ -293,6 +318,7 @@ namespace DiIiS_NA.GameServer
/// <summary> /// <summary>
/// If <see cref="NephalemRiftAutoFinish"></see> is enabled, this is the threshold. /// If <see cref="NephalemRiftAutoFinish"></see> is enabled, this is the threshold.
/// </summary> /// </summary>
[Obsolete("Use GameModsConfig instead.")]
public int NephalemRiftAutoFinishThreshold public int NephalemRiftAutoFinishThreshold
{ {
get => GetInt(nameof(NephalemRiftAutoFinishThreshold), 2); get => GetInt(nameof(NephalemRiftAutoFinishThreshold), 2);
@ -302,6 +328,7 @@ namespace DiIiS_NA.GameServer
/// <summary> /// <summary>
/// Nephalem Rifts chance of spawning a orb. /// Nephalem Rifts chance of spawning a orb.
/// </summary> /// </summary>
[Obsolete("Use GameModsConfig instead.")]
public float NephalemRiftOrbsChance public float NephalemRiftOrbsChance
{ {
get => GetFloat(nameof(NephalemRiftOrbsChance), 0f); get => GetFloat(nameof(NephalemRiftOrbsChance), 0f);
@ -311,6 +338,7 @@ namespace DiIiS_NA.GameServer
/// <summary> /// <summary>
/// Forces the game to reveal all the map. /// Forces the game to reveal all the map.
/// </summary> /// </summary>
[Obsolete("Use GameModsConfig instead.")]
public bool ForceMinimapVisibility public bool ForceMinimapVisibility
{ {
get => GetBoolean(nameof(ForceMinimapVisibility), false); get => GetBoolean(nameof(ForceMinimapVisibility), false);

View File

@ -0,0 +1,16 @@
public class ParagonConfig<T>
{
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;
}
}

View File

@ -33,6 +33,7 @@ using System.Security.Permissions;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using DiIiS_NA.Core.Extensions; using DiIiS_NA.Core.Extensions;
using DiIiS_NA.D3_GameServer;
using Spectre.Console; using Spectre.Console;
using Environment = System.Environment; using Environment = System.Environment;
@ -65,11 +66,19 @@ namespace DiIiS_NA
public static string RestServerIp = RestConfig.Instance.IP; public static string RestServerIp = RestConfig.Instance.IP;
public static string PublicGameServerIp = DiIiS_NA.GameServer.NATConfig.Instance.PublicIP; public static string PublicGameServerIp = DiIiS_NA.GameServer.NATConfig.Instance.PublicIP;
public static int Build => 30; public const int Build = 30;
public static int Stage => 2; public const int Stage = 3;
public static TypeBuildEnum TypeBuild => TypeBuildEnum.Beta; public static TypeBuildEnum TypeBuild => TypeBuildEnum.Beta;
private static bool DiabloCoreEnabled = DiIiS_NA.GameServer.GameServerConfig.Instance.CoreActive; 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() static void WriteBanner()
{ {
void RightTextRule(string text, string ruleStyle) => AnsiConsole.Write(new Rule(text).RuleStyle(ruleStyle)); void RightTextRule(string text, string ruleStyle) => AnsiConsole.Write(new Rule(text).RuleStyle(ruleStyle));
@ -104,7 +113,7 @@ namespace DiIiS_NA
if (!DiabloCoreEnabled) if (!DiabloCoreEnabled)
Logger.Warning("Diablo III Core is $[red]$disabled$[/]$."); Logger.Warning("Diablo III Core is $[red]$disabled$[/]$.");
#endif #endif
var mod = GameModsConfig.Instance;
#pragma warning disable CS4014 #pragma warning disable CS4014
Task.Run(async () => Task.Run(async () =>
#pragma warning restore CS4014 #pragma warning restore CS4014
@ -126,6 +135,9 @@ namespace DiIiS_NA
$"Memory: {totalMemory:0.000} GB | " + $"Memory: {totalMemory:0.000} GB | " +
$"CPU Time: {cpuTime.ToSmallText()} | " + $"CPU Time: {cpuTime.ToSmallText()} | " +
$"Uptime: {uptime.ToSmallText()}"; $"Uptime: {uptime.ToSmallText()}";
if (IsCancellationRequested())
text = "SHUTTING DOWN: " + text;
if (SetTitle(text)) if (SetTitle(text))
await Task.Delay(1000); await Task.Delay(1000);
else else
@ -242,15 +254,15 @@ namespace DiIiS_NA
IChannel boundChannel = await serverBootstrap.BindAsync(loginConfig.Port); IChannel boundChannel = await serverBootstrap.BindAsync(loginConfig.Port);
Logger.Info( Logger.Info("$[bold deeppink4]$Gracefully$[/]$ shutdown with $[red3_1]$CTRL+C$[/]$ or $[deeppink4]$!q[uit]$[/]$.");
"$[bold red3_1]$Tip:$[/]$ graceful shutdown with $[red3_1]$CTRL+C$[/]$ or $[red3_1]$!q[uit]$[/]$ or $[red3_1]$!exit$[/]$."); while (!IsCancellationRequested())
Logger.Info("$[bold red3_1]$" +
"Tip:$[/]$ SNO breakdown with $[red3_1]$!sno$[/]$ $[red3_1]$<fullSnoBreakdown(true:false)>$[/]$.");
while (true)
{ {
var line = Console.ReadLine(); var line = Console.ReadLine();
if (line is null or "!q" or "!quit" or "!exit") if (line is null or "!q" or "!quit" or "!exit")
{
break; break;
}
if (line is "!cls" or "!clear" or "cls" or "clear") if (line is "!cls" or "!clear" or "cls" or "clear")
{ {
AnsiConsole.Clear(); AnsiConsole.Clear();
@ -272,9 +284,11 @@ namespace DiIiS_NA
if (PlayerManager.OnlinePlayers.Count > 0) if (PlayerManager.OnlinePlayers.Count > 0)
{ {
Logger.Success("Gracefully shutting down...");
Logger.Info( Logger.Info(
$"Server is shutting down in 1 minute, $[blue]${PlayerManager.OnlinePlayers.Count} players$[/]$ are still online."); $"Server is shutting down in 1 minute, $[blue]${PlayerManager.OnlinePlayers.Count} players$[/]$ are still online.");
PlayerManager.SendWhisper("Server is shutting down in 1 minute."); PlayerManager.SendWhisper("Server is shutting down in 1 minute.");
await Task.Delay(TimeSpan.FromMinutes(1)); await Task.Delay(TimeSpan.FromMinutes(1));
} }
@ -292,16 +306,22 @@ 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")); AnsiTarget.StopIfRunning(IsTargetEnabled("ansi"));
if (exception != null) if (exception != null)
{ {
AnsiConsole.WriteLine("An unhandled exception occured at initialization. Please report this to the developers."); AnsiConsole.WriteLine(
"An unhandled exception occured at initialization. Please report this to the developers.");
AnsiConsole.WriteException(exception); AnsiConsole.WriteException(exception);
} }
AnsiConsole.Progress().Start(ctx => AnsiConsole.Progress().Start(ctx =>
{ {
var task = ctx.AddTask("[darkred_1]Shutting down[/] [white]in[/] [red underline]10 seconds[/]"); var task = ctx.AddTask("[darkred_1]Shutting down[/] [white]in[/] [red underline]10 seconds[/]");
@ -312,15 +332,13 @@ namespace DiIiS_NA
{ {
task.Increment(1); task.Increment(1);
Thread.Sleep(100); Thread.Sleep(100);
} }
} }
task.Description = $"[darkred_1]Shutting down[/]"; task.Description = $"[darkred_1]Shutting down now.[/]";
task.StopTask(); task.StopTask();
}); });
}
Environment.Exit(exception is null ? 0 : -1); Environment.Exit(exception is null ? 0 : -1);
} }