diff --git a/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs b/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs index f703bcc0..e693d129 100644 --- a/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs @@ -1,6 +1,7 @@ using Arrowgene.Ddon.GameServer.Characters; using Arrowgene.Ddon.GameServer.Party; using Arrowgene.Ddon.GameServer.Quests; +using Arrowgene.Ddon.GameServer.Scripting.Interfaces; using Arrowgene.Ddon.Server; using Arrowgene.Ddon.Server.Network; using Arrowgene.Ddon.Shared.Entity.PacketStructure; @@ -173,7 +174,18 @@ public override S2CInstanceEnemyKillRes Handle(GameClient client, C2SInstanceEne } } - uint baseEnemyExp = _gameServer.ExpManager.GetScaledPointAmount(RewardSource.Enemy, ExpType.ExperiencePoints, enemyKilled.GetDroppedExperience()); + uint baseEnemyExp = 0; + if (Server.GameLogicSettings.Get("GameLogicSettings", "EnableExpCalculationMixin")) + { + var expCurveMixin = Server.ScriptManager.MixinModule.Get("exp_curve"); + baseEnemyExp = expCurveMixin.GetExpValue(enemyKilled); + } + else + { + baseEnemyExp = enemyKilled.GetDroppedExperience(); + } + + baseEnemyExp = _gameServer.ExpManager.GetScaledPointAmount(RewardSource.Enemy, ExpType.ExperiencePoints, baseEnemyExp); uint calcExp = _gameServer.ExpManager.GetAdjustedExp(client.GameMode, RewardSource.Enemy, client.Party, baseEnemyExp, enemyKilled.Lv); uint calcPP = _gameServer.ExpManager.GetScaledPointAmount(RewardSource.Enemy, ExpType.PlayPoints, enemyKilled.GetDroppedPlayPoints()); diff --git a/Arrowgene.Ddon.GameServer/Scripting/GameServerScriptManager.cs b/Arrowgene.Ddon.GameServer/Scripting/GameServerScriptManager.cs index 2390c9a9..289b24c8 100644 --- a/Arrowgene.Ddon.GameServer/Scripting/GameServerScriptManager.cs +++ b/Arrowgene.Ddon.GameServer/Scripting/GameServerScriptManager.cs @@ -22,6 +22,7 @@ public class GameServerScriptManager : ScriptManager private GlobalVariables Globals { get; } public NpcExtendedFacilityModule NpcExtendedFacilityModule { get; private set; } = new NpcExtendedFacilityModule(); public GameItemModule GameItemModule { get; private set; } = new GameItemModule(); + public MixinModule MixinModule { get; private set; } = new MixinModule(); public GameServerScriptManager(DdonGameServer server) : base(server.AssetRepository.AssetsPath) { @@ -31,6 +32,7 @@ public GameServerScriptManager(DdonGameServer server) : base(server.AssetReposit // Add modules to the list so the generic logic can iterate over all scripting modules ScriptModules[NpcExtendedFacilityModule.ModuleRoot] = NpcExtendedFacilityModule; ScriptModules[GameItemModule.ModuleRoot] = GameItemModule; + ScriptModules[MixinModule.ModuleRoot] = MixinModule; } public override void Initialize() diff --git a/Arrowgene.Ddon.GameServer/Scripting/Interfaces/IExpCurveMixin.cs b/Arrowgene.Ddon.GameServer/Scripting/Interfaces/IExpCurveMixin.cs new file mode 100644 index 00000000..9b5b0dc3 --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Scripting/Interfaces/IExpCurveMixin.cs @@ -0,0 +1,9 @@ +using Arrowgene.Ddon.Shared.Model; + +namespace Arrowgene.Ddon.GameServer.Scripting.Interfaces +{ + public abstract class IExpCurveMixin + { + public abstract uint GetExpValue(InstancedEnemy enemy); + } +} diff --git a/Arrowgene.Ddon.GameServer/Scripting/Modules/MixinModule.cs b/Arrowgene.Ddon.GameServer/Scripting/Modules/MixinModule.cs new file mode 100644 index 00000000..6b805799 --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Scripting/Modules/MixinModule.cs @@ -0,0 +1,69 @@ +using Arrowgene.Ddon.Shared; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Scripting; +using System; +using System.Collections.Generic; +using System.Dynamic; +using System.IO; +using System.Runtime.CompilerServices; + +namespace Arrowgene.Ddon.GameServer.Scripting +{ + public class MixinModule : ScriptModule + { + public override string ModuleRoot => "mixins"; + public override string Filter => "*.csx"; + public override bool ScanSubdirectories => true; + public override bool EnableHotLoad => true; + + private Dictionary Mixins { get; set; } + + public MixinModule() + { + Mixins = new Dictionary(); + } + + public T Get(string scriptName) + { + if (!Mixins.ContainsKey(scriptName)) + { + throw new Exception($"A mixin with the name '{scriptName}' doesn't exist"); + } + + return (T) Mixins[scriptName]; + } + + public override ScriptOptions Options() + { + return ScriptOptions.Default + .AddReferences(MetadataReference.CreateFromFile(typeof(DdonGameServer).Assembly.Location)) + .AddReferences(MetadataReference.CreateFromFile(typeof(AssetRepository).Assembly.Location)) + .AddReferences(MetadataReference.CreateFromFile(typeof(DynamicAttribute).Assembly.Location)) + .AddImports("System", "System.Collections", "System.Collections.Generic") + .AddImports("System.Runtime.CompilerServices") + .AddImports("Arrowgene.Ddon.Shared") + .AddImports("Arrowgene.Ddon.Shared.Model") + .AddImports("Arrowgene.Ddon.GameServer") + .AddImports("Arrowgene.Ddon.GameServer.Characters") + .AddImports("Arrowgene.Ddon.GameServer.Scripting") + .AddImports("Arrowgene.Ddon.GameServer.Scripting.Interfaces") + .AddImports("Arrowgene.Ddon.Shared.Entity.PacketStructure") + .AddImports("Arrowgene.Ddon.Shared.Entity.Structure") + .AddImports("Arrowgene.Ddon.Shared.Model.Quest"); + } + + public override bool EvaluateResult(string path, ScriptState result) + { + if (result == null) + { + return false; + } + + // Mixins are c# Func<> with arbitary return and params based on the functionality + string mixinName = Path.GetFileNameWithoutExtension(path); + Mixins[mixinName] = result.ReturnValue; + + return true; + } + } +} diff --git a/Arrowgene.Ddon.Shared/Files/Assets/scripts/mixins/README.md b/Arrowgene.Ddon.Shared/Files/Assets/scripts/mixins/README.md new file mode 100644 index 00000000..ef8e3399 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/scripts/mixins/README.md @@ -0,0 +1,3 @@ +# Mixins + +Mixins are a collection of configurable functionality which can be shared across various server components and scripts, but is not tied to a particular feature. \ No newline at end of file diff --git a/Arrowgene.Ddon.Shared/Files/Assets/scripts/mixins/exp_curve.csx b/Arrowgene.Ddon.Shared/Files/Assets/scripts/mixins/exp_curve.csx new file mode 100644 index 00000000..d54dde36 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/scripts/mixins/exp_curve.csx @@ -0,0 +1,17 @@ +/** + * @breif This mixin is used to calculate the exp amount for a given + * enemy based on observations from realworld exp data. It should be + * possible to call this script directly from the server code or from + * other scripts. + */ + +public class Mixin : IExpCurveMixin +{ + public override uint GetExpValue(InstancedEnemy enemy) + { + // TODO: Implement algorithm to calculate exp amount + return enemy.GetDroppedExperience(); + } +} + +return new Mixin(); diff --git a/Arrowgene.Ddon.Shared/Files/Assets/scripts/settings/GameLogicSettings.csx b/Arrowgene.Ddon.Shared/Files/Assets/scripts/settings/GameLogicSettings.csx index 212a8b5f..f09dbae9 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/scripts/settings/GameLogicSettings.csx +++ b/Arrowgene.Ddon.Shared/Files/Assets/scripts/settings/GameLogicSettings.csx @@ -17,6 +17,13 @@ bool EnableVisualEquip = true; uint DefaultWarpFavorites = 3; uint LaternBurnTimeInSeconds = 1500; +/** + * @brief If enabled, overrides default EXP values assigned to enemies + * in the InstanceEnemyKillHandler and uses the calculations inside of + * /mixins/exp_curve.csx. + */ +bool EnableExpCalculationMixin = true; + // Crafting Settings double AdditionalProductionSpeedFactor = 1.0; double AdditionalCostPerformanceFactor = 1.0;