diff --git a/README.md b/README.md
index bb38c02..9aabaab 100644
--- a/README.md
+++ b/README.md
@@ -51,23 +51,18 @@ The currently supported version of the client: **2.7.4.84161**
### Compile and run
1. Install [.NET 7 SDK and runtime](https://dotnet.microsoft.com/en-us/download/dotnet/7.0) (just runtime, not asp.net or desktop)
2. Go to the repo directory and compile the project using this command:
- ```shell
- dotnet publish ./src/DiIiS-NA/Blizzless.csproj --configuration Release --output ./publish
- ```
-3. [Skip this stage for local game] Copy the [config.ini](configs/config.ini) file to the publish folder (It overwrites the default settings):
- - Update the parameter entries with your IP record on the network: `BindIP` and `PublicIP`.
-4. Go to the publish folder, launch Blizzless executable, wait until server start - it creates a hierarchy.
-5. Create user account(s) using console: `!account add Login Password Tag`
-
-#### Example:
-
-> !account add username@ YourPassword YourBattleTag
-
-Creates an account with Login `username@`, password `YourPassword` and BattleTag `YourBattleTag`
-
-> !account add username@ YourPassword YourBattleTag owner
-
-Creates an account with Login `username@`, password `YourPassword` and BattleTag `YourBattleTag` with rank `owner`
+```shell
+dotnet publish ./src/DiIiS-NA/Blizzless.csproj --configuration Release --output ./publish
+```
+3. __Skip this stage for local game__ Copy the [config.mods.json](https://github.com/blizzless/blizzless-diiis/blob/community/configs/config.mods.json) file to the folder, and modify however you want. A file will be generated automatically from the `config.ini` for now.
+4. Update your `config.ini` file on the published folder with your network's IP records (`BindIP` and `PublicIP`)
+5. Go to the publish folder, launch Blizzless executable, wait until server start - it creates a hierarchy.
+6. Create user account(s) using console: `!account add Login Password Tag`
+ - Example:
+ - `!account add username@ YourPassword YourBattleTag`
+ - Creates an account with Login `username@`, password `YourPassword` and BattleTag `YourBattleTag`
+ - `!account add username@ YourPassword YourBattleTag owner`
+ - Creates an account with Login `username@`, password `YourPassword` and BattleTag `YourBattleTag` with rank `owner`
## Prepare Client
diff --git a/configs/config.ini b/configs/config.ini
deleted file mode 100644
index 70c2f39..0000000
--- a/configs/config.ini
+++ /dev/null
@@ -1,114 +0,0 @@
-;
-; # This is a template configuration file which can be modified as desired.
-;
-; # Community branch (recommended): https://github.com/blizzless/blizzless-diiis/tree/community
-; # test-stable branch: https://github.com/blizzless/blizzless-diiis/
-; # Master branch: https://github.com/blizzless/blizzless-diiis/tree/master
-;
-
-; Settings for Bnet
-[Battle-Server]
-Enabled = true
-BindIP = 127.0.0.1
-WebPort = 9800
-Port = 1119
-MotdEnabled = true
-Motd = Welcome to Blizzless D3!
-
-; ------------------------
-; [IWServer]
-; IWServer = false
-
-; ------------------------
-; REST services for login (and others)
-[REST]
-IP = 127.0.0.1
-Public = true
-PublicIP = 127.0.0.1
-PORT = 80
-
-; ------------------------
-; Game server options and game-mods.
-;
-[Game-Server]
-Enabled = true
-CoreActive = true
-BindIP = 127.0.0.1
-WebPort = 9001
-Port = 1345
-BindIPv6 = ::1
-DRLGemu = true
-
-; Modding of game (please check https://github.com/blizzless/blizzless-diiis/blob/community/docs/game-world-settings.md)
-;
-
-; rates
-RateExp = 1
-RateMoney = 1
-RateDrop = 1
-RateChangeDrop = 1
-RateMonsterHP = 1
-RateMonsterDMG = 1
-; items
-ChanceHighQualityUnidentified = 30
-ChanceNormalUnidentified = 5
-; bosses
-BossHealthMultiplier = 6
-BossDamageMultiplier = 3
-; nephalem
-NephalemRiftProgressMultiplier = 1
-; health
-HealthPotionRestorePercentage = 60
-HealthPotionCooldown = 30
-ResurrectionCharges = 3
-; waypoints
-UnlockAllWaypoints = false
-; player attribute modifier
-StrengthMultiplier = 1
-StrengthParagonMultiplier = 1
-DexterityMultiplier = 1
-DexterityParagonMultiplier = 1
-IntelligenceMultiplier = 1
-IntelligenceParagonMultiplier = 1
-VitalityMultiplier = 1
-VitalityParagonMultiplier = 1
-; quests
-AutoSaveQuests = false
-; minimap
-ForceMinimapVisibility = false
-
-; ------------------------
-; Network address translation
-;
-[NAT]
-Enabled = True
-; use your public IP
-PublicIP = 127.0.0.1
-
-; ------------------------
-; Where the outputs should be.
-; Best for visualization (default): AnsiLog (target: Ansi)
-; Best for debugging: ConsoleLog (target: console)
-; Best for packet analysis: PacketLog (target: file)
-;
-[AnsiLog]
-Enabled = true
-Target = Ansi
-IncludeTimeStamps = true
-MinimumLevel = Debug
-MaximumLevel = Fatal
-
-[ConsoleLog]
-Enabled = false
-Target = Console
-IncludeTimeStamps = true
-MinimumLevel = Debug
-MaximumLevel = PacketDump
-
-[PacketLog]
-Enabled = true
-Target = file
-FileName = packet.log
-IncludeTimeStamps = true
-MinimumLevel = Debug
-MaximumLevel = PacketDump
diff --git a/configs/config.mods.json b/configs/config.mods.json
new file mode 100644
index 0000000..5341038
--- /dev/null
+++ b/configs/config.mods.json
@@ -0,0 +1,60 @@
+{
+ "Rate": {
+ "Experience": 1.0,
+ "Money": 1.0,
+ "Drop": 1.0,
+ "ChangeDrop": 1.0
+ },
+ "Health": {
+ "PotionRestorePercentage": 60.0,
+ "PotionCooldown": 30.0,
+ "ResurrectionCharges": 3
+ },
+ "Monster": {
+ "HealthMultiplier": 1.0,
+ "DamageMultiplier": 1.0
+ },
+ "Boss": {
+ "HealthMultiplier": 6.0,
+ "DamageMultiplier": 3.0
+ },
+ "Quest": {
+ "AutoSave": false,
+ "UnlockAllWaypoints": false
+ },
+ "Player": {
+ "Multipliers": {
+ "Strength": {
+ "Normal": 1.0,
+ "Paragon": 1.0
+ },
+ "Dexterity": {
+ "Normal": 1.0,
+ "Paragon": 1.0
+ },
+ "Intelligence": {
+ "Normal": 1.0,
+ "Paragon": 1.0
+ },
+ "Vitality": {
+ "Normal": 1.0,
+ "Paragon": 1.0
+ }
+ }
+ },
+ "Items": {
+ "UnidentifiedDropChances": {
+ "HighQuality": 30.0,
+ "NormalQuality": 5.0
+ }
+ },
+ "Minimap": {
+ "ForceVisibility": false
+ },
+ "NephalemRift": {
+ "ProgressMultiplier": 1.0,
+ "AutoFinish": false,
+ "AutoFinishThreshold": 2,
+ "OrbsChance": 2.0
+ }
+}
\ No newline at end of file
diff --git a/docs/game-world-settings.md b/docs/game-world-settings.md
index 8863def..0c60118 100644
--- a/docs/game-world-settings.md
+++ b/docs/game-world-settings.md
@@ -1,81 +1,76 @@
# Game World Settings
-The parameters of the world can be easily altered using the configuration file located within `config.ini`.
+The parameters of the world can be easily altered using the configuration file located within `config.maps.json`, which is built on server initialization.
+
+For older configs, it will be migrated from `config.ini` automatically.
## Configuration
-The parameters specified in the `config.ini` file will be saved to the server folder, overwriting the default settings. For example, all values below use their default settings.
+The parameters specified in the `config.mods.json` file will be created on the server folder, migrating from config.ini, to overwrite the default settings. For example, all values below use their default settings.
-```ini
-[Game-Server]
-; rates
-RateExp = 1
-RateMoney = 1
-RateDrop = 1
-RateChangeDrop = 1
-RateMonsterHP = 1
-RateMonsterDMG = 1
-; items
-ChanceHighQualityUnidentified = 30
-ChanceNormalUnidentified = 5
-; bosses
-BossHealthMultiplier = 6
-BossDamageMultiplier = 3
-; nephalem
-NephalemRiftProgressMultiplier = 1
-NephalemRiftAutoFinish = false
-NephalemRiftAutoFinishThreshold = 2
-NephalemRiftOrbsChance = 0
-; health
-HealthPotionRestorePercentage = 60
-HealthPotionCooldown = 30
-ResurrectionCharges = 3
-; waypoints
-UnlockAllWaypoints = false
-; player attribute modifier
-StrengthMultiplier = 1
-StrengthParagonMultiplier = 1
-DexterityMultiplier = 1
-DexterityParagonMultiplier = 1
-IntelligenceMultiplier = 1
-IntelligenceParagonMultiplier = 1
-VitalityMultiplier = 1
-VitalityParagonMultiplier = 1
-; quests
-AutoSaveQuests = false
-; minimap
-ForceMinimapVisibility = false
-```
+The default configuration can be found at [config.mods.json](https://github.com/blizzless/blizzless-diiis/blob/community/configs/config.mods.json)
## Description
-| Key | Description |
-| ---------------- | ------------------------- |
-| `RateExp` | Experience multiplier |
-| `RateMoney` | Currency multiplier |
-| `RateDrop` | Drop quantity multiplier |
-| `RateChangeDrop` | Drop quality multiplier |
-| `RateMonsterHP` | Monsters HP multiplier |
-| `RateMonsterDMG` | Monster damage multiplier |
-| `ChanceHighQualityUnidentified` | Percentage that a unique, legendary, set or special item created is unidentified |
-| `ChanceNormalUnidentified` | Percentage that normal item created is unidentified |
-| `ResurrectionCharges` | Amount of times user can resurrect at corpse |
-| `BossHealthMultiplier` | Boss Health Multiplier |
-| `BossDamageMultiplier` | Boss Damage Multiplier |
-| `HealthPotionRestorePercentage` | How much (from 1-100) a health potion will heal. |
-| `HealthPotionCooldown` | How much (in seconds) to use a health potion again. |
-| `UnlockAllWaypoints` | Unlocks all waypoints in campaign |
-| `StrengthMultiplier` | Player's strength multiplier |
-| `StrengthParagonMultiplier` | Player's strength multiplier **for paragons** |
-| `DexterityMultiplier` | Player's dexterity multiplier |
-| `DexterityParagonMultiplier` | Player's dexterity multiplier **for paragons** |
-| `IntelligenceMultiplier` | Player's intelligence multiplier |
-| `IntelligenceParagonMultiplier` | Player's intelligence multiplier **for paragons** |
-| `VitalityMultiplier` | Player's vitality multiplier |
-| `VitalityParagonMultiplier` | Player's vitality multiplier **for paragons** |
-| `AutoSaveQuests` *in tests* | Force Save Quests/Step, even if Act's quest setup marked as Saveable = FALSE. Doesn't apply to OpenWorld games. |
-| `NephalemRiftProgressMultiplier` | Nephalem Rift Progress Modifier |
-| `NephalemRiftAutoFinish` | Nephalem Auto-Finish when there's still `NephalemRiftAutoFinishThreshold` monsters or less are alive on the rift |
-| `NephalemRiftAutoFinishThreshold` | Nephalem Rift Progress Modifier |
-| `NephalemRiftOrbsChance` | Nephalem Rifts chance of spawning a orb. |
-| `ForceMinimapVisibility` | Forces the minimap visibility |
+```json
+{
+ "Rate": {
+ "Experience": 1.0, // Experience Rate
+ "Money": 1.0, // money rate
+ "Drop": 1.0, // drop rate
+ "ChangeDrop": 1.0 // change drop rate
+ },
+ "Health": {
+ "PotionRestorePercentage": 60.0, // how many in percent will a potion restore
+ "PotionCooldown": 30.0, // how many seconds for a full potion recharge
+ "ResurrectionCharges": 3 // how many times can you revive at corpse
+ },
+ "Monster": {
+ "HealthMultiplier": 1.0, // monster health multiplier
+ "DamageMultiplier": 1.0 // monster damage multiplier
+ },
+ "Boss": {
+ "HealthMultiplier": 6.0, // boss health multiplier
+ "DamageMultiplier": 3.0 // boss damage multiplier
+ },
+ "Quest": {
+ "AutoSave": false, // auto save at every quest
+ "UnlockAllWaypoints": false // unlocks all waypoints in-game
+ },
+ "Player": {
+ "Multipliers": { // multipliers for the player (e.g. a paragon might need twice these values for fairer gameplay)
+ "Strength": {
+ "Normal": 1.0,
+ "Paragon": 1.0
+ },
+ "Dexterity": {
+ "Normal": 1.0,
+ "Paragon": 1.0
+ },
+ "Intelligence": {
+ "Normal": 1.0,
+ "Paragon": 1.0
+ },
+ "Vitality": {
+ "Normal": 1.0,
+ "Paragon": 1.0
+ }
+ }
+ },
+ "Items": {
+ "UnidentifiedDropChances": { // chances in % of a dropped item to be unidentified
+ "HighQuality": 30.0,
+ "NormalQuality": 5.0
+ }
+ },
+ "Minimap": {
+ "ForceVisibility": false // forces in-game minimap to be always visible
+ },
+ "NephalemRift": { // improves overall nephalem rift experience
+ "ProgressMultiplier": 1.0,
+ "AutoFinish": false,
+ "AutoFinishThreshold": 2,
+ "OrbsChance": 2.0 // chances of spawning an orb
+ }
+}
+```
diff --git a/src/DiIiS-NA/BGS-Server/Base/ConnectHandler.cs b/src/DiIiS-NA/BGS-Server/Base/ConnectHandler.cs
index 1546a37..df4b1bb 100644
--- a/src/DiIiS-NA/BGS-Server/Base/ConnectHandler.cs
+++ b/src/DiIiS-NA/BGS-Server/Base/ConnectHandler.cs
@@ -250,6 +250,8 @@ namespace DiIiS_NA.LoginServer.Base
}
internal class WebSocketServerProtocolHandshakeHandler : ChannelHandlerAdapter
{
+
+ static readonly Logger _logger = LogManager.CreateLogger();
private readonly string websocketPath;
private readonly string subprotocols;
@@ -290,12 +292,16 @@ namespace DiIiS_NA.LoginServer.Base
{
if (!object.Equals(req.Method, HttpMethod.Get))
{
- SendHttpResponse(ctx, req, new DefaultFullHttpResponse(HttpVersion.Http11, HttpResponseStatus.Forbidden));
+ SendHttpResponse(ctx, req,
+ new DefaultFullHttpResponse(HttpVersion.Http11, HttpResponseStatus.Forbidden));
return;
}
+
//v1.rpc.battle.net
//
- WebSocketServerHandshakerFactory webSocketServerHandshakerFactory = new WebSocketServerHandshakerFactory(GetWebSocketLocation(ctx.Channel.Pipeline, req, websocketPath), subprotocols, allowExtensions, maxFramePayloadSize, allowMaskMismatch);
+ WebSocketServerHandshakerFactory webSocketServerHandshakerFactory =
+ new WebSocketServerHandshakerFactory(GetWebSocketLocation(ctx.Channel.Pipeline, req, websocketPath),
+ subprotocols, allowExtensions, maxFramePayloadSize, allowMaskMismatch);
WebSocketServerHandshaker handshaker = webSocketServerHandshakerFactory.NewHandshaker(req);
if (handshaker == null)
{
@@ -303,7 +309,7 @@ namespace DiIiS_NA.LoginServer.Base
return;
}
- handshaker.HandshakeAsync(ctx.Channel, req).ContinueWith(delegate (Task t)
+ handshaker.HandshakeAsync(ctx.Channel, req).ContinueWith(delegate(Task t)
{
if (t.Status != TaskStatus.RanToCompletion)
{
@@ -311,12 +317,17 @@ namespace DiIiS_NA.LoginServer.Base
}
else
{
- ctx.FireUserEventTriggered(new HandshakeHandler.HandshakeComplete(req.Uri, req.Headers, handshaker.SelectedSubprotocol));
+ ctx.FireUserEventTriggered(new HandshakeHandler.HandshakeComplete(req.Uri, req.Headers,
+ handshaker.SelectedSubprotocol));
}
}, TaskContinuationOptions.ExecuteSynchronously);
HandshakeHandler.SetHandshaker(ctx.Channel, handshaker);
ctx.Channel.Pipeline.Replace(this, "WS403Responder", HandshakeHandler.ForbiddenHttpRequestResponder());
}
+ catch (Exception ex)
+ {
+ _logger.ErrorException(ex, "Handshake failure");
+ }
finally
{
req.Release();
diff --git a/src/DiIiS-NA/BGS-Server/Battle/BattleClient.cs b/src/DiIiS-NA/BGS-Server/Battle/BattleClient.cs
index dfc604d..2610b82 100644
--- a/src/DiIiS-NA/BGS-Server/Battle/BattleClient.cs
+++ b/src/DiIiS-NA/BGS-Server/Battle/BattleClient.cs
@@ -16,6 +16,8 @@ using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
+using System.Net.Http;
+using System.Net.Http.Json;
using System.Net.Security;
using System.Threading.Tasks;
using DiIiS_NA.GameServer.MessageSystem.Message.Definitions.Text;
@@ -501,10 +503,41 @@ namespace DiIiS_NA.LoginServer.Battle
}
public void SendMotd()
{
- if (string.IsNullOrWhiteSpace(LoginServerConfig.Instance.Motd) || !LoginServerConfig.Instance.MotdEnabled)
- return;
- Logger.Debug($"Motd sent to {Account.BattleTag}.");
- SendServerWhisper(LoginServerConfig.Instance.Motd);
+ if (LoginServerConfig.Instance.MotdEnabled)
+ {
+ if (LoginServerConfig.Instance.MotdEnabledRemote)
+ {
+ if (string.IsNullOrWhiteSpace(LoginServerConfig.Instance.MotdRemoteUrl))
+ {
+ Logger.Warn("No Motd remote URL defined, falling back to normal motd.");
+ }
+ else
+ {
+ var url = LoginServerConfig.Instance.MotdRemoteUrl.Trim();
+ HttpClient client = new();
+ var post = client.PostAsJsonAsync(url, new
+ {
+ GameAccountId = InGameClient.Player?.Toon?.GameAccountId ?? 0,
+ ToonName = InGameClient.Player?.Toon?.Name ?? string.Empty,
+ WorldGlobalId = InGameClient.Player?.World?.GlobalID ?? 0
+ }).Result;
+ if (post.IsSuccessStatusCode)
+ {
+ var text = post.Content.ReadAsStringAsync().Result;
+ SendServerWhisper(text);
+ Logger.Info("Remote Motd sent successfully.");
+ return;
+ }
+
+ Logger.Warn("Could not POST to $[red]$" + url + "$[/]$. Please ensure the URL is correct. Falling back to normal MotD if available.");
+ }
+ }
+ if (!string.IsNullOrWhiteSpace(LoginServerConfig.Instance.Motd))
+ {
+ Logger.Debug($"Motd sent to {Account.BattleTag}.");
+ SendServerWhisper(LoginServerConfig.Instance.Motd);
+ }
+ }
}
public override void ChannelInactive(IChannelHandlerContext context)
diff --git a/src/DiIiS-NA/BGS-Server/LoginServerConfig.cs b/src/DiIiS-NA/BGS-Server/LoginServerConfig.cs
index 40649bf..c0b53b8 100644
--- a/src/DiIiS-NA/BGS-Server/LoginServerConfig.cs
+++ b/src/DiIiS-NA/BGS-Server/LoginServerConfig.cs
@@ -47,6 +47,12 @@ namespace DiIiS_NA.LoginServer
get => GetBoolean(nameof(MotdEnabled), true);
set => Set(nameof(MotdEnabled), value);
}
+
+ public bool MotdEnabledWhenWorldLoads
+ {
+ get => GetBoolean(nameof(MotdEnabledWhenWorldLoads), false);
+ set => Set(nameof(MotdEnabledWhenWorldLoads), value);
+ }
///
/// Motd text
@@ -58,6 +64,18 @@ namespace DiIiS_NA.LoginServer
set => Set(nameof(Motd), value);
}
+ public bool MotdEnabledRemote
+ {
+ get => GetBoolean(nameof(MotdEnabledRemote), false);
+ set => Set(nameof(MotdEnabledRemote), value);
+ }
+
+ public string MotdRemoteUrl
+ {
+ get => GetString(nameof(MotdRemoteUrl), "");
+ set => Set(nameof(MotdRemoteUrl), value);
+ }
+
public static readonly LoginServerConfig Instance = new();
private LoginServerConfig() : base("Battle-Server")
diff --git a/src/DiIiS-NA/Core/Extensions/DataConversionExtensions.cs b/src/DiIiS-NA/Core/Extensions/DataConversionExtensions.cs
new file mode 100644
index 0000000..1bf1357
--- /dev/null
+++ b/src/DiIiS-NA/Core/Extensions/DataConversionExtensions.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace DiIiS_NA.Core.Extensions;
+
+public static class MathConversionsOperations
+{
+ public static int Floor(this float value) => (int) Math.Floor(value);
+ public static int Ceiling(this float value) => (int) Math.Ceiling(value);
+ public static int Floor(this double value) => (int) Math.Floor(value);
+ public static int Ceil(this double value) => (int) Math.Floor(value);
+}
\ No newline at end of file
diff --git a/src/DiIiS-NA/Core/Logging/AnsiTarget.cs b/src/DiIiS-NA/Core/Logging/AnsiTarget.cs
index bb93ea8..5a99285 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,124 +123,85 @@ 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.QuestInfo => new Style(Color.Plum2),
+ Logger.Level.QuestStep => new Style(Color.Plum3, decoration: Decoration.Dim),
Logger.Level.PacketDump => new Style(Color.Maroon),//
_ => new Style(Color.White)
};
@@ -242,4 +214,7 @@ public static class AnsiTargetExtensions
{
return text.Replace("$[", "").Replace("]$", "").Replace("[", "[[").Replace("]", "]]");
}
+
+ public static string StyleAnsi(this object obj, string style) =>
+ $"$[{style}]$" + obj.ToString().EscapeMarkup() + "$[/]$";
}
\ No newline at end of file
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..2b6fa32 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
@@ -66,6 +68,15 @@ namespace DiIiS_NA.Core.Logging
/// Fatal messages (usually unrecoverable errors that leads to client or server crashes).
///
Fatal,
+
+ ///
+ /// The messages meant for quest general logging purposes.
+ ///
+ QuestInfo,
+ ///
+ /// The messages meant for quest logging purposes.
+ ///
+ QuestStep,
///
/// Packet messages.
///
@@ -105,14 +116,39 @@ namespace DiIiS_NA.Core.Logging
/// The log message.
public void MethodTrace(string message, [CallerMemberName] string methodName = "", [CallerFilePath] string filePath = "", [CallerLineNumber] int lineNumber = 0)
{
+ var m = $"$[darkolivegreen3_2]${methodName}()$[/]$";
#if DEBUG
var fileName = Path.GetFileName(filePath);
- Log(Level.MethodTrace, $"$[underline white]${fileName}:{lineNumber}$[/]$ $[darkolivegreen3_2]${methodName}()$[/]$: " + message, null);
+ Log(Level.MethodTrace, $"$[red]${fileName}:{lineNumber}$[/]$ in {m}: " + message, null);
#else
- Log(Level.MethodTrace, $"$[darkolivegreen3_2]${methodName}()$[/]$: " + message, null);
+ Log(Level.MethodTrace, $"{m}: " + message, null);
#endif
}
+ public void QuestStep(string message, [CallerMemberName] string methodName = "",
+ [CallerFilePath] string filePath = "", [CallerLineNumber] int lineNumber = 0)
+ {
+ var m = $"$[darkolivegreen3_2]${methodName}()$[/]$";
+#if DEBUG
+ var fileName = Path.GetFileName(filePath);
+ Log(Level.MethodTrace, $"$[red]${fileName}:{lineNumber}$[/]$ in {m}: " + message, null);
+#else
+ Log(Level.MethodTrace, $"{m}: " + message, null);
+#endif
+ }
+
+ public void QuestInfo(string message, [CallerMemberName] string methodName = "",
+ [CallerFilePath] string filePath = "", [CallerLineNumber] int lineNumber = 0)
+ {
+ var m = $"$[darkolivegreen3_2]${methodName}()$[/]$";
+#if DEBUG
+ var fileName = Path.GetFileName(filePath);
+ Log(Level.MethodTrace, $"$[red]${fileName}:{lineNumber}$[/]$ in {m}: " + message, null);
+#else
+ Log(Level.MethodTrace, $"{m}: " + message, null);
+#endif
+ }
+
/// The log message.
public void Debug(string message) => Log(Level.Debug, message, null);
@@ -153,7 +189,7 @@ namespace DiIiS_NA.Core.Logging
/// The log message.
/// Additional arguments.
- public void Fatal(string message, params object[] args) => Log(Level.Fatal, message, args);
+ public void Fatal(string message, params object[] args) => Log(Level.Fatal, message, args);
#endregion
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/CommandManager.cs b/src/DiIiS-NA/D3-GameServer/CommandManager/CommandManager.cs
index 56f648a..7b9c75c 100644
--- a/src/DiIiS-NA/D3-GameServer/CommandManager/CommandManager.cs
+++ b/src/DiIiS-NA/D3-GameServer/CommandManager/CommandManager.cs
@@ -119,7 +119,7 @@ namespace DiIiS_NA.GameServer.CommandManager
output = $"Unknown command.";
#endif
- if (output == string.Empty)
+ if (string.IsNullOrEmpty(output))
return true;
if (output.Contains("\n"))
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..367897e
--- /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/Core/Types/Math/Vector3D.cs b/src/DiIiS-NA/D3-GameServer/Core/Types/Math/Vector3D.cs
index d271cf4..5c68e74 100644
--- a/src/DiIiS-NA/D3-GameServer/Core/Types/Math/Vector3D.cs
+++ b/src/DiIiS-NA/D3-GameServer/Core/Types/Math/Vector3D.cs
@@ -112,6 +112,24 @@ namespace DiIiS_NA.GameServer.Core.Types.Math
return ((x * x) + (y * y)) + (z * z);
}
+ private static Random rand = new Random();
+
+ public Vector3D Around(float radius)
+ {
+ return Around(radius, radius, radius);
+ }
+ public Vector3D Around(float x, float y, float z)
+ {
+ float newX = X + ((float)rand.NextDouble() * 2 * x) - x;
+ float newY = Y + ((float)rand.NextDouble() * 2 * y) - y;
+ float newZ = Z + ((float)rand.NextDouble() * 2 * z) - z;
+ return new Vector3D(newX, newY, newZ);
+ }
+
+ public Vector3D Around(Vector3D vector)
+ {
+ return Around(vector.X, vector.Y, vector.Z);
+ }
public static bool operator ==(Vector3D a, Vector3D b) => a?.Equals(b) ?? ReferenceEquals(null, b);
diff --git a/src/DiIiS-NA/D3-GameServer/GSSystem/AISystem/Brain.cs b/src/DiIiS-NA/D3-GameServer/GSSystem/AISystem/Brain.cs
index a0d46dd..2e546c2 100644
--- a/src/DiIiS-NA/D3-GameServer/GSSystem/AISystem/Brain.cs
+++ b/src/DiIiS-NA/D3-GameServer/GSSystem/AISystem/Brain.cs
@@ -47,7 +47,7 @@ namespace DiIiS_NA.GameServer.GSSystem.AISystem
public virtual void Update(int tickCounter)
{
- if (State == BrainState.Dead || Body == null || Body.World == null || State == BrainState.Off)
+ if (State == BrainState.Dead || Body?.World == null || State == BrainState.Off)
return;
Think(tickCounter); // let the brain think.
diff --git a/src/DiIiS-NA/D3-GameServer/GSSystem/AISystem/Brains/MonsterBrain.cs b/src/DiIiS-NA/D3-GameServer/GSSystem/AISystem/Brains/MonsterBrain.cs
index 6ea3910..345137b 100644
--- a/src/DiIiS-NA/D3-GameServer/GSSystem/AISystem/Brains/MonsterBrain.cs
+++ b/src/DiIiS-NA/D3-GameServer/GSSystem/AISystem/Brains/MonsterBrain.cs
@@ -21,456 +21,532 @@ using DiIiS_NA.GameServer.MessageSystem;
namespace DiIiS_NA.GameServer.GSSystem.AISystem.Brains
{
- public class MonsterBrain : Brain
- {
- private new readonly Logger Logger;
- // list of power SNOs that are defined for the monster
- public Dictionary PresetPowers { get; private set; }
+ public class MonsterBrain : Brain
+ {
+ private new readonly Logger _logger;
- private TickTimer _powerDelay;
+ // list of power SNOs that are defined for the monster
+ public Dictionary PresetPowers { get; private set; }
- public struct Cooldown
- {
- public TickTimer CooldownTimer;
- public float CooldownTime;
- }
+ private TickTimer _powerDelay;
- private bool _warnedNoPowers;
- private Actor _target { get; set; }
- private int _mpqPowerCount;
- private bool Feared = false;
+ public struct Cooldown
+ {
+ public TickTimer CooldownTimer;
+ public float CooldownTime;
+ }
- public Actor AttackedBy = null;
- public TickTimer TimeoutAttacked = null;
+ private bool _warnedNoPowers;
+ private Actor Target { get; set; }
+ private int _mpqPowerCount;
+ private bool _feared = false;
- public Actor PriorityTarget = null;
+ public Actor AttackedBy = null;
+ public TickTimer TimeoutAttacked = null;
- public MonsterBrain(Actor body)
- : base(body)
- {
- Logger = LogManager.CreateLogger(GetType().Name);
-
- PresetPowers = new Dictionary();
+ public Actor PriorityTarget = null;
- // build list of powers defined in monster mpq data
- if (body.ActorData.MonsterSNO <= 0)
- {
- Logger.Warn($"$[red]${GetType().Name}$[/]$ - Monster \"{body.SNO}\" has no monster SNO");
- return;
- }
- var monsterData = (DiIiS_NA.Core.MPQ.FileFormats.Monster)MPQStorage.Data.Assets[SNOGroup.Monster][body.ActorData.MonsterSNO].Data;
- _mpqPowerCount = monsterData.SkillDeclarations.Count(e => e.SNOPower != -1);
- for (int i = 0; i < monsterData.SkillDeclarations.Length; i++)
- {
- if (monsterData.SkillDeclarations[i].SNOPower == -1) continue;
- if (PowerLoader.HasImplementationForPowerSNO(monsterData.SkillDeclarations[i].SNOPower))
- {
- var cooldownTime = monsterData.MonsterSkillDeclarations[i].Timer / 10f;
- PresetPowers.Add(monsterData.SkillDeclarations[i].SNOPower, new Cooldown { CooldownTimer = null, CooldownTime = cooldownTime });
- }
- }
+ public MonsterBrain(Actor body)
+ : base(body)
+ {
+ _logger = LogManager.CreateLogger(GetType().Name);
- if (monsterData.SkillDeclarations.All(s => s.SNOPower != 30592))
- PresetPowers.Add(30592, new Cooldown { CooldownTimer = null, CooldownTime = 0f }); //hack for dummy mobs without powers
- }
+ PresetPowers = new Dictionary();
- public override void Think(int tickCounter)
- {
- switch (Body.SNO)
- {
- case ActorSno._uber_siegebreakerdemon:
- case ActorSno._a4dun_garden_corruption_monster:
- case ActorSno._a4dun_garden_hellportal_pillar:
- case ActorSno._belialvoiceover:
- return;
- }
+ // build list of powers defined in monster mpq data
+ if (body.ActorData.MonsterSNO <= 0)
+ {
+ _logger.Warn($"$[red]${GetType().Name}$[/]$ - Monster \"{body.SNO}\" has no monster SNO");
+ return;
+ }
- if (Body.Hidden)
- return;
+ var monsterData =
+ (DiIiS_NA.Core.MPQ.FileFormats.Monster)MPQStorage.Data.Assets[SNOGroup.Monster][
+ body.ActorData.MonsterSNO].Data;
+ _mpqPowerCount = monsterData.SkillDeclarations.Count(e => e.SNOPower != -1);
+ for (int i = 0; i < monsterData.SkillDeclarations.Length; i++)
+ {
+ if (monsterData.SkillDeclarations[i].SNOPower == -1) continue;
+ if (!PowerLoader.HasImplementationForPowerSNO(monsterData.SkillDeclarations[i].SNOPower)) continue;
+ var cooldownTime = monsterData.MonsterSkillDeclarations[i].Timer / 10f;
+ PresetPowers.Add(monsterData.SkillDeclarations[i].SNOPower,
+ new Cooldown { CooldownTimer = null, CooldownTime = cooldownTime });
+ }
- if (CurrentAction != null && PriorityTarget != null && PriorityTarget.Attributes[GameAttributes.Is_Helper] == true)
- {
- PriorityTarget = null;
- CurrentAction.Cancel(tickCounter);
- CurrentAction = null;
- return;
- }
+ if (monsterData.SkillDeclarations.All(s => s.SNOPower != 30592))
+ PresetPowers.Add(30592,
+ new Cooldown { CooldownTimer = null, CooldownTime = 0f }); //hack for dummy mobs without powers
+ }
- if (tickCounter % 60 != 0) return;
-
- if (Body is NPC) return;
+ public override void Think(int tickCounter)
+ {
+ switch (Body.SNO)
+ {
+ case ActorSno._uber_siegebreakerdemon:
+ case ActorSno._a4dun_garden_corruption_monster:
+ case ActorSno._a4dun_garden_hellportal_pillar:
+ case ActorSno._belialvoiceover:
+ return;
+ }
- if (!Body.Visible || Body.Dead) return;
+ if (Body.Hidden)
+ return;
- if (Body.World.Game.Paused) return;
- if (Body.Attributes[GameAttributes.Disabled]) return;
+ if (CurrentAction != null && PriorityTarget != null &&
+ PriorityTarget.Attributes[GameAttributes.Is_Helper] == true)
+ {
+ PriorityTarget = null;
+ CurrentAction.Cancel(tickCounter);
+ CurrentAction = null;
+ return;
+ }
- if (Body.Attributes[GameAttributes.Frozen] ||
- Body.Attributes[GameAttributes.Stunned] ||
- Body.Attributes[GameAttributes.Blind] ||
- Body.Attributes[GameAttributes.Webbed] ||
- Body.Disable ||
- Body.World.BuffManager.GetFirstBuff(Body) != null ||
- Body.World.BuffManager.GetFirstBuff(Body) != null)
- {
- if (CurrentAction != null)
- {
- CurrentAction.Cancel(tickCounter);
- CurrentAction = null;
- }
- _powerDelay = null;
+ if (tickCounter % 60 != 0) return;
- return;
- }
+ if (Body is NPC) return;
- if (Body.Attributes[GameAttributes.Feared])
- {
- if (!Feared || CurrentAction == null)
- {
- if (CurrentAction != null)
- {
- CurrentAction.Cancel(tickCounter);
- CurrentAction = null;
- }
- Feared = true;
- CurrentAction = new MoveToPointWithPathfindAction(
- Body,
- PowerContext.RandomDirection(Body.Position, 3f, 8f)
- );
- return;
- }
+ if (!Body.Visible || Body.Dead) return;
- return;
- }
+ if (Body.World.Game.Paused) return;
+ if (Body.Attributes[GameAttributes.Disabled]) return;
- Feared = false;
+ if (Body.Attributes[GameAttributes.Frozen] ||
+ Body.Attributes[GameAttributes.Stunned] ||
+ Body.Attributes[GameAttributes.Blind] ||
+ Body.Attributes[GameAttributes.Webbed] ||
+ Body.Disable ||
+ Body.World.BuffManager.GetFirstBuff(Body) != null ||
+ Body.World.BuffManager.GetFirstBuff(Body) != null)
+ {
+ if (CurrentAction != null)
+ {
+ CurrentAction.Cancel(tickCounter);
+ CurrentAction = null;
+ }
- if (CurrentAction == null)
- {
- _powerDelay ??= new SecondsTickTimer(Body.World.Game, 1.0f);
- if (AttackedBy != null || Body.GetObjectsInRange(50f).Count != 0)
- {
- if (_powerDelay.TimedOut)
- {
- _powerDelay = new SecondsTickTimer(Body.World.Game, 1.0f);
+ _powerDelay = null;
- if (AttackedBy != null)
- PriorityTarget = AttackedBy;
+ return;
+ }
- if (PriorityTarget == null)
- {
- Actor[] targets;
+ if (Body.Attributes[GameAttributes.Feared])
+ {
+ if (_feared && CurrentAction != null) return;
+ if (CurrentAction != null)
+ {
+ CurrentAction.Cancel(tickCounter);
+ CurrentAction = null;
+ }
- if (Body.Attributes[GameAttributes.Team_Override] == 1)
- targets = Body.GetObjectsInRange(60f)
- .Where(p => !p.Dead)
- .OrderBy((monster) => PowerMath.Distance2D(monster.Position, Body.Position))
- .ToArray();
- else
- targets = Body.GetActorsInRange(50f)
- .Where(p => ((p is Player) && !p.Dead && p.Attributes[GameAttributes.Loading] == false && p.Attributes[GameAttributes.Is_Helper] == false && p.World.BuffManager.GetFirstBuff(p) == null)
- || ((p is Minion) && !p.Dead && p.Attributes[GameAttributes.Is_Helper] == false)
- || (p is DesctructibleLootContainer && p.SNO.IsDoorOrBarricade())
- || ((p is Hireling) && !p.Dead)
- )
- .OrderBy((actor) => PowerMath.Distance2D(actor.Position, Body.Position))
- .ToArray();
+ _feared = true;
+ CurrentAction = new MoveToPointWithPathfindAction(
+ Body,
+ PowerContext.RandomDirection(Body.Position, 3f, 8f)
+ );
+ return;
+ }
- if (targets.Length == 0) return;
-
- _target = targets.First();
- }
- else
- _target = PriorityTarget;
+ _feared = false;
- int powerToUse = PickPowerToUse();
- if (powerToUse > 0)
- {
- PowerScript power = PowerLoader.CreateImplementationForPowerSNO(powerToUse);
- power.User = Body;
- float attackRange = Body.ActorData.Cylinder.Ax2 + (power.EvalTag(PowerKeys.AttackRadius) > 0f ? (powerToUse == 30592 ? 10f : Math.Min((float)power.EvalTag(PowerKeys.AttackRadius), 35f)) : 35f);
- float targetDistance = PowerMath.Distance2D(_target.Position, Body.Position);
- if (targetDistance < attackRange + _target.ActorData.Cylinder.Ax2)
- {
- if (Body.WalkSpeed != 0)
- Body.TranslateFacing(_target.Position, false);
+ if (CurrentAction != null) return;
+ _powerDelay ??= new SecondsTickTimer(Body.World.Game, 1.0f);
+ // Check if the character has been attacked or if there are any players within 50 units range
+ if (AttackedBy != null || Body.GetObjectsInRange(50f).Count != 0)
+ {
+ // If the power delay hasn't timed out, return
+ if (!_powerDelay.TimedOut) return;
- CurrentAction = new PowerAction(Body, powerToUse, _target);
+ // Reset the power delay
+ _powerDelay = new SecondsTickTimer(Body.World.Game, 1.0f);
- if (power is SummoningSkill)
- PresetPowers[powerToUse] = new Cooldown { CooldownTimer = null, CooldownTime = (Body is Boss ? 15f : 7f) };
+ // If the character has been attacked, set the attacker as the priority target
+ if (AttackedBy != null)
+ PriorityTarget = AttackedBy;
- if (power is MonsterAffixSkill monsterAffixSkill)
- PresetPowers[powerToUse] = new Cooldown { CooldownTimer = null, CooldownTime = monsterAffixSkill.CooldownTime };
+ // If there's no defined priority target, start a search
+ if (PriorityTarget == null)
+ {
+ Actor[] targets;
- if (PresetPowers[powerToUse].CooldownTime > 0f)
- PresetPowers[powerToUse] = new Cooldown { CooldownTimer = new SecondsTickTimer(Body.World.Game, PresetPowers[powerToUse].CooldownTime), CooldownTime = PresetPowers[powerToUse].CooldownTime };
+ // If the character is part of a team, search for alive monsters within a range of 60 units and order them by distance
+ if (Body.Attributes[GameAttributes.Team_Override] == 1)
+ targets = Body.GetObjectsInRange(60f)
+ .Where(p => !p.Dead)
+ .OrderBy((monster) => PowerMath.Distance2D(monster.Position, Body.Position))
+ .ToArray();
+ else
+ // Otherwise, search for different types of actors including players, minions, destructible loot containers, or hirelings that are alive, not loading and not helpers, and order them by distance
+ targets = Body.GetActorsInRange(50f)
+ .Where(p => ((p is Player) && !p.Dead && p.Attributes[GameAttributes.Loading] == false &&
+ p.Attributes[GameAttributes.Is_Helper] == false &&
+ p.World.BuffManager.GetFirstBuff(p) == null)
+ || ((p is Minion) && !p.Dead && p.Attributes[GameAttributes.Is_Helper] == false)
+ || (p is DesctructibleLootContainer && p.SNO.IsDoorOrBarricade())
+ || ((p is Hireling) && !p.Dead)
+ )
+ .OrderBy((actor) => PowerMath.Distance2D(actor.Position, Body.Position))
+ .ToArray();
- if (powerToUse is 96925 or 223284)
- PresetPowers[powerToUse] = new Cooldown { CooldownTimer = new SecondsTickTimer(Body.World.Game, 10f), CooldownTime = 10f };
- }
- else if (Body.WalkSpeed != 0)
- {
- if (Body.SNO.IsWoodwraithOrWasp())
- {
- Logger.Trace($"{GetType().Name} $[underline white]${nameof(MoveToPointAction)}$[/]$ to target $[white]${_target.ActorType}$[/]$ [{_target.Position}]");
- CurrentAction = new MoveToPointAction(
- Body, _target.Position
- );
- }
- else
- {
- Logger.Trace($"{GetType().Name} {nameof(MoveToTargetWithPathfindAction)} to target [{_target.ActorType}] {_target.SNO.ToString()}");
- CurrentAction = new MoveToTargetWithPathfindAction(
- Body,
- _target,
- attackRange + _target.ActorData.Cylinder.Ax2,
- powerToUse
- );
- }
- }
- else
- {
- powerToUse = Body.SNO switch
- {
- ActorSno._a1dun_leor_firewall2 => 223284,
- _ => powerToUse
- };
- CurrentAction = new PowerAction(Body, powerToUse, _target);
+ // If there are no targets, return
+ if (targets.Length == 0) return;
- if (power is SummoningSkill)
- PresetPowers[powerToUse] = new Cooldown { CooldownTimer = null, CooldownTime = (Body is Boss ? 15f : 7f) };
+ // Set the first found target as the target
+ Target = targets.First();
+ }
+ else
+ // If there is a priority target, set it as the target
+ Target = PriorityTarget;
- if (power is MonsterAffixSkill)
- PresetPowers[powerToUse] = new Cooldown { CooldownTimer = null, CooldownTime = (power as MonsterAffixSkill).CooldownTime };
+ int powerToUse = PickPowerToUse();
+ if (powerToUse <= 0) return;
+ PowerScript power = PowerLoader.CreateImplementationForPowerSNO(powerToUse);
+ power.User = Body;
+ float attackRange = Body.ActorData.Cylinder.Ax2 + (power.EvalTag(PowerKeys.AttackRadius) > 0f
+ ? (powerToUse == 30592 ? 10f : Math.Min((float)power.EvalTag(PowerKeys.AttackRadius), 35f))
+ : 35f);
+ float targetDistance = PowerMath.Distance2D(Target.Position, Body.Position);
+ if (targetDistance < attackRange + Target.ActorData.Cylinder.Ax2)
+ {
+ if (Body.WalkSpeed != 0)
+ Body.TranslateFacing(Target.Position, false);
- if (PresetPowers[powerToUse].CooldownTime > 0f)
- PresetPowers[powerToUse] = new Cooldown { CooldownTimer = new SecondsTickTimer(Body.World.Game, PresetPowers[powerToUse].CooldownTime), CooldownTime = PresetPowers[powerToUse].CooldownTime };
+ CurrentAction = new PowerAction(Body, powerToUse, Target);
- if (powerToUse == 96925 ||
- powerToUse == 223284)
- PresetPowers[powerToUse] = new Cooldown { CooldownTimer = new SecondsTickTimer(Body.World.Game, 10f), CooldownTime = 10f };
- }
- }
- }
- }
+ PresetPowers[powerToUse] = power switch
+ {
+ SummoningSkill => new Cooldown
+ {
+ CooldownTimer = null, CooldownTime = (Body is Boss ? 15f : 7f)
+ },
+ MonsterAffixSkill monsterAffixSkill => new Cooldown
+ {
+ CooldownTimer = null, CooldownTime = monsterAffixSkill.CooldownTime
+ },
+ _ => PresetPowers[powerToUse]
+ };
- else if (Body.GetObjectsInRange(50f).Count != 0)
- {
- if (_powerDelay.TimedOut)
- {
- _powerDelay = new SecondsTickTimer(Body.World.Game, 1.0f);
+ if (PresetPowers[powerToUse].CooldownTime > 0f)
+ PresetPowers[powerToUse] = new Cooldown
+ {
+ CooldownTimer =
+ new SecondsTickTimer(Body.World.Game, PresetPowers[powerToUse].CooldownTime),
+ CooldownTime = PresetPowers[powerToUse].CooldownTime
+ };
- if (AttackedBy != null)
- PriorityTarget = AttackedBy;
+ if (powerToUse is 96925 or 223284)
+ PresetPowers[powerToUse] = new Cooldown
+ { CooldownTimer = new SecondsTickTimer(Body.World.Game, 10f), CooldownTime = 10f };
+ }
+ else if (Body.WalkSpeed != 0)
+ {
+ if (Body.SNO.IsWoodwraithOrWasp())
+ {
+ _logger.Trace(
+ $"{GetType().Name} $[underline white]${nameof(MoveToPointAction)}$[/]$ to target $[white]${Target.ActorType}$[/]$ [{Target.Position}]");
+ CurrentAction = new MoveToPointAction(
+ Body, Target.Position
+ );
+ }
+ else
+ {
+ _logger.Trace(
+ $"{GetType().Name} {nameof(MoveToTargetWithPathfindAction)} to target [{Target.ActorType}] {Target.SNO.ToString()}");
+ CurrentAction = new MoveToTargetWithPathfindAction(
+ Body,
+ Target,
+ attackRange + Target.ActorData.Cylinder.Ax2,
+ powerToUse
+ );
+ }
+ }
+ else
+ {
+ powerToUse = Body.SNO switch
+ {
+ ActorSno._a1dun_leor_firewall2 => 223284,
+ _ => powerToUse
+ };
+ CurrentAction = new PowerAction(Body, powerToUse, Target);
- if (PriorityTarget == null)
- {
- var targets = Body.GetActorsInRange(50f)
- .Where(p => ((p is LorathNahr_NPC) && !p.Dead)
- || ((p is CaptainRumford) && !p.Dead)
- || (p is DesctructibleLootContainer && p.SNO.IsDoorOrBarricade())
- || ((p is Cain) && !p.Dead))
- .OrderBy((actor) => PowerMath.Distance2D(actor.Position, Body.Position))
- .ToArray();
+ PresetPowers[powerToUse] = power switch
+ {
+ SummoningSkill => new Cooldown
+ {
+ CooldownTimer = null, CooldownTime = (Body is Boss ? 15f : 7f)
+ },
+ MonsterAffixSkill skill => new Cooldown
+ {
+ CooldownTimer = null, CooldownTime = skill.CooldownTime
+ },
+ _ => PresetPowers[powerToUse]
+ };
- if (targets.Length == 0)
- {
- targets = Body.GetActorsInRange(20f)
- .Where(p => ((p is Monster) && !p.Dead)
- || ((p is CaptainRumford) && !p.Dead)
- )
- .OrderBy((actor) => PowerMath.Distance2D(actor.Position, Body.Position))
- .ToArray();
+ if (PresetPowers[powerToUse].CooldownTime > 0f)
+ PresetPowers[powerToUse] = new Cooldown
+ {
+ CooldownTimer =
+ new SecondsTickTimer(Body.World.Game, PresetPowers[powerToUse].CooldownTime),
+ CooldownTime = PresetPowers[powerToUse].CooldownTime
+ };
+
+ if (powerToUse is 96925 or 223284)
+ PresetPowers[powerToUse] = new Cooldown
+ { CooldownTimer = new SecondsTickTimer(Body.World.Game, 10f), CooldownTime = 10f };
+ }
+ }
+
+ else if (Body.GetObjectsInRange(50f).Count != 0)
+ {
+ if (!_powerDelay.TimedOut) return;
+ _powerDelay = new SecondsTickTimer(Body.World.Game, 1.0f);
+
+ if (AttackedBy != null)
+ PriorityTarget = AttackedBy;
+
+ if (PriorityTarget == null)
+ {
+ var targets = Body.GetActorsInRange(50f)
+ .Where(p => ((p is LorathNahr_NPC) && !p.Dead)
+ || ((p is CaptainRumford) && !p.Dead)
+ || (p is DesctructibleLootContainer && p.SNO.IsDoorOrBarricade())
+ || ((p is Cain) && !p.Dead))
+ .OrderBy((actor) => PowerMath.Distance2D(actor.Position, Body.Position))
+ .ToArray();
+
+ if (targets.Length == 0)
+ {
+ targets = Body.GetActorsInRange(20f)
+ .Where(p => ((p is Monster) && !p.Dead)
+ || ((p is CaptainRumford) && !p.Dead)
+ )
+ .OrderBy((actor) => PowerMath.Distance2D(actor.Position, Body.Position))
+ .ToArray();
- if (targets.Length == 0)
- return;
+ if (targets.Length == 0)
+ return;
- foreach (var monsterActor in targets.Where(tar => _target == null))
- if (monsterActor is Monster { Brain: MonsterBrain brain } monster && monsterActor != Body)
- if (brain.AttackedBy != null)
- _target = brain.AttackedBy;
- }
- else
- {
- _target = targets.First();
- }
- foreach (var tar in targets)
- if (tar is DesctructibleLootContainer && tar.SNO.IsDoorOrBarricade() && tar.SNO != ActorSno._trout_wagon_barricade)
- { _target = tar; break; }
- }
- else
- _target = PriorityTarget;
+ foreach (var monsterActor in targets.Where(tar => Target == null))
+ if (monsterActor is Monster { Brain: MonsterBrain brain } monster && monsterActor != Body)
+ if (brain.AttackedBy != null)
+ Target = brain.AttackedBy;
+ }
+ else
+ {
+ Target = targets.First();
+ }
- int powerToUse = PickPowerToUse();
- if (powerToUse > 0)
- {
- PowerScript power = PowerLoader.CreateImplementationForPowerSNO(powerToUse);
- power.User = Body;
- if (_target == null)
- {
- /*
- if (!this.Body.ActorSNO.Name.ToLower().Contains("woodwraith") &&
- !this.Body.ActorSNO.Name.ToLower().Contains("wasp"))
- if (this.Body.Quality < 2)
- {
- this.CurrentAction = new MoveToPointWithPathfindAction(this.Body, RandomPosibleDirection(this.Body.CheckPointPosition, 3f, 8f, this.Body.World));
- return;
- }
- else
- //*/
- return;
- }
- float attackRange = Body.ActorData.Cylinder.Ax2 + (power.EvalTag(PowerKeys.AttackRadius) > 0f ? (powerToUse == 30592 ? 10f : Math.Min((float)power.EvalTag(PowerKeys.AttackRadius), 35f)) : 35f);
- float targetDistance = PowerMath.Distance2D(_target.Position, Body.Position);
- if (targetDistance < attackRange + _target.ActorData.Cylinder.Ax2)
- {
- if (Body.WalkSpeed != 0)
- Body.TranslateFacing(_target.Position, false); //columns and other non-walkable shit can't turn
+ foreach (var tar in targets)
+ if (tar is DesctructibleLootContainer && tar.SNO.IsDoorOrBarricade() &&
+ tar.SNO != ActorSno._trout_wagon_barricade)
+ {
+ Target = tar;
+ break;
+ }
+ }
+ else
+ Target = PriorityTarget;
-
- Logger.Trace($"{GetType().Name} {nameof(PowerAction)} to target [{_target.ActorType}] {_target.SNO.ToString()}");
- // Logger.Trace("PowerAction to target");
- CurrentAction = new PowerAction(Body, powerToUse, _target);
+ int powerToUse = PickPowerToUse();
+ if (powerToUse > 0)
+ {
+ PowerScript power = PowerLoader.CreateImplementationForPowerSNO(powerToUse);
+ power.User = Body;
+ if (Target == null)
+ {
+ /*
+ if (!this.Body.ActorSNO.Name.ToLower().Contains("woodwraith") &&
+ !this.Body.ActorSNO.Name.ToLower().Contains("wasp"))
+ if (this.Body.Quality < 2)
+ {
+ this.CurrentAction = new MoveToPointWithPathfindAction(this.Body, RandomPosibleDirection(this.Body.CheckPointPosition, 3f, 8f, this.Body.World));
+ return;
+ }
+ else
+ //*/
+ return;
+ }
- if (power is SummoningSkill)
- PresetPowers[powerToUse] = new Cooldown { CooldownTimer = null, CooldownTime = (Body is Boss ? 15f : 7f) };
+ float attackRange = Body.ActorData.Cylinder.Ax2 + (power.EvalTag(PowerKeys.AttackRadius) > 0f
+ ? (powerToUse == 30592 ? 10f : Math.Min((float)power.EvalTag(PowerKeys.AttackRadius), 35f))
+ : 35f);
+ float targetDistance = PowerMath.Distance2D(Target.Position, Body.Position);
+ if (targetDistance < attackRange + Target.ActorData.Cylinder.Ax2)
+ {
+ if (Body.WalkSpeed != 0)
+ Body.TranslateFacing(Target.Position,
+ false); //columns and other non-walkable shit can't turn
- if (power is MonsterAffixSkill monsterSkill)
- PresetPowers[powerToUse] = new Cooldown { CooldownTimer = null, CooldownTime = monsterSkill.CooldownTime };
- if (PresetPowers[powerToUse].CooldownTime > 0f)
- PresetPowers[powerToUse] = new Cooldown { CooldownTimer = new SecondsTickTimer(Body.World.Game, PresetPowers[powerToUse].CooldownTime), CooldownTime = PresetPowers[powerToUse].CooldownTime };
- }
- else if (Body.WalkSpeed != 0)
- {
- if (Body.SNO.IsWoodwraithOrWasp())
- {
- Logger.Trace($"{GetType().Name} {nameof(MoveToPointAction)} to target [{_target.Position}]");
+ _logger.Trace(
+ $"{GetType().Name} {nameof(PowerAction)} to target [{Target.ActorType}] {Target.SNO.ToString()}");
+ // Logger.Trace("PowerAction to target");
+ CurrentAction = new PowerAction(Body, powerToUse, Target);
- CurrentAction = new MoveToPointAction(
- Body, _target.Position
- );
- }
- else
- {
- Logger.Trace($"{GetType().Name} {nameof(MoveToTargetWithPathfindAction)} to target [{_target.ActorType}] {_target.SNO.ToString()}");
+ PresetPowers[powerToUse] = power switch
+ {
+ SummoningSkill => new Cooldown
+ {
+ CooldownTimer = null, CooldownTime = (Body is Boss ? 15f : 7f)
+ },
+ MonsterAffixSkill monsterSkill => new Cooldown
+ {
+ CooldownTimer = null, CooldownTime = monsterSkill.CooldownTime
+ },
+ _ => PresetPowers[powerToUse]
+ };
- CurrentAction = new MoveToTargetWithPathfindAction(
- Body,
- //(
- _target,// + MovementHelpers.GetMovementPosition(
- //new Vector3D(0, 0, 0),
- //this.Body.WalkSpeed,
- //MovementHelpers.GetFacingAngle(_target.Position, this.Body.Position),
- //6
- //)
- //)
- attackRange + _target.ActorData.Cylinder.Ax2,
- powerToUse
- );
- }
- }
- }
- }
- }
+ if (PresetPowers[powerToUse].CooldownTime > 0f)
+ PresetPowers[powerToUse] = new Cooldown
+ {
+ CooldownTimer = new SecondsTickTimer(Body.World.Game,
+ PresetPowers[powerToUse].CooldownTime),
+ CooldownTime = PresetPowers[powerToUse].CooldownTime
+ };
+ }
+ else if (Body.WalkSpeed != 0)
+ {
+ if (Body.SNO.IsWoodwraithOrWasp())
+ {
+ _logger.Trace(
+ $"{GetType().Name} {nameof(MoveToPointAction)} to target [{Target.Position}]");
- else
- {
- //Logger.Trace("No enemies in range, return to master");
- if (Body.Position != Body.CheckPointPosition)
- CurrentAction = new MoveToPointWithPathfindAction(Body, Body.CheckPointPosition);
- }
- }
- }
- public static Core.Types.Math.Vector3D RandomPossibleDirection(Core.Types.Math.Vector3D position, float minRadius, float maxRadius, MapSystem.World world)
- {
- float angle = (float)(FastRandom.Instance.NextDouble() * Math.PI * 2);
- float radius = minRadius + (float)FastRandom.Instance.NextDouble() * (maxRadius - minRadius);
- Core.Types.Math.Vector3D point = null;
- int tryC = 0;
- while (tryC < 100)
- {
- //break;
- point = new Core.Types.Math.Vector3D(position.X + (float)Math.Cos(angle) * radius,
- position.Y + (float)Math.Sin(angle) * radius,
- position.Z);
- if (world.CheckLocationForFlag(point, DiIiS_NA.Core.MPQ.FileFormats.Scene.NavCellFlags.AllowWalk))
- break;
- tryC++;
- }
- return point;
- }
+ CurrentAction = new MoveToPointAction(
+ Body, Target.Position
+ );
+ }
+ else
+ {
+ _logger.Trace(
+ $"{GetType().Name} {nameof(MoveToTargetWithPathfindAction)} to target [{Target.ActorType}] {Target.SNO.ToString()}");
- public void FastAttack(Actor target, int skillSNO)
- {
- PowerScript power = PowerLoader.CreateImplementationForPowerSNO(skillSNO);
- power.User = Body;
- if (Body.WalkSpeed != 0)
- Body.TranslateFacing(target.Position, false); //columns and other non-walkable shit can't turn
- Logger.Trace($"{GetType().Name} {nameof(FastAttack)} {nameof(PowerAction)} to target [{_target.ActorType}] {_target.SNO.ToString()}");
- CurrentAction = new PowerAction(Body, skillSNO, target);
+ CurrentAction = new MoveToTargetWithPathfindAction(
+ Body,
+ //(
+ Target, // + MovementHelpers.GetMovementPosition(
+ //new Vector3D(0, 0, 0),
+ //this.Body.WalkSpeed,
+ //MovementHelpers.GetFacingAngle(_target.Position, this.Body.Position),
+ //6
+ //)
+ //)
+ attackRange + Target.ActorData.Cylinder.Ax2,
+ powerToUse
+ );
+ }
+ }
+ }
+ }
- if (power is SummoningSkill)
- PresetPowers[skillSNO] = new Cooldown { CooldownTimer = null, CooldownTime = (Body is Boss ? 15f : 7f) };
+ else
+ {
+ //Logger.Trace("No enemies in range, return to master");
+ if (Body.Position != Body.CheckPointPosition)
+ CurrentAction = new MoveToPointWithPathfindAction(Body, Body.CheckPointPosition);
+ }
+ }
- if (power is MonsterAffixSkill monsterAffixSkill)
- PresetPowers[skillSNO] = new Cooldown { CooldownTimer = null, CooldownTime = monsterAffixSkill.CooldownTime };
+ public static Core.Types.Math.Vector3D RandomPossibleDirection(Core.Types.Math.Vector3D position,
+ float minRadius, float maxRadius, MapSystem.World world)
+ {
+ float angle = (float)(FastRandom.Instance.NextDouble() * Math.PI * 2);
+ float radius = minRadius + (float)FastRandom.Instance.NextDouble() * (maxRadius - minRadius);
+ Core.Types.Math.Vector3D point = null;
+ int tryC = 0;
+ while (tryC < 100)
+ {
+ //break;
+ point = new Core.Types.Math.Vector3D(position.X + (float)Math.Cos(angle) * radius,
+ position.Y + (float)Math.Sin(angle) * radius,
+ position.Z);
+ if (world.CheckLocationForFlag(point, DiIiS_NA.Core.MPQ.FileFormats.Scene.NavCellFlags.AllowWalk))
+ break;
+ tryC++;
+ }
- if (PresetPowers[skillSNO].CooldownTime > 0f)
- PresetPowers[skillSNO] = new Cooldown { CooldownTimer = new SecondsTickTimer(Body.World.Game, PresetPowers[skillSNO].CooldownTime), CooldownTime = PresetPowers[skillSNO].CooldownTime };
- }
+ return point;
+ }
- protected virtual int PickPowerToUse()
- {
- if (!_warnedNoPowers && PresetPowers.Count == 0)
- {
- Logger.Warn($"Monster $[red]$\"{Body.Name}\"$[/]$ has no usable powers. {_mpqPowerCount} are defined in mpq data.");
- _warnedNoPowers = true;
- return -1;
- }
+ public void FastAttack(Actor target, int skillSno)
+ {
+ PowerScript power = PowerLoader.CreateImplementationForPowerSNO(skillSno);
+ power.User = Body;
+ if (Body.WalkSpeed != 0)
+ Body.TranslateFacing(target.Position, false); //columns and other non-walkable shit can't turn
+ _logger.Trace(
+ $"{GetType().Name} {nameof(FastAttack)} {nameof(PowerAction)} to target [{Target.ActorType}] {Target.SNO.ToString()}");
+ CurrentAction = new PowerAction(Body, skillSno, target);
- // randomly used an implemented power
- if (PresetPowers.Count <= 0) return -1;
-
- //int power = this.PresetPowers[RandomHelper.Next(this.PresetPowers.Count)].Key;
- var availablePowers = PresetPowers.Where(p => (p.Value.CooldownTimer == null || p.Value.CooldownTimer.TimedOut) && PowerLoader.HasImplementationForPowerSNO(p.Key)).Select(p => p.Key).ToList();
- if (availablePowers.Where(p => p != 30592).TryPickRandom(out var selectedPower))
- {
- return selectedPower;
- }
+ PresetPowers[skillSno] = power switch
+ {
+ SummoningSkill => new Cooldown { CooldownTimer = null, CooldownTime = (Body is Boss ? 15f : 7f) },
+ MonsterAffixSkill monsterAffixSkill => new Cooldown
+ {
+ CooldownTimer = null, CooldownTime = monsterAffixSkill.CooldownTime
+ },
+ _ => PresetPowers[skillSno]
+ };
- if (availablePowers.Contains(30592))
- return 30592; // melee attack
+ if (PresetPowers[skillSno].CooldownTime > 0f)
+ PresetPowers[skillSno] = new Cooldown
+ {
+ CooldownTimer = new SecondsTickTimer(Body.World.Game, PresetPowers[skillSno].CooldownTime),
+ CooldownTime = PresetPowers[skillSno].CooldownTime
+ };
+ }
- // no usable power
- return -1;
- }
+ protected virtual int PickPowerToUse()
+ {
+ if (!_warnedNoPowers && PresetPowers.Count == 0)
+ {
+ _logger.Warn(
+ $"Monster $[red]$\"{Body.Name}\"$[/]$ has no usable powers. {_mpqPowerCount} are defined in mpq data.");
+ _warnedNoPowers = true;
+ return -1;
+ }
- public void AddPresetPower(int powerSNO)
- {
- if (PresetPowers.ContainsKey(powerSNO))
- {
- Logger.Debug($"Monster $[red]$\"{Body.Name}\"$[/]$ already has power {powerSNO}.");
- // Logger.MethodTrace("power sno {0} already defined for monster \"{1}\"",
- //powerSNO, this.Body.ActorSNO.Name);
- return;
- }
+ // randomly used an implemented power
+ if (PresetPowers.Count <= 0) return -1;
- PresetPowers.Add(powerSNO,
- PresetPowers.ContainsKey(30592) //if can cast melee
- ? new Cooldown { CooldownTimer = null, CooldownTime = 5f }
- : new Cooldown
- { CooldownTimer = null, CooldownTime = 1f + (float)FastRandom.Instance.NextDouble() });
- }
+ //int power = this.PresetPowers[RandomHelper.Next(this.PresetPowers.Count)].Key;
+ var availablePowers = PresetPowers
+ .Where(p => (p.Value.CooldownTimer == null || p.Value.CooldownTimer.TimedOut) &&
+ PowerLoader.HasImplementationForPowerSNO(p.Key)).Select(p => p.Key).ToList();
+ if (availablePowers.Where(p => p != 30592).TryPickRandom(out var selectedPower))
+ {
+ return selectedPower;
+ }
- public void RemovePresetPower(int powerSNO)
- {
- if (PresetPowers.ContainsKey(powerSNO))
- {
- PresetPowers.Remove(powerSNO);
- }
- }
- }
-}
+ if (availablePowers.Contains(30592))
+ return 30592; // melee attack
+
+ // no usable power
+ return -1;
+ }
+
+ public void AddPresetPower(int powerSno)
+ {
+ if (PresetPowers.ContainsKey(powerSno))
+ {
+ _logger.Debug($"Monster $[red]$\"{Body.Name}\"$[/]$ already has power {powerSno}.");
+ // Logger.MethodTrace("power sno {0} already defined for monster \"{1}\"",
+ //powerSNO, this.Body.ActorSNO.Name);
+ return;
+ }
+
+ PresetPowers.Add(powerSno,
+ PresetPowers.ContainsKey(30592) //if can cast melee
+ ? new Cooldown { CooldownTimer = null, CooldownTime = 5f }
+ : new Cooldown
+ { CooldownTimer = null, CooldownTime = 1f + (float)FastRandom.Instance.NextDouble() });
+ }
+
+ public void RemovePresetPower(int powerSno)
+ {
+ if (PresetPowers.ContainsKey(powerSno))
+ {
+ PresetPowers.Remove(powerSno);
+ }
+ }
+ }
+}
\ No newline at end of file
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/Implementations/Monsters/Bosses.cs b/src/DiIiS-NA/D3-GameServer/GSSystem/ActorSystem/Implementations/Monsters/Bosses.cs
index 465ef36..1ba12a8 100644
--- a/src/DiIiS-NA/D3-GameServer/GSSystem/ActorSystem/Implementations/Monsters/Bosses.cs
+++ b/src/DiIiS-NA/D3-GameServer/GSSystem/ActorSystem/Implementations/Monsters/Bosses.cs
@@ -2,6 +2,7 @@
using DiIiS_NA.GameServer.Core.Types.TagMap;
using DiIiS_NA.GameServer.GSSystem.MapSystem;
using DiIiS_NA.GameServer.MessageSystem;
+using Microsoft.Extensions.Logging;
namespace DiIiS_NA.GameServer.GSSystem.ActorSystem.Implementations.Monsters
{
@@ -27,13 +28,9 @@ namespace DiIiS_NA.GameServer.GSSystem.ActorSystem.Implementations.Monsters
public override int Quality
{
- get
- {
- return (int)DiIiS_NA.Core.MPQ.FileFormats.SpawnType.Boss;
- }
+ get => (int)DiIiS_NA.Core.MPQ.FileFormats.SpawnType.Boss;
set
{
-
}
}
diff --git a/src/DiIiS-NA/D3-GameServer/GSSystem/ActorSystem/Monster.cs b/src/DiIiS-NA/D3-GameServer/GSSystem/ActorSystem/Monster.cs
index 6a1d9ad..a1f5336 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;
@@ -22,7 +23,7 @@ namespace DiIiS_NA.GameServer.GSSystem.ActorSystem
{
public class Monster : Living, IUpdateable
{
- private static readonly Logger Logger = LogManager.CreateLogger(nameof(Monster));
+ private static readonly Logger Logger = LogManager.CreateLogger();
public override ActorType ActorType => ActorType.Monster;
public TickTimer DestroyTimer { get; }
@@ -70,7 +71,7 @@ namespace DiIiS_NA.GameServer.GSSystem.ActorSystem
//WalkSpeed /= 2f;
Brain = new MonsterBrain(this);
- Attributes[GameAttributes.Attacks_Per_Second] = 1.2f;
+ Attributes[GameAttributes.Attacks_Per_Second] = GameModsConfig.Instance.Monster.AttacksPerSecond;// 1.2f;
UpdateStats();
}
@@ -80,7 +81,7 @@ namespace DiIiS_NA.GameServer.GSSystem.ActorSystem
#if DEBUG
string monster = "monster";
if (this is Boss) monster = "boss";
- Logger.MethodTrace($"Player {player.Name} targeted {monster} {GetType().Name}.");
+ Logger.MethodTrace($"Player {player.Name} targeted $[underline]${monster}$[/]$ {GetType().Name}.");
#endif
}
@@ -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;
}
@@ -187,14 +188,12 @@ namespace DiIiS_NA.GameServer.GSSystem.ActorSystem
lock (_adjustLock)
{
int count = player.World.Game.Players.Count;
- if (count > 0 && _adjustedPlayers != count)
- {
- Attributes[GameAttributes.Damage_Weapon_Min, 0] = _nativeDmg * (1f + (0.05f * (count - 1) * player.World.Game.Difficulty));
- Attributes[GameAttributes.Hitpoints_Max] = _nativeHp * (1f + ((0.75f + (0.1f * player.World.Game.Difficulty)) * (count - 1)));
- Attributes[GameAttributes.Hitpoints_Cur] = Attributes[GameAttributes.Hitpoints_Max_Total];
- Attributes.BroadcastChangedIfRevealed();
- _adjustedPlayers = count;
- }
+ if (count <= 0 || _adjustedPlayers == count) return true;
+ Attributes[GameAttributes.Damage_Weapon_Min, 0] = _nativeDmg * (1f + (0.05f * (count - 1) * player.World.Game.Difficulty));
+ Attributes[GameAttributes.Hitpoints_Max] = _nativeHp * (1f + ((0.75f + (0.1f * player.World.Game.Difficulty)) * (count - 1)));
+ Attributes[GameAttributes.Hitpoints_Cur] = Attributes[GameAttributes.Hitpoints_Max_Total];
+ Attributes.BroadcastChangedIfRevealed();
+ _adjustedPlayers = count;
}
return true;
diff --git a/src/DiIiS-NA/D3-GameServer/GSSystem/GameSystem/ActEnum.cs b/src/DiIiS-NA/D3-GameServer/GSSystem/GameSystem/ActEnum.cs
new file mode 100644
index 0000000..adc99ec
--- /dev/null
+++ b/src/DiIiS-NA/D3-GameServer/GSSystem/GameSystem/ActEnum.cs
@@ -0,0 +1,11 @@
+namespace DiIiS_NA.GameServer.GSSystem.GameSystem;
+
+public enum ActEnum
+{
+ Act1 = 0,
+ Act2 = 100,
+ Act3 = 200,
+ Act4 = 300,
+ Act5 = 400,
+ OpenWorld = 3000
+}
\ No newline at end of file
diff --git a/src/DiIiS-NA/D3-GameServer/GSSystem/GameSystem/Game.cs b/src/DiIiS-NA/D3-GameServer/GSSystem/GameSystem/Game.cs
index f952a83..675beda 100644
--- a/src/DiIiS-NA/D3-GameServer/GSSystem/GameSystem/Game.cs
+++ b/src/DiIiS-NA/D3-GameServer/GSSystem/GameSystem/Game.cs
@@ -33,6 +33,7 @@ using DiIiS_NA.GameServer.GSSystem.AISystem.Brains;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using DiIiS_NA.Core.MPQ.FileFormats;
+using DiIiS_NA.D3_GameServer;
using DiIiS_NA.D3_GameServer.Core.Types.SNO;
using DiIiS_NA.D3_GameServer.GSSystem.GameSystem;
using Actor = DiIiS_NA.GameServer.GSSystem.ActorSystem.Actor;
@@ -42,16 +43,6 @@ using World = DiIiS_NA.GameServer.GSSystem.MapSystem.World;
namespace DiIiS_NA.GameServer.GSSystem.GameSystem
{
- public enum ActEnum
- {
- Act1 = 0,
- Act2 = 100,
- Act3 = 200,
- Act4 = 300,
- Act5 = 400,
- OpenWorld = 3000
- }
-
public class Game : IMessageConsumer
{
private static readonly Logger Logger = LogManager.CreateLogger();
@@ -66,6 +57,8 @@ namespace DiIiS_NA.GameServer.GSSystem.GameSystem
///
public ConcurrentDictionary Players { get; private set; }
+ public Player FirstPlayer() => Players.Values.First();
+
public ImmutableArray ConnectedPlayers => Players
.Where(s => s.Value != null && s.Key.Connection.IsOpen() && !s.Key.IsLoggingOut)
.Select(s => s.Value).ToImmutableArray();
@@ -190,9 +183,10 @@ namespace DiIiS_NA.GameServer.GSSystem.GameSystem
/// Current quest SNOid.
///
public int CurrentQuest = -1;
-
public int CurrentSideQuest = -1;
+ public bool IsCurrentOpenWorld => CurrentQuest == 312429;
+
///
/// Current quest step SNOid.
///
@@ -1283,11 +1277,23 @@ namespace DiIiS_NA.GameServer.GSSystem.GameSystem
if (diff > 0)
{
var handicapLevels = (GameBalance)MPQStorage.Data.Assets[SNOGroup.GameBalance][256027].Data;
- HpModifier = handicapLevels.HandicapLevelTables[diff].HPMod;
- DmgModifier = handicapLevels.HandicapLevelTables[diff].DmgMod;
- XpModifier = (1f + handicapLevels.HandicapLevelTables[diff].XPMod);
- GoldModifier = (1f + handicapLevels.HandicapLevelTables[diff].GoldMod);
+ HpModifier = handicapLevels.HandicapLevelTables[diff].HPMod * GameModsConfig.Instance.Rate.HealthByDifficulty[Difficulty]
+ * GameModsConfig.Instance.Monster.HealthMultiplier;
+ DmgModifier = handicapLevels.HandicapLevelTables[diff].DmgMod
+ * GameModsConfig.Instance.Rate.GetDamageByDifficulty(diff)
+ * GameModsConfig.Instance.Monster.DamageMultiplier;
+ XpModifier = (1f + handicapLevels.HandicapLevelTables[diff].XPMod) * GameModsConfig.Instance.Rate.Experience;
+ GoldModifier = (1f + handicapLevels.HandicapLevelTables[diff].GoldMod * GameModsConfig.Instance.Rate.Gold);
}
+ else
+ {
+ HpModifier = GameModsConfig.Instance.Rate.HealthByDifficulty[diff] * GameModsConfig.Instance.Monster.HealthMultiplier;
+ DmgModifier = GameModsConfig.Instance.Rate.GetDamageByDifficulty(diff) * GameModsConfig.Instance.Monster.DamageMultiplier;
+ XpModifier = 1f + GameModsConfig.Instance.Rate.Experience;
+ GoldModifier = (1f * GameModsConfig.Instance.Rate.Gold);
+ }
+
+ Logger.Info($"$[italic]$Updated Game #$[underline]${GameId}$[/]$ difficulty to {diff}.$[/]$");
foreach (var wld in _worlds)
foreach (var monster in wld.Value.Monsters)
@@ -1570,9 +1576,8 @@ namespace DiIiS_NA.GameServer.GSSystem.GameSystem
//handling quest triggers
- if (QuestProgress.QuestTriggers.ContainsKey(levelArea)) //EnterLevelArea
+ if (QuestProgress.QuestTriggers.TryGetValue(levelArea, out var trigger)) //EnterLevelArea
{
- var trigger = QuestProgress.QuestTriggers[levelArea];
if (trigger.TriggerType == QuestStepObjectiveType.EnterLevelArea)
{
try
@@ -1645,7 +1650,7 @@ namespace DiIiS_NA.GameServer.GSSystem.GameSystem
//Берем каина
var firstPoint = new Vector3D(120.92718f, 121.26151f, 0.099973306f);
var secondPoint = new Vector3D(120.73298f, 160.61829f, 0.31863004f);
- var sceletonPoint = new Vector3D(120.11514f, 140.77332f, 0.31863004f);
+ var sketonPosition = new Vector3D(120.11514f, 140.77332f, 0.31863004f);
var firstfacingAngle =
ActorSystem.Movement.MovementHelpers.GetFacingAngle(cainRun, firstPoint);
@@ -1666,9 +1671,9 @@ namespace DiIiS_NA.GameServer.GSSystem.GameSystem
{
foreach (var skeleton in skeletons)
{
- skeleton.Move(sceletonPoint,
+ skeleton.Move(sketonPosition,
ActorSystem.Movement.MovementHelpers.GetFacingAngle(skeleton,
- sceletonPoint));
+ sketonPosition));
}
cainRun.Move(secondPoint, secondfacingAngle);
diff --git a/src/DiIiS-NA/D3-GameServer/GSSystem/GameSystem/QuestManager.cs b/src/DiIiS-NA/D3-GameServer/GSSystem/GameSystem/QuestManager.cs
index ea1ac37..66b34fa 100644
--- a/src/DiIiS-NA/D3-GameServer/GSSystem/GameSystem/QuestManager.cs
+++ b/src/DiIiS-NA/D3-GameServer/GSSystem/GameSystem/QuestManager.cs
@@ -21,6 +21,7 @@ using DiIiS_NA.GameServer.MessageSystem;
using DiIiS_NA.GameServer.MessageSystem.Message.Definitions.Map;
using DiIiS_NA.GameServer.MessageSystem.Message.Definitions.Quest;
using DiIiS_NA.GameServer.MessageSystem.Message.Fields;
+using Spectre.Console;
using Monster = DiIiS_NA.GameServer.GSSystem.ActorSystem.Monster;
namespace DiIiS_NA.D3_GameServer.GSSystem.GameSystem
@@ -29,11 +30,6 @@ namespace DiIiS_NA.D3_GameServer.GSSystem.GameSystem
{
private static readonly Logger Logger = new(nameof(QuestManager));
- ///
- /// Accessor for quests
- ///
- /// snoId of the quest to retrieve
- ///
public readonly Dictionary Quests = new();
public readonly Dictionary SideQuests = new();
@@ -164,12 +160,30 @@ namespace DiIiS_NA.D3_GameServer.GSSystem.GameSystem
Bounties.AddRange(actToKillUniqueBounties[BountyData.ActT.A5].Take(4));
}
+ private readonly struct Rewards
+ {
+ public int Experience { get; }
+ public int Gold { get; }
+
+ public Rewards(int experience, int gold)
+ {
+ Experience = experience;
+ Gold = gold;
+ }
+
+ public Rewards(float experience, float gold) : this((int) Math.Floor(experience), (int) Math.Floor(gold)) {}
+ }
+
+ private Rewards GetCurrentQuestRewards() =>
+ new Rewards(Quests[Game.CurrentQuest].RewardXp, Quests[Game.CurrentQuest].RewardGold);
///
/// Advances a quest by a step
///
/// snoID of the quest to advance
public void Advance()
{
+ int oldQuest = Game.CurrentQuest;
+ int oldStep = Game.CurrentStep;
Quests[Game.CurrentQuest].Steps[Game.CurrentStep].Completed = true;
Game.CurrentStep = Quests[Game.CurrentQuest].Steps[Game.CurrentStep].NextStep;
Game.QuestProgress.QuestTriggers.Clear();
@@ -185,6 +199,13 @@ namespace DiIiS_NA.D3_GameServer.GSSystem.GameSystem
if (Quests[Game.CurrentQuest].Steps[Game.CurrentStep].NextStep != -1)
{
+ Logger.QuestInfo(
+ $"{Emoji.Known.RightArrow} Step Advance ".StyleAnsi("deeppink4") +
+ $"Game #{Game.GameId.StyleAnsi("underline")} " +
+ $"from quest {oldQuest}/" +
+ $"step {oldStep.StyleAnsi("deeppink4")}" +
+ $"to quest {Game.CurrentQuest}'s " +
+ $"step {Game.CurrentStep.StyleAnsi("deeppink4")}");
}
else
{
@@ -192,23 +213,25 @@ namespace DiIiS_NA.D3_GameServer.GSSystem.GameSystem
if (!Game.Empty)
{
SaveQuestProgress(true);
- Logger.Trace(
- $"$[white]$(Advance)$[/]$ Game {Game.GameId} Advanced to quest $[underline white]${Game.CurrentQuest}$[/]$, completed $[underline white]${Quests[Game.CurrentQuest].Completed}$[/]$");
+ Logger.QuestInfo(
+ $"{Emoji.Known.NextTrackButton} Quest Advance ".StyleAnsi("white") +
+ $"Game #{Game.GameId.StyleAnsi("underline")} " +
+ $"from quest {oldQuest.StyleAnsi("turquoise2")}/" +
+ $"step {oldStep.StyleAnsi("deeppink4")}" +
+ $"to quest {Game.CurrentQuest.StyleAnsi("turquoise2")}/" +
+ $"step {Game.CurrentStep.StyleAnsi("deeppink4")}");
Game.BroadcastPlayers((client, player) =>
{
- if (Game.CurrentQuest == 312429) return; // open world quest
+ if (Game.IsCurrentOpenWorld) return; // open world quest
- int xpReward = (int)(Quests[Game.CurrentQuest].RewardXp *
- Game.XpModifier);
- int goldReward = (int)(Quests[Game.CurrentQuest].RewardGold *
- Game.GoldModifier);
+ var rewards = GetCurrentQuestRewards();
player.InGameClient.SendMessage(new QuestStepCompleteMessage()
{
QuestStepComplete = QuestStepComplete.CreateBuilder()
.SetReward(QuestReward.CreateBuilder()
- .SetGoldGranted(goldReward)
- .SetXpGranted((ulong)xpReward)
+ .SetGoldGranted(rewards.Gold)
+ .SetXpGranted((ulong)rewards.Experience)
.SetSnoQuest(Game.CurrentQuest)
)
.SetIsQuestComplete(true)
@@ -224,7 +247,7 @@ namespace DiIiS_NA.D3_GameServer.GSSystem.GameSystem
WorldID = player.World.DynamicID(player),
},
- Amount = xpReward,
+ Amount = rewards.Experience,
Type = GameServer.MessageSystem.Message.Definitions.Base
.FloatingAmountMessage.FloatType.Experience,
});
@@ -238,13 +261,13 @@ namespace DiIiS_NA.D3_GameServer.GSSystem.GameSystem
WorldID = player.World.DynamicID(player),
},
- Amount = goldReward,
+ Amount = rewards.Gold,
Type = GameServer.MessageSystem.Message.Definitions.Base
.FloatingAmountMessage.FloatType.Gold,
});
- player.UpdateExp(xpReward);
- player.Inventory.AddGoldAmount(goldReward);
- player.AddAchievementCounter(74987243307173, (uint)goldReward);
+ player.UpdateExp(rewards.Experience);
+ player.Inventory.AddGoldAmount(rewards.Gold);
+ player.AddAchievementCounter(74987243307173, (uint)rewards.Gold);
player.CheckQuestCriteria(Game.CurrentQuest);
});
}
@@ -270,7 +293,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..aa01ea6 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.Gold);
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 e213ba8..238ed58 100644
--- a/src/DiIiS-NA/D3-GameServer/GSSystem/PlayerSystem/Player.cs
+++ b/src/DiIiS-NA/D3-GameServer/GSSystem/PlayerSystem/Player.cs
@@ -56,10 +56,12 @@ 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;
using DiIiS_NA.D3_GameServer.GSSystem.PlayerSystem;
+using DiIiS_NA.LoginServer;
using NHibernate.Util;
namespace DiIiS_NA.GameServer.GSSystem.PlayerSystem;
@@ -2480,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)
@@ -2674,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]}");
@@ -3607,7 +3625,7 @@ public class Player : Actor, IMessageConsumer, IUpdateable
if (!_motdSent && LoginServer.LoginServerConfig.Instance.MotdEnabled)
{
- if (GameServerConfig.Instance.MotdWhenWorldLoads)
+ if (!LoginServerConfig.Instance.MotdEnabledWhenWorldLoads)
_motdSent = true;
InGameClient.BnetClient.SendMotd();
}
@@ -3994,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);
@@ -4010,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;
@@ -4021,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);
@@ -4030,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;
@@ -4089,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,
@@ -5498,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/Implementations/HeroSkills/Crusader.cs b/src/DiIiS-NA/D3-GameServer/GSSystem/PowerSystem/Implementations/HeroSkills/Crusader.cs
index 5de07be..6f00fd8 100644
--- a/src/DiIiS-NA/D3-GameServer/GSSystem/PowerSystem/Implementations/HeroSkills/Crusader.cs
+++ b/src/DiIiS-NA/D3-GameServer/GSSystem/PowerSystem/Implementations/HeroSkills/Crusader.cs
@@ -148,7 +148,7 @@ namespace DiIiS_NA.GameServer.GSSystem.PowerSystem.Implementations
if (Rune_B > 0) DmgType = DamageType.Lightning; //Electrify
AttackPayload attack = new AttackPayload(this);
attack.Targets = GetEnemiesInArcDirection(User.Position, TargetPosition, 12f, Rune_D > 0 ? 120f : 90f); //Carve
- if (Rune_C > 0) attack.chcBonus = ScriptFormula(14); //Crush
+ if (Rune_C > 0) attack.ChcBonus = ScriptFormula(14); //Crush
attack.AddWeaponDamage(ScriptFormula(0), DmgType);
attack.OnHit = hitPayload =>
{
@@ -3150,7 +3150,7 @@ namespace DiIiS_NA.GameServer.GSSystem.PowerSystem.Implementations
attack.Targets = GetEnemiesInRadius(point, 12f);
attack.AddWeaponDamage(ScriptFormula(3), DamageType.Physical);
if (Rune_B > 0) //Annihilate
- attack.chcBonus = 1f; //will be capped to 85% anyway
+ attack.ChcBonus = 1f; //will be capped to 85% anyway
attack.OnHit = (hitPayload) =>
{
if (Rune_A > 0) //Barrels of tar
diff --git a/src/DiIiS-NA/D3-GameServer/GSSystem/PowerSystem/Implementations/HeroSkills/Necromancer.cs b/src/DiIiS-NA/D3-GameServer/GSSystem/PowerSystem/Implementations/HeroSkills/Necromancer.cs
index 7628543..94630fa 100644
--- a/src/DiIiS-NA/D3-GameServer/GSSystem/PowerSystem/Implementations/HeroSkills/Necromancer.cs
+++ b/src/DiIiS-NA/D3-GameServer/GSSystem/PowerSystem/Implementations/HeroSkills/Necromancer.cs
@@ -1264,7 +1264,7 @@ namespace DiIiS_NA.GameServer.GSSystem.PowerSystem.Implementations
}
}
#endregion
- //Done
+ //Done - testing, apparently Rune_A not working.
#region CorpseExlosion
[ImplementsPowerSNO(SkillsSystem.Skills.Necromancer.ExtraSkills.CorpseExlosion)]
@@ -1272,72 +1272,86 @@ namespace DiIiS_NA.GameServer.GSSystem.PowerSystem.Implementations
{
public override IEnumerable Main()
{
- //ScriptFormulaDetails_Fields
- //PowerDefinition_Fields
- //Мертвячинка) - if (player.SkillSet.HasPassive(208594)) 454066
- if (Rune_B > 0)
- ((Player) User).AddPercentageHP(-2);
- float Radius = 20f;
- float Damage = 10.5f;
- DamageType DType = DamageType.Physical;
- var PowerData = (DiIiS_NA.Core.MPQ.FileFormats.Power)MPQStorage.Data.Assets[SNOGroup.Power][PowerSNO].Data;
- var Point = SpawnEffect(ActorSno._p6_necro_bonespikes, TargetPosition, 0, WaitSeconds(0.2f));
- Point.PlayEffect(Effect.PlayEffectGroup, RuneSelect(459954, 473926, 459954, 473907, 459954//D
- , 473864));
+ // Initializing main variables for Bonespikes ability.
+ float radius = 20f;
+ float damage = 10.5f;
+ DamageType damageType = DamageType.Physical;
+
+ // Fetching the data for the respective Power from the MPQ cache.
+ var powerData = (DiIiS_NA.Core.MPQ.FileFormats.Power)MPQStorage.Data.Assets[SNOGroup.Power][PowerSNO].Data;
+
+ // Creating a point effect on the target position, playing various effect groups depending on the selected Rune.
+ var point = SpawnEffect(ActorSno._p6_necro_bonespikes, TargetPosition, 0, WaitSeconds(0.2f));
+ point.PlayEffect(Effect.PlayEffectGroup, RuneSelect(459954, 473926, 459954, 473907, 459954, 473864));
+
+ // Depending on a specific game attribute, either spawn a new monster at the target position, or select up to five existing corpses.
var actors = User.Attributes[GameAttributes.Necromancer_Corpse_Free_Casting]
? new List { User.World.SpawnMonster(ActorSno._p6_necro_corpse_flesh, TargetPosition).GlobalID }
- : User.GetActorsInRange(TargetPosition, 11).Where(x => x.SNO == ActorSno._p6_necro_corpse_flesh).Select(x => x.GlobalID).Take(5).ToList();
- if (Rune_D > 0)
- Radius = 25f;
- else if (Rune_C > 0)//licking action
- { Damage = 15.75f; DType = DamageType.Poison; }
- else if (Rune_A > 0)
- DType = DamageType.Poison;
+ : User.GetActorsInRange(TargetPosition, 11).Where(x => x.SNO == ActorSno._p6_necro_corpse_flesh)
+ .Select(x => x.GlobalID).Take(5).ToList();
+ // Modifying main parameters of the ability depending on the selected Rune.
+ if (Rune_D > 0)
+ {
+ radius = 25f;
+ }
+ else if (Rune_C > 0) // Licking action.
+ {
+ damage = 15.75f;
+ damageType = DamageType.Poison;
+ }
+ else if (Rune_A > 0)
+ {
+ damageType = DamageType.Poison;
+ }
+
+ // Applying the effects of the Bonespikes ability on the selected corpses.
foreach (var actor in actors)
{
-
if (Rune_B > 0)
{
var bomb = World.GetActorByGlobalId(actor);
var nearestEnemy = bomb.GetActorsInRange(20f).First();
if (nearestEnemy != null)
bomb.Teleport(nearestEnemy.Position);
-
}
-
- var Explosion = SpawnEffect(
+
+ // Spawning explosion effect.
+ var explosionEffect = SpawnEffect(
ActorSno._p6_necro_corpseexplosion_projectile_spawn,
World.GetActorByGlobalId(actor).Position,
ActorSystem.Movement.MovementHelpers.GetFacingAngle(User, World.GetActorByGlobalId(actor)),
WaitSeconds(0.2f)
);
- Explosion.PlayEffect(Effect.PlayEffectGroup, RuneSelect(457183, 471539, 471258, 471249, 471247, 471236));
+ explosionEffect.PlayEffect(Effect.PlayEffectGroup,
+ RuneSelect(457183, 471539, 471258, 471249, 471247, 471236));
+ explosionEffect.UpdateDelay = 0.1f;
- Explosion.UpdateDelay = 0.1f;
- Explosion.OnUpdate = () =>
+ explosionEffect.OnUpdate = () =>
{
- AttackPayload attack = new AttackPayload(this)
+ // Creating the attack payload.
+ AttackPayload attack = new(this)
{
- Targets = GetEnemiesInRadius(User.Position, Radius)
+ Targets = GetEnemiesInRadius(User.Position, radius)
};
if (Rune_E > 0)
- DType = DamageType.Cold;
-
- attack.AddWeaponDamage(Damage, DType);
+ damageType = DamageType.Cold;
+
+ // Applying weapon damage.
+ attack.AddWeaponDamage(damage, damageType);
attack.OnHit = hitPayload =>
{
if (Rune_E > 0)
AddBuff(hitPayload.Target, new DebuffFrozen(WaitSeconds(2f)));
};
+ // Applying the attack.
attack.Apply();
};
+ // Destroying the selected corpse.
World.GetActorByGlobalId(actor).Destroy();
}
-
- //});
yield break;
}
}
diff --git a/src/DiIiS-NA/D3-GameServer/GSSystem/PowerSystem/Payloads/AttackPayload.cs b/src/DiIiS-NA/D3-GameServer/GSSystem/PowerSystem/Payloads/AttackPayload.cs
index d4a1ced..12dc3a5 100644
--- a/src/DiIiS-NA/D3-GameServer/GSSystem/PowerSystem/Payloads/AttackPayload.cs
+++ b/src/DiIiS-NA/D3-GameServer/GSSystem/PowerSystem/Payloads/AttackPayload.cs
@@ -16,7 +16,7 @@ namespace DiIiS_NA.GameServer.GSSystem.PowerSystem.Payloads
// list of targets to try and hit with this payload, must be set before calling Apply()
public TargetList Targets;
- public float chcBonus = 0f;
+ public float ChcBonus = 0f;
// list of each amount and type of damage the attack will contain
public class DamageEntry
@@ -115,7 +115,7 @@ namespace DiIiS_NA.GameServer.GSSystem.PowerSystem.Payloads
if (target == null || target.World == null || target.World != null && target.World.PowerManager.IsDeletingActor(target))
continue;
- var payload = new HitPayload(this, _DoCriticalHit(Context.User, target, chcBonus)
+ var payload = new HitPayload(this, _DoCriticalHit(Context.User, target, ChcBonus)
, target);
payload.AutomaticHitEffects = AutomaticHitEffects;
payload.OnDeath = OnDeath;
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/GSSystem/QuestSystem/ActI.cs b/src/DiIiS-NA/D3-GameServer/GSSystem/QuestSystem/ActI.cs
index 8b55eca..109c8c4 100644
--- a/src/DiIiS-NA/D3-GameServer/GSSystem/QuestSystem/ActI.cs
+++ b/src/DiIiS-NA/D3-GameServer/GSSystem/QuestSystem/ActI.cs
@@ -244,16 +244,23 @@ namespace DiIiS_NA.GameServer.GSSystem.QuestSystem
//AddFollower(this.Game.GetWorld(71150), 4580);
Game.AddOnLoadWorldAction(WorldSno.trout_town, () =>
{
- // TODO: CHeck for possible removing outer adding
- Game.AddOnLoadWorldAction(WorldSno.trout_town, () =>
+ if (Game.CurrentQuest == 72095 && Game.CurrentStep is -1 or 7)
{
- if (Game.CurrentQuest == 72095)
- if (Game.CurrentStep == -1 || Game.CurrentStep == 7)
- {
- AddFollower(Game.GetWorld(WorldSno.trout_town), ActorSno._leah);
- }
- });
-
+ // var world = Game.GetWorld(WorldSno.trout_town);
+ // Logger.QuestStep("Adding leah follower");
+ // // teleport leah
+ // var actor = world.GetActorBySNO(ActorSno._leah);
+ // if (actor != null)
+ // {
+ // actor.Teleport(Game.FirstPlayer().Position.Around(2f));
+ // }
+ AddUniqueFollower(Game.GetWorld(WorldSno.trout_town), ActorSno._leah);
+ }
+ else
+ {
+ Logger.QuestStep($"Can't add leah follower: {Game.CurrentQuest} / {Game.CurrentStep}");
+ }
+
});
}
});
@@ -265,6 +272,7 @@ namespace DiIiS_NA.GameServer.GSSystem.QuestSystem
NextStep = 49,
OnAdvance = () =>
{ //go to gates
+ AddUniqueFollower(Game.GetWorld(WorldSno.trout_town), ActorSno._leah);
var world = Game.GetWorld(WorldSno.trout_town);
StartConversation(world, 166678);
ListenProximity(ActorSno._trout_oldtristram_exit_gate, new Advance());
@@ -409,12 +417,16 @@ namespace DiIiS_NA.GameServer.GSSystem.QuestSystem
Saveable = true,
NextStep = 23,
OnAdvance = () =>
- { //go to church
+ {
+ //go to church
var world = Game.GetWorld(WorldSno.trout_town);
ListenProximity(ActorSno._trdun_cath_cathedraldoorexterior, new Advance());
var leah = world.GetActorBySNO(ActorSno._leah);
if (leah != null)
+ {
leah.Hidden = false;
+ leah.SetVisible(true);
+ }
SetActorVisible(world, ActorSno._tristram_mayor, false);
var cart = world.GetActorBySNO(ActorSno._trout_newtristram_blocking_cart, true);
if (cart != null)
@@ -482,6 +494,7 @@ namespace DiIiS_NA.GameServer.GSSystem.QuestSystem
OnAdvance = () =>
{ //go with Cain
Game.CurrentEncounter.Activated = false;
+
StartConversation(Game.GetWorld(WorldSno.trdun_cain_intro), 72496);
ListenTeleport(19938, new Advance());
}
@@ -504,19 +517,17 @@ namespace DiIiS_NA.GameServer.GSSystem.QuestSystem
StartConversation(tristramWorld, 72498);
});
//StartConversation(this.Game.GetWorld(71150), 72496);
- var leah = tristramWorld.GetActorBySNO(ActorSno._leah, true);
- if (leah == null)
+ DestroyFollower(ActorSno._leah);
+
+ var leah = tristramWorld.GetActorsBySNO(ActorSno._leah);
+ if (!leah.Any())
{
- leah = tristramWorld.GetActorBySNO(ActorSno._leah, false);
- if (leah != null)
- {
- leah.Hidden = false;
- leah.SetVisible(true);
- }
- else
- {
- Logger.Warn($"Leah not found in world {tristramWorld.SNO.ToString()} - quest 72095/step 32");
- }
+ Logger.Warn("Leah not found in world.");
+ }
+ foreach (var l in leah)
+ {
+ l.Hidden = false;
+ l.SetVisible(true);
}
ListenConversation(198617, new Advance());
}
diff --git a/src/DiIiS-NA/D3-GameServer/GSSystem/QuestSystem/QuestProgress.cs b/src/DiIiS-NA/D3-GameServer/GSSystem/QuestSystem/QuestProgress.cs
index 30aaa07..157eea3 100644
--- a/src/DiIiS-NA/D3-GameServer/GSSystem/QuestSystem/QuestProgress.cs
+++ b/src/DiIiS-NA/D3-GameServer/GSSystem/QuestSystem/QuestProgress.cs
@@ -276,6 +276,12 @@ namespace DiIiS_NA.GameServer.GSSystem.QuestSystem
return Game.Players.Values.First().Followers.Any(x => x.Value == sno);
}
+ public void AddUniqueFollower(World world, ActorSno sno)
+ {
+ if (!HasFollower(sno))
+ AddFollower(world, sno);
+ }
+
public void AddFollower(World world, ActorSno sno)
{
if (Game.Players.Count > 0)
diff --git a/src/DiIiS-NA/D3-GameServer/GameModsConfig.cs b/src/DiIiS-NA/D3-GameServer/GameModsConfig.cs
new file mode 100644
index 0000000..90cf18e
--- /dev/null
+++ b/src/DiIiS-NA/D3-GameServer/GameModsConfig.cs
@@ -0,0 +1,292 @@
+using System;
+using System.Collections.Generic;
+using System.Dynamic;
+using System.IO;
+using System.Threading.Tasks;
+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 GetDamageByDifficulty(int diff)
+ {
+ if (diff < 0) diff = 0;
+ if (diff > 19) diff = 19;
+ return !DamageByDifficulty.ContainsKey(diff) ? 1f : DamageByDifficulty[diff];
+ }
+ public Dictionary HealthByDifficulty { get; set; } = new()
+ {
+ [0] = 1.0f, [1] = 1.0f, [2] = 1.0f, [3] = 1.0f, [4] = 1.0f, [5] = 1.0f,
+ [6] = 1.0f, [7] = 1.0f, [8] = 1.0f, [9] = 1.0f, [10] = 1.0f, [11] = 1.0f,
+ [12] = 1.0f, [13] = 1.0f, [14] = 1.0f, [15] = 1.0f, [16] = 1.0f,
+ [17] = 1.0f, [18] = 1.0f, [19] = 1.0f,
+ };
+
+ public Dictionary DamageByDifficulty { get; set; } = new()
+ {
+ [0] = 1.0f, [1] = 1.0f, [2] = 1.0f, [3] = 1.0f, [4] = 1.0f, [5] = 1.0f,
+ [6] = 1.0f, [7] = 1.0f, [8] = 1.0f, [9] = 1.0f, [10] = 1.0f, [11] = 1.0f,
+ [12] = 1.0f, [13] = 1.0f, [14] = 1.0f, [15] = 1.0f, [16] = 1.0f,
+ [17] = 1.0f, [18] = 1.0f, [19] = 1.0f,
+ };
+ public float Experience { get; set; } = 1;
+ public float Gold { 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 MonsterConfig
+{
+ public float AttacksPerSecond { get; set; } = 1.2f;
+
+ 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 MonsterConfig 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();
+ }
+
+ private static readonly object InstanceCreationLock = new();
+ public static GameModsConfig Instance { get; private set; }
+
+ private static void CreateInstance()
+ {
+ lock (InstanceCreationLock)
+ {
+ if (!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!");
+ Logger.Info("$[italic]$Recreating $[underline]$config.mods.json$[/]$ in order to keep the structure and with all fields...$[/]$");
+ var @new = config.ToJson(Formatting.Indented);
+ File.WriteAllText(@"config.mods.json", @new);
+ Logger.Success("Game mods re-structured!");
+ 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,
+ Gold = 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 bce9504..cdbb2e2 100644
--- a/src/DiIiS-NA/D3-GameServer/GameServerConfig.cs
+++ b/src/DiIiS-NA/D3-GameServer/GameServerConfig.cs
@@ -65,21 +65,13 @@ namespace DiIiS_NA.GameServer
#endif
set => Set(nameof(AfkDisconnect), value);
}
-
- ///
- /// Always send motd when world loads for player.
- ///
- public bool MotdWhenWorldLoads
- {
- get => GetBoolean(nameof(MotdWhenWorldLoads), true);
- set => Set(nameof(MotdWhenWorldLoads), value);
- }
-
+
#region Game Mods
///
/// Rate of experience gain.
///
+ [Obsolete("Use GameModsConfig instead.")]
public float RateExp
{
get => GetFloat(nameof(RateExp), 1);
@@ -89,6 +81,7 @@ namespace DiIiS_NA.GameServer
///
/// Rate of gold gain.
///
+ [Obsolete("Use GameModsConfig instead.")]
public float RateMoney
{
get => GetFloat(nameof(RateMoney), 1);
@@ -98,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);
@@ -113,6 +108,7 @@ namespace DiIiS_NA.GameServer
///
/// Rate of monster's HP.
///
+ [Obsolete("Use GameModsConfig instead.")]
public float RateMonsterHP
{
get => GetFloat(nameof(RateMonsterHP), 1);
@@ -122,6 +118,7 @@ namespace DiIiS_NA.GameServer
///
/// Rate of monster's damage.
///
+ [Obsolete("Use GameModsConfig instead.")]
public float RateMonsterDMG
{
get => GetFloat(nameof(RateMonsterDMG), 1);
@@ -131,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);
@@ -140,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);
@@ -149,6 +148,7 @@ namespace DiIiS_NA.GameServer
///
/// Resurrection charges on changing worlds
///
+ [Obsolete("Use GameModsConfig instead.")]
public int ResurrectionCharges
{
get => GetInt(nameof(ResurrectionCharges), 3);
@@ -158,6 +158,7 @@ namespace DiIiS_NA.GameServer
///
/// Boss Health Multiplier
///
+ [Obsolete("Use GameModsConfig instead.")]
public float BossHealthMultiplier
{
get => GetFloat(nameof(BossHealthMultiplier), 6f);
@@ -167,6 +168,7 @@ namespace DiIiS_NA.GameServer
///
/// Boss Damage Multiplier
///
+ [Obsolete("Use GameModsConfig instead.")]
public float BossDamageMultiplier
{
get => GetFloat(nameof(BossDamageMultiplier), 3f);
@@ -176,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);
@@ -185,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);
@@ -194,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);
@@ -203,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);
@@ -212,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);
@@ -221,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);
@@ -230,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);
@@ -239,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);
@@ -248,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);
@@ -257,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);
@@ -266,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);
@@ -275,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);
@@ -284,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);
@@ -293,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);
@@ -302,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);
@@ -311,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);
@@ -320,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);
@@ -327,6 +346,7 @@ namespace DiIiS_NA.GameServer
}
#endregion
+
public static GameServerConfig Instance { get; } = new();
private GameServerConfig() : base("Game-Server")
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);
}
diff --git a/src/DiIiS-NA/REST/SocketBase.cs b/src/DiIiS-NA/REST/SocketBase.cs
index 148381a..fbaa7ca 100644
--- a/src/DiIiS-NA/REST/SocketBase.cs
+++ b/src/DiIiS-NA/REST/SocketBase.cs
@@ -45,16 +45,14 @@ namespace DiIiS_NA.REST
try
{
- using (var socketEventargs = new SocketAsyncEventArgs())
- {
- socketEventargs.SetBuffer(_receiveBuffer, 0, _receiveBuffer.Length);
- socketEventargs.Completed += (sender, args) => ReadHandlerInternal(args);
- socketEventargs.SocketFlags = SocketFlags.None;
- socketEventargs.RemoteEndPoint = _socket.RemoteEndPoint;
+ using var socketEventArgs = new SocketAsyncEventArgs();
+ socketEventArgs.SetBuffer(_receiveBuffer, 0, _receiveBuffer.Length);
+ socketEventArgs.Completed += (sender, args) => ReadHandlerInternal(args);
+ socketEventArgs.SocketFlags = SocketFlags.None;
+ socketEventArgs.RemoteEndPoint = _socket.RemoteEndPoint;
- if (!_socket.ReceiveAsync(socketEventargs))
- ReadHandlerInternal(socketEventargs);
- }
+ if (!_socket.ReceiveAsync(socketEventArgs))
+ ReadHandlerInternal(socketEventArgs);
}
catch (Exception ex)
{
diff --git a/src/DiIiS-NA/config.ini b/src/DiIiS-NA/config.ini
index 764b0b4..a6edadb 100644
--- a/src/DiIiS-NA/config.ini
+++ b/src/DiIiS-NA/config.ini
@@ -1,69 +1,118 @@
-; Settings for Bnet
-[Battle-Server]
-Enabled = true
-BindIP = 127.0.0.1
-WebPort = 9800
-Port = 1119
-BindIPv6 = ::1
-MotdEnabled = true
-Motd = Welcome to Blizzless Diablo 3!
+; ==========
+; Configuration File Template
+; ==========
+; This is a template configuration file which can be modified as desired. The following branches are available for your convenience:
+; - Community branch (recommended): https://github.com/blizzless/blizzless-diiis/tree/community
+; - Test-stable branch: https://github.com/blizzless/blizzless-diiis/
+; - Master branch: https://github.com/blizzless/blizzless-diiis/tree/master
-; ------------------------
+; Battle Server Settings
+[Battle-Server]
+; Enable or disable the Battle Server
+Enabled = true
+; IP address on which the server will be bound
+BindIP = 127.0.0.1
+; Port for web interactions
+WebPort = 9800
+; Port for the server
+Port = 1119
+
+; Message of the Day (MotD) Settings
+; - MotdEnabled: Toggles whether the Message of The Day (MotD) is enabled or not
+; - MotdEnabledWhenWorldLoads: Determines if MotD should be displayed every time a new world is loaded for a player
+; - Motd: Text displayed as the MotD
+MotdEnabled = true
+MotdEnabledWhenWorldLoads = false
+Motd = Welcome to Blizzless D3!
+; - Remote MotD Enabled: Enable receiving MotD from a remote URL via POST request with payload: { "GameAccountId": ulong, "ToonName": string, "WorldGlobalId": uint }
+; - MotdRemoteUrl: Remote URL to send payload and receive string; falls back to Motd string if unavailable
+MotdEnabledRemote = false
+MotdRemoteUrl = https://your-site.local/yourmotd
+
+; IWServer Setting (Currently inactive)
; [IWServer]
; IWServer = false
-; ------------------------
-; REST services for login (and others)
+; REST Service Settings for Login and Other Functions
[REST]
IP = 127.0.0.1
-Public = true
PublicIP = 127.0.0.1
PORT = 80
+Public = true
-; ------------------------
-; Game server options and game-mods.
+; Game Server Settings
[Game-Server]
+; Enable or disable the game server
Enabled = true
+; Activate game server core functionality
CoreActive = true
+; IP address on which the game server will be bound
BindIP = 127.0.0.1
+; Port for web interactions
WebPort = 9001
+; Port for game server connections
Port = 1345
+; IP address for IPv6 bindings
BindIPv6 = ::1
+; DRLG Emulation status
DRLGemu = true
-; Modding of game
+
+; NAT (Network Address Translation) Settings
+[NAT]
+; Toggles the NAT functionality
+Enabled = True
+; Your public IP address to enable NAT
+PublicIP = 127.0.0.1
+
+; ==========
+; Game Modding Configuration
+; For documentation, please check https://github.com/blizzless/blizzless-diiis/blob/community/docs/game-world-settings.md
+; Multipliers for various gameplay rates
RateExp = 1
RateMoney = 1
RateDrop = 1
RateChangeDrop = 1
RateMonsterHP = 1
RateMonsterDMG = 1
-; Percentage that a unique, legendary, set or special item created is unidentified
-ChanceHighQualityUnidentified = 80
-; Percentage that normal item created is unidentified
+
+; Quality and identification chances for items
+ChanceHighQualityUnidentified = 30
ChanceNormalUnidentified = 5
-; Amount of times user can resurrect at corpse
-ResurrectionCharges = 5
-BossHealthMultiplier = 2
-BossDamageMultiplier = 1
-AutoSaveQuests = true
-; ------------------------
-; Network address translation
-[NAT]
-Enabled = True
-PublicIP = 127.0.0.1
+; Boss health and damage multipliers
+BossHealthMultiplier = 6
+BossDamageMultiplier = 3
-; ------------------------
-; Where the outputs should be.
-; Best for visualization (default): AnsiLog (target: Ansi)
-; Best for debugging: ConsoleLog (target: console)
-; Best for packet analysis: PacketLog (target: file)
-; Logging level (ordered):
-; Rarely used: RenameAccountLog (0), ChatMessage (1), BotCommand (2),
-; Useful: Debug (3), MethodTrace (4), Trace (5),
-; Normal and human-readable: Info (6), Success (7),
-; Errors: Warn (8), Error (9), Fatal (10),
-; Network Logs: PacketDump (11)
+; Nephalem Rift progress multiplier
+NephalemRiftProgressMultiplier = 1
+
+; Health potion mechanics
+HealthPotionRestorePercentage = 60
+HealthPotionCooldown = 30
+ResurrectionCharges = 3
+
+; Waypoint settings
+UnlockAllWaypoints = false
+
+; Player attribute modifiers
+StrengthMultiplier = 1
+StrengthParagonMultiplier = 1
+DexterityMultiplier = 1
+DexterityParagonMultiplier = 1
+IntelligenceMultiplier = 1
+IntelligenceParagonMultiplier = 1
+VitalityMultiplier = 1
+VitalityParagonMultiplier = 1
+
+; Quest saving behavior
+AutoSaveQuests = false
+
+; Minimap visibility settings
+ForceMinimapVisibility = false
+
+; ===================
+; Log Output Settings
+; AnsiLog for visualization, ConsoleLog for debugging, and PacketLog for packet analysis
[AnsiLog]
Enabled = true
@@ -76,8 +125,8 @@ MaximumLevel = Fatal
Enabled = false
Target = Console
IncludeTimeStamps = true
-MinimumLevel = Debug
-MaximumLevel = PacketDump
+MinimumLevel = MethodTrace
+MaximumLevel = Fatal
[PacketLog]
Enabled = true
diff --git a/src/DiIiS-NA/database.Account.config b/src/DiIiS-NA/database.Account.config
index 9de7d15..1f16a9e 100644
--- a/src/DiIiS-NA/database.Account.config
+++ b/src/DiIiS-NA/database.Account.config
@@ -8,7 +8,7 @@
true
0
- Server=localhost;Database=diiis;User ID=postgres;Password=postgres
+ Server=localhost;Database=diiis;User ID=postgres;Password=password
on_close
0
@@ -16,4 +16,4 @@
false
false
-
\ No newline at end of file
+
diff --git a/src/DiIiS-NA/database.Worlds.config b/src/DiIiS-NA/database.Worlds.config
index 8545182..2f11a7b 100644
--- a/src/DiIiS-NA/database.Worlds.config
+++ b/src/DiIiS-NA/database.Worlds.config
@@ -7,7 +7,7 @@
true
0
- Server=localhost;Database=worlds;User ID=postgres;Password=postgres
+ Server=localhost;Database=worlds;User ID=postgres;Password=password
on_close
0
@@ -15,4 +15,4 @@
false
false
-
\ No newline at end of file
+