Stable WIP: Health/Damage multipliers in settings by difficulty.

This commit is contained in:
Lucca Faria Ferri 2023-06-22 21:40:54 -07:00
parent 7275551ab9
commit b39f5e7c4c
14 changed files with 202 additions and 114 deletions

View File

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

View File

@ -200,6 +200,8 @@ public class AnsiTarget : LogTarget
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)
};
@ -212,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() + "$[/]$";
}

View File

@ -68,6 +68,15 @@ namespace DiIiS_NA.Core.Logging
/// Fatal messages (usually unrecoverable errors that leads to client or server crashes).
/// </summary>
Fatal,
/// <summary>
/// The messages meant for quest general logging purposes.
/// </summary>
QuestInfo,
/// <summary>
/// The messages meant for quest logging purposes.
/// </summary>
QuestStep,
/// <summary>
/// Packet messages.
/// </summary>
@ -109,7 +118,29 @@ namespace DiIiS_NA.Core.Logging
{
#if DEBUG
var fileName = Path.GetFileName(filePath);
Log(Level.MethodTrace, $"$[underline white]${fileName}:{lineNumber}$[/]$ $[darkolivegreen3_2]${methodName}()$[/]$: " + message, null);
Log(Level.MethodTrace, $"$[link={filePath}]${fileName}:{lineNumber} in {methodName}()$[/]$: " + message, null);
#else
Log(Level.MethodTrace, $"$[darkolivegreen3_2]${methodName}()$[/]$: " + message, null);
#endif
}
public void QuestStep(string message, [CallerMemberName] string methodName = "",
[CallerFilePath] string filePath = "", [CallerLineNumber] int lineNumber = 0)
{
#if DEBUG
var fileName = Path.GetFileName(filePath);
Log(Level.QuestStep, $"$[link={filePath}]${fileName}:{lineNumber} in {methodName}()$[/]$: " + message, null);
#else
Log(Level.MethodTrace, $":crossed_swords: $[darkolivegreen3_2]${methodName}()$[/]$: " + message, null);
#endif
}
public void QuestInfo(string message, [CallerMemberName] string methodName = "",
[CallerFilePath] string filePath = "", [CallerLineNumber] int lineNumber = 0)
{
#if DEBUG
var fileName = Path.GetFileName(filePath);
Log(Level.MethodTrace, $"$[link={filePath}]${fileName}:{lineNumber} in {methodName}()$[/]$: " + message, null);
#else
Log(Level.MethodTrace, $"$[darkolivegreen3_2]${methodName}()$[/]$: " + message, null);
#endif
@ -155,7 +186,7 @@ namespace DiIiS_NA.Core.Logging
/// <param name="message">The log message.</param>
/// <param name="args">Additional arguments.</param>
public void Fatal(string message, params object[] args) => Log(Level.Fatal, message, args);
public void Fatal(string message, params object[] args) => Log(Level.Fatal, ":skull_and_crossbones: " + message, args);
#endregion

View File

@ -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"))

View File

@ -13,6 +13,6 @@ public class GameCommand : CommandGroup
public void ReloadMods(string[] @params, BattleClient invokerClient)
{
GameModsConfig.ReloadSettings();
invokerClient.SendServerWhisper("Game mods updated successfully!");
invokerClient?.SendServerWhisper("Game mods updated successfully!");
}
}

View File

@ -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);

View File

@ -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
}

View File

@ -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
/// </summary>
public ConcurrentDictionary<GameClient, Player> Players { get; private set; }
public Player FirstPlayer() => Players.Values.First();
public ImmutableArray<Player> 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.
/// </summary>
public int CurrentQuest = -1;
public int CurrentSideQuest = -1;
public bool IsCurrentOpenWorld => CurrentQuest == 312429;
/// <summary>
/// Current quest step SNOid.
/// </summary>
@ -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[Difficulty] * GameModsConfig.Instance.Monster.HealthMultiplier;
DmgModifier = GameModsConfig.Instance.Rate.GetDamageByDifficulty(Difficulty) * 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)

View File

@ -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));
/// <summary>
/// Accessor for quests
/// </summary>
/// <param name="snoQuest">snoId of the quest to retrieve</param>
/// <returns></returns>
public readonly Dictionary<int, QuestRegistry.Quest> Quests = new();
public readonly Dictionary<int, QuestRegistry.Quest> 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);
/// <summary>
/// Advances a quest by a step
/// </summary>
/// <param name="snoQuest">snoID of the quest to advance</param>
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);
});
}

View File

@ -931,7 +931,7 @@ namespace DiIiS_NA.GameServer.GSSystem.MapSystem
/// <param name="position">The position for drop.</param>
public void SpawnGold(Actor source, Player player, int Min = -1)
{
int amount = (int)(LootManager.GetGoldAmount(player.Attributes[GameAttributes.Level]) * Game.GoldModifier * GameModsConfig.Instance.Rate.Money);
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.

View File

@ -244,15 +244,22 @@ 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)
if (Game.CurrentStep == -1 || Game.CurrentStep == 7)
if (Game.CurrentQuest == 72095 && Game.CurrentStep is -1 or 7)
{
// 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));
// }
AddFollower(Game.GetWorld(WorldSno.trout_town), ActorSno._leah);
}
});
else
{
Logger.QuestStep($"Can't add leah follower: {Game.CurrentQuest} / {Game.CurrentStep}");
}
});
}

View File

@ -1,6 +1,8 @@
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;
@ -10,8 +12,35 @@ namespace DiIiS_NA.D3_GameServer;
public class RateConfig
{
public float GetDamageByDifficulty(int diff)
{
if (diff > 19) diff = 19;
if (!DamageByDifficulty.ContainsKey(diff) || Math.Abs(DamageByDifficulty[diff] - (-1)) < 0.001)
{
if (diff == 0)
return 1;
return GetDamageByDifficulty(diff - 1);
}
return DamageByDifficulty[diff];
}
public Dictionary<int, float> 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<int, float> 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 Money { get; set; } = 1;
public float Gold { get; set; } = 1;
public float Drop { get; set; } = 1;
public float ChangeDrop { get; set; } = 1;
}
@ -94,18 +123,17 @@ public class GameModsConfig
public static void ReloadSettings()
{
CreateInstance(reload: true);
CreateInstance();
}
private static readonly object InstanceCreationLock = new();
public static GameModsConfig Instance { get; private set; }
private static void CreateInstance(bool reload = false)
private static void CreateInstance()
{
lock (InstanceCreationLock)
{
if (reload && File.Exists("config.mods.json")) File.Delete("config.mods.json");
if (reload || !File.Exists("config.mods.json"))
if (!File.Exists("config.mods.json"))
{
Instance = CreateDefaultFile();
}
@ -115,6 +143,10 @@ public class GameModsConfig
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;
}
@ -132,63 +164,7 @@ public class GameModsConfig
GameModsConfig content = new()
{
#pragma warning disable CS0618
Rate =
{
Experience = migration.RateExp,
Money = migration.RateMoney,
ChangeDrop = migration.RateChangeDrop,
Drop = migration.RateDrop
},
Health =
{
ResurrectionCharges = migration.ResurrectionCharges,
PotionCooldown = migration.HealthPotionCooldown,
PotionRestorePercentage = migration.HealthPotionRestorePercentage
},
Monster =
{
HealthMultiplier = migration.RateMonsterHP,
DamageMultiplier = migration.RateMonsterDMG
},
Boss =
{
HealthMultiplier = migration.BossHealthMultiplier,
DamageMultiplier = migration.BossDamageMultiplier
},
Quest =
{
AutoSave = migration.AutoSaveQuests,
UnlockAllWaypoints = migration.UnlockAllWaypoints
},
Player =
{
Multipliers =
{
Strength = new(migration.StrengthMultiplier, migration.StrengthParagonMultiplier),
Dexterity = new(migration.DexterityMultiplier, migration.DexterityParagonMultiplier),
Intelligence = new(migration.IntelligenceMultiplier, migration.IntelligenceParagonMultiplier),
Vitality = new(migration.VitalityMultiplier, migration.VitalityParagonMultiplier)
}
},
Items =
{
UnidentifiedDropChances =
{
HighQuality = migration.ChanceHighQualityUnidentified,
NormalQuality = migration.ChanceNormalUnidentified
}
},
Minimap =
{
ForceVisibility = migration.ForceMinimapVisibility
},
NephalemRift =
{
AutoFinish = migration.NephalemRiftAutoFinish,
AutoFinishThreshold = migration.NephalemRiftAutoFinishThreshold,
OrbsChance = migration.NephalemRiftAutoFinishThreshold,
ProgressMultiplier = migration.NephalemRiftProgressMultiplier
}
#pragma warning restore CS0618
};
File.WriteAllText("config.mods.json", content.ToJson());

View File

@ -8,7 +8,7 @@
<property name="use_proxy_validator">true</property>
<property name="command_timeout">0</property>
<property name="connection.connection_string">
Server=localhost;Database=diiis;User ID=postgres;Password=postgres
Server=localhost;Database=diiis;User ID=postgres;Password=131275
</property>
<property name="connection.release_mode">on_close</property>
<property name="adonet.batch_size">0</property>

View File

@ -7,7 +7,7 @@
<property name="use_proxy_validator">true</property>
<property name="command_timeout">0</property>
<property name="connection.connection_string">
Server=localhost;Database=worlds;User ID=postgres;Password=postgres
Server=localhost;Database=worlds;User ID=postgres;Password=131275
</property>
<property name="connection.release_mode">on_close</property>
<property name="adonet.batch_size">0</property>