From e6e507373728706ec44aa059354f7ee252b605f0 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Mon, 23 Dec 2024 14:40:57 -0500 Subject: [PATCH] refactor: Refactor code and show different scripting example - Refactored code so implementation can be shared between Server and DdonGameServer. - Moved settings into settings module and implemented an example using a less structured scripting module interface. - Created new ScriptableSettings class to assist with interacting with non-structured settings module. - Created README.md files for suggestions and guidelines for module implementation. --- Arrowgene.Ddon.Cli/Command/ServerCommand.cs | 12 +- .../Characters/ExpManager.cs | 1 + Arrowgene.Ddon.GameServer/DdonGameServer.cs | 5 +- .../Scripting/GameServerScriptManager.cs | 39 ++ .../Modules/NpcExtendedFacilityModule.cs | 3 +- Arrowgene.Ddon.LoginServer/DdonLoginServer.cs | 1 + .../Handler/GetLoginSettingHandler.cs | 1 + .../Arrowgene.Ddon.Server.csproj | 3 + Arrowgene.Ddon.Server/GameLogicSetting.cs | 325 --------- .../ScriptedServerSettings.cs | 145 ---- .../Scripting/ScriptManager.cs | 79 +-- .../Scripting/ScriptModule.cs | 12 +- .../Scripting/ScriptUtils.cs | 2 - .../Scripting/interfaces/GameLogicSetting.cs | 649 ++++++++++++++++++ .../modules/GameServerSettingsModule.cs | 52 ++ .../Scripting/utils/ScriptableSettings.cs | 52 ++ Arrowgene.Ddon.Server/ServerScriptManager.cs | 36 + .../Arrowgene.Ddon.Shared.csproj | 23 +- .../Assets/scripts/GameLogicSettings.csx | 163 ----- .../Files/Assets/scripts/README.md | 12 + .../scripts/extended_facilities/README.md | 10 + .../scripts/settings/GameLogicSettings.csx | 159 +++++ .../Files/Assets/scripts/settings/README.md | 18 + .../GameServer/Characters/CraftManagerTest.cs | 21 +- 24 files changed, 1107 insertions(+), 716 deletions(-) create mode 100644 Arrowgene.Ddon.GameServer/Scripting/GameServerScriptManager.cs delete mode 100644 Arrowgene.Ddon.Server/GameLogicSetting.cs delete mode 100644 Arrowgene.Ddon.Server/ScriptedServerSettings.cs rename {Arrowgene.Ddon.GameServer => Arrowgene.Ddon.Server}/Scripting/ScriptManager.cs (74%) rename {Arrowgene.Ddon.GameServer => Arrowgene.Ddon.Server}/Scripting/ScriptModule.cs (61%) rename {Arrowgene.Ddon.GameServer => Arrowgene.Ddon.Server}/Scripting/ScriptUtils.cs (92%) create mode 100644 Arrowgene.Ddon.Server/Scripting/interfaces/GameLogicSetting.cs create mode 100644 Arrowgene.Ddon.Server/Scripting/modules/GameServerSettingsModule.cs create mode 100644 Arrowgene.Ddon.Server/Scripting/utils/ScriptableSettings.cs create mode 100644 Arrowgene.Ddon.Server/ServerScriptManager.cs delete mode 100644 Arrowgene.Ddon.Shared/Files/Assets/scripts/GameLogicSettings.csx create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/scripts/README.md create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/scripts/extended_facilities/README.md create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/scripts/settings/GameLogicSettings.csx create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/scripts/settings/README.md diff --git a/Arrowgene.Ddon.Cli/Command/ServerCommand.cs b/Arrowgene.Ddon.Cli/Command/ServerCommand.cs index 7c480c84..23b4ce5a 100644 --- a/Arrowgene.Ddon.Cli/Command/ServerCommand.cs +++ b/Arrowgene.Ddon.Cli/Command/ServerCommand.cs @@ -20,7 +20,7 @@ namespace Arrowgene.Ddon.Cli.Command private readonly Setting _setting; private DdonLoginServer _loginServer; private DdonGameServer _gameServer; - private ScriptedServerSettings _scriptServerSettings; + private ServerScriptManager _serverScriptManager; private DdonWebServer _webServer; private RpcWebServer _rpcWebServer; private IDatabase _database; @@ -111,15 +111,15 @@ namespace Arrowgene.Ddon.Cli.Command _assetRepository.Initialize(); } - if (_scriptServerSettings == null) + if (_serverScriptManager == null) { - _scriptServerSettings = new ScriptedServerSettings(_setting.AssetPath); - _scriptServerSettings.LoadSettings(); + _serverScriptManager = new ServerScriptManager(_setting.AssetPath); + _serverScriptManager.Initialize(); } if (_loginServer == null) { - _loginServer = new DdonLoginServer(_setting.LoginServerSetting, _scriptServerSettings.GameLogicSetting, _database, _assetRepository); + _loginServer = new DdonLoginServer(_setting.LoginServerSetting, _serverScriptManager.GameServerSettings.GameLogicSetting, _database, _assetRepository); } if (_webServer == null) @@ -129,7 +129,7 @@ namespace Arrowgene.Ddon.Cli.Command if (_gameServer == null) { - _gameServer = new DdonGameServer(_setting.GameServerSetting, _scriptServerSettings.GameLogicSetting, _database, _assetRepository); + _gameServer = new DdonGameServer(_setting.GameServerSetting, _serverScriptManager.GameServerSettings.GameLogicSetting, _database, _assetRepository); } if (_rpcWebServer == null) diff --git a/Arrowgene.Ddon.GameServer/Characters/ExpManager.cs b/Arrowgene.Ddon.GameServer/Characters/ExpManager.cs index 634b035b..70f265cf 100644 --- a/Arrowgene.Ddon.GameServer/Characters/ExpManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/ExpManager.cs @@ -2,6 +2,7 @@ using Arrowgene.Ddon.GameServer.Party; using Arrowgene.Ddon.Server; using Arrowgene.Ddon.Server.Network; +using Arrowgene.Ddon.Server.Scripting.interfaces; using Arrowgene.Ddon.Shared.Entity.PacketStructure; using Arrowgene.Ddon.Shared.Entity.Structure; using Arrowgene.Ddon.Shared.Model; diff --git a/Arrowgene.Ddon.GameServer/DdonGameServer.cs b/Arrowgene.Ddon.GameServer/DdonGameServer.cs index fdf3f6cd..e2f09c93 100644 --- a/Arrowgene.Ddon.GameServer/DdonGameServer.cs +++ b/Arrowgene.Ddon.GameServer/DdonGameServer.cs @@ -34,6 +34,7 @@ using Arrowgene.Ddon.GameServer.Shop; using Arrowgene.Ddon.Server; using Arrowgene.Ddon.Server.Handler; using Arrowgene.Ddon.Server.Network; +using Arrowgene.Ddon.Server.Scripting.interfaces; using Arrowgene.Ddon.Shared; using Arrowgene.Ddon.Shared.Entity; using Arrowgene.Ddon.Shared.Entity.PacketStructure; @@ -55,7 +56,7 @@ namespace Arrowgene.Ddon.GameServer { ServerSetting = new GameServerSetting(setting); GameLogicSettings = gameLogicSettings; - ScriptManager = new ScriptManager(this); + ScriptManager = new GameServerScriptManager(this); ClientLookup = new GameClientLookup(); ChatLogHandler = new ChatLogHandler(); ChatManager = new ChatManager(this); @@ -95,7 +96,7 @@ namespace Arrowgene.Ddon.GameServer public event EventHandler ClientConnectionChangeEvent; public GameServerSetting ServerSetting { get; } public GameLogicSetting GameLogicSettings { get; } - public ScriptManager ScriptManager { get; } + public GameServerScriptManager ScriptManager { get; } public ChatManager ChatManager { get; } public ItemManager ItemManager { get; } public CraftManager CraftManager { get; } diff --git a/Arrowgene.Ddon.GameServer/Scripting/GameServerScriptManager.cs b/Arrowgene.Ddon.GameServer/Scripting/GameServerScriptManager.cs new file mode 100644 index 00000000..4ac0010b --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Scripting/GameServerScriptManager.cs @@ -0,0 +1,39 @@ +using Arrowgene.Ddon.Server; +using Arrowgene.Ddon.Shared.Scripting; +using Arrowgene.Logging; + +namespace Arrowgene.Ddon.GameServer.Scripting +{ + public class GlobalVariables + { + public GlobalVariables(DdonGameServer server) + { + Server = server; + } + + public DdonGameServer Server { get; } + }; + + public class GameServerScriptManager : ScriptManager + { + private static readonly ServerLogger Logger = LogProvider.Logger(typeof(GameServerScriptManager)); + + private DdonGameServer Server { get; } + private GlobalVariables Globals { get; } + public NpcExtendedFacilityModule NpcExtendedFacilityModule { get; private set; } = new NpcExtendedFacilityModule(); + + public GameServerScriptManager(DdonGameServer server) : base(server.AssetRepository.AssetsPath) + { + Server = server; + Globals = new GlobalVariables(Server); + + // Add modules to the list so the generic logic can iterate over all scripting modules + ScriptModules[NpcExtendedFacilityModule.ModuleRoot] = NpcExtendedFacilityModule; + } + + public override void Initialize() + { + base.Initialize(Globals); + } + } +} diff --git a/Arrowgene.Ddon.GameServer/Scripting/Modules/NpcExtendedFacilityModule.cs b/Arrowgene.Ddon.GameServer/Scripting/Modules/NpcExtendedFacilityModule.cs index b4c11fce..f71a3fbc 100644 --- a/Arrowgene.Ddon.GameServer/Scripting/Modules/NpcExtendedFacilityModule.cs +++ b/Arrowgene.Ddon.GameServer/Scripting/Modules/NpcExtendedFacilityModule.cs @@ -12,6 +12,7 @@ namespace Arrowgene.Ddon.GameServer.Scripting public override string ModuleRoot => "extended_facilities"; public override string Filter => "*.csx"; public override bool ScanSubdirectories => true; + public override bool EnableHotLoad => true; public Dictionary NpcExtendedFacilities { get; private set; } @@ -37,7 +38,7 @@ namespace Arrowgene.Ddon.GameServer.Scripting .AddImports("Arrowgene.Ddon.Shared.Model.Quest"); } - public override bool EvaluateResult(ScriptState result) + public override bool EvaluateResult(string path, ScriptState result) { if (result == null) { diff --git a/Arrowgene.Ddon.LoginServer/DdonLoginServer.cs b/Arrowgene.Ddon.LoginServer/DdonLoginServer.cs index 6dd09507..e9769b6a 100644 --- a/Arrowgene.Ddon.LoginServer/DdonLoginServer.cs +++ b/Arrowgene.Ddon.LoginServer/DdonLoginServer.cs @@ -26,6 +26,7 @@ using Arrowgene.Ddon.LoginServer.Handler; using Arrowgene.Ddon.Server; using Arrowgene.Ddon.Server.Handler; using Arrowgene.Ddon.Server.Network; +using Arrowgene.Ddon.Server.Scripting.interfaces; using Arrowgene.Ddon.Shared; using Arrowgene.Ddon.Shared.Network; using Arrowgene.Logging; diff --git a/Arrowgene.Ddon.LoginServer/Handler/GetLoginSettingHandler.cs b/Arrowgene.Ddon.LoginServer/Handler/GetLoginSettingHandler.cs index fda61b6b..6d91efd5 100644 --- a/Arrowgene.Ddon.LoginServer/Handler/GetLoginSettingHandler.cs +++ b/Arrowgene.Ddon.LoginServer/Handler/GetLoginSettingHandler.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using Arrowgene.Ddon.Server; using Arrowgene.Ddon.Server.Network; +using Arrowgene.Ddon.Server.Scripting.interfaces; using Arrowgene.Ddon.Shared.Entity.PacketStructure; using Arrowgene.Ddon.Shared.Entity.Structure; using Arrowgene.Ddon.Shared.Network; diff --git a/Arrowgene.Ddon.Server/Arrowgene.Ddon.Server.csproj b/Arrowgene.Ddon.Server/Arrowgene.Ddon.Server.csproj index b3241f51..69edc55d 100644 --- a/Arrowgene.Ddon.Server/Arrowgene.Ddon.Server.csproj +++ b/Arrowgene.Ddon.Server/Arrowgene.Ddon.Server.csproj @@ -25,5 +25,8 @@ + + + diff --git a/Arrowgene.Ddon.Server/GameLogicSetting.cs b/Arrowgene.Ddon.Server/GameLogicSetting.cs deleted file mode 100644 index 6ff99084..00000000 --- a/Arrowgene.Ddon.Server/GameLogicSetting.cs +++ /dev/null @@ -1,325 +0,0 @@ -using Arrowgene.Ddon.Shared.Model; -using System; -using System.Collections.Generic; -using System.Runtime.Serialization; - -namespace Arrowgene.Ddon.Server -{ - public class GameLogicSetting - { - /// - /// Additional factor to change how long crafting a recipe will take to finish. - /// - public double AdditionalProductionSpeedFactor { get; set; } - - /// - /// Additional factor to change how much a recipe will cost. - /// - public double AdditionalCostPerformanceFactor { get; set; } - - /// - /// Sets the maximim level that the exp ring will reward a bonus. - /// - public uint RookiesRingMaxLevel { get; set; } - - /// - /// The multiplier applied to the bonus amount of exp rewarded. - /// Must be a non-negtive value. If it is less than 0.0, a default of 1.0 - /// will be selected. - /// - public double RookiesRingBonus { get; set; } - - /// - /// Controls whether to pass lobby context packets on demand or only on entry to the server. - /// True = Server entry only. Lower packet load, but also causes invisible people in lobbies. - /// False = On-demand. May cause performance issues due to packet load. - /// - public bool NaiveLobbyContextHandling { get; set; } - - /// - /// Determines the maximum amount of consumable items that can be crafted in one go with a pawn. - /// The default is a value of 10 which is equivalent to the original game's behavior. - /// - public byte CraftConsumableProductionTimesMax { get; set; } - - /// - /// Configures if party exp is adjusted based on level differences of members. - /// - public bool EnableAdjustPartyEnemyExp { get; set; } - - /// - /// List of the inclusive ranges of (MinLv, Maxlv, ExpMultiplier). ExpMultiplier is a value - /// from (0.0 - 1.0) which is multipled into the base exp amount to determine the adjusted exp. - /// The minlv and maxlv determine the relative level range that this multiplier should be applied to. - /// - public List<(uint MinLv, uint MaxLv, double ExpMultiplier)> AdjustPartyEnemyExpTiers { get; set; } - - /// - /// Configures if exp is adjusted based on level differences of members vs target level. - /// - public bool EnableAdjustTargetLvEnemyExp { get; set; } - - /// - /// List of the inclusive ranges of (MinLv, Maxlv, ExpMultiplier). ExpMultiplier is a value from - /// (0.0 - 1.0) which is multipled into the base exp amount to determine the adjusted exp. - /// The minlv and maxlv determine the relative level range that this multiplier should be applied to. - /// - public List<(uint MinLv, uint MaxLv, double ExpMultiplier)> AdjustTargetLvEnemyExpTiers { get; set; } - - /// - /// The number of real world minutes that make up an in-game day. - /// - public uint GameClockTimescale { get; set; } - - /// - /// Use a poisson process to randomly generate a weather cycle containing this many events, using the statistics in WeatherStatistics. - /// - public uint WeatherSequenceLength { get; set; } - - /// - /// Statistics that drive semirandom weather generation. List is expected to be in (Fair, Cloudy, Rainy) order. - /// meanLength: Average length of the weather, in seconds, when it gets rolled. - /// weight: Relative weight of rolling that weather. Set to 0 to disable. - /// - public List<(uint MeanLength, uint Weight)> WeatherStatistics { get; set; } - - /// - /// Configures if the Pawn Exp Catchup mechanic is enabled. This mechanic still rewards the player pawn EXP when the pawn is outside - /// the allowed level range and a lower level than the owner. - /// - public bool EnablePawnCatchup { get; set; } - - /// - /// If the flag EnablePawnCatchup=true, this is the multiplier value used when calculating exp to catch the pawns level back up to the player. - /// - public double PawnCatchupMultiplier { get; set; } - - /// - /// If the flag EnablePawnCatchup=true, this is the range of level that the pawn falls behind the player before the catchup mechanic kicks in. - /// - public uint PawnCatchupLvDiff { get; set; } - - /// - /// Configures the default time in seconds a latern is active after igniting it. - /// - public uint LaternBurnTimeInSeconds { get; set; } - - /// - /// Maximum amount of play points the client will display in the UI. - /// Play points past this point will also trigger a chat log message saying you've reached the cap. - /// - public uint PlayPointMax { get; set; } - - /// - /// Maximum level for each job. - /// Shared with the login server. - /// - public uint JobLevelMax { get; set; } - - /// - /// Maximum number of members in a single clan. - /// Shared with the login server. - /// - public uint ClanMemberMax { get; set; } - - /// - /// Maximum number of characters per account. - /// Shared with the login server. - /// - public byte CharacterNumMax { get; set; } - - /// - /// Toggles the visual equip set for all characters. - /// Shared with the login server. - /// - public bool EnableVisualEquip { get; set; } - - /// - /// Maximum entries in the friends list. - /// Shared with the login server. - /// - public uint FriendListMax { get; set; } - - /// - /// Limits for each wallet type. - /// - public Dictionary WalletLimits { get; set; } - - /// - /// Number of bazaar entries that are given to new characters. - /// - public uint DefaultMaxBazaarExhibits { get; set; } - - /// - /// Number of favorite warps that are given to new characters. - /// - public uint DefaultWarpFavorites { get; set; } - - /// - /// Disables the exp correction if all party members are owned by the same character. - /// - public bool DisableExpCorrectionForMyPawn { get; set; } - - /// - /// Global modifier for enemy exp calculations to scale up or down. - /// - public double EnemyExpModifier { get; set; } - - /// - /// Global modifier for quest exp calculations to scale up or down. - /// - public double QuestExpModifier { get; set; } - - /// - /// Global modifier for pp calculations to scale up or down. - /// - public double PpModifier { get; set; } - - /// - /// Global modifier for Gold calculations to scale up or down. - /// - public double GoldModifier { get; set; } - - /// - /// Global modifier for Rift calculations to scale up or down. - /// - public double RiftModifier { get; set; } - - /// - /// Global modifier for BO calculations to scale up or down. - /// - public double BoModifier { get; set; } - - /// - /// Global modifier for HO calculations to scale up or down. - /// - public double HoModifier { get; set; } - - /// - /// Global modifier for JP calculations to scale up or down. - /// - public double JpModifier { get; set; } - - /// - /// Configures the maximum amount of reward box slots. - /// - public byte RewardBoxMax { get; set; } - - /// - /// Configures the maximum amount of quests that can be ordered at one time. - /// - public byte QuestOrderMax { get; set; } - - /// - /// Configures if epitaph rewards are limited once per weekly reset. - /// - public bool EnableEpitaphWeeklyRewards { get; set; } - - /// - /// Enables main pawns in party to gain EXP and JP from quests - /// Original game apparantly did not have pawns share quest reward, so will set to false for default, - /// change as needed - /// - public bool EnableMainPartyPawnsQuestRewards { get; set; } - - /// - /// Specifies the time in seconds that a bazaar exhibit will last. - /// By default, the equivalent of 3 days - /// - public ulong BazaarExhibitionTimeSeconds { get; set; } - - /// - /// Specifies the time in seconds that a slot in the bazaar won't be able to be used again. - /// By default, the equivalent of 1 day - /// - public ulong BazaarCooldownTimeSeconds { get; set; } - - /// - /// Various URLs used by the client. - /// Shared with the login server. - /// - public string UrlManual { get; set; } - public string UrlShopDetail { get; set; } - public string UrlShopCounterA { get; set; } - public string UrlShopAttention { get; set; } - public string UrlShopStoneLimit { get; set; } - public string UrlShopCounterB { get; set; } - public string UrlChargeCallback { get; set; } - public string UrlChargeA { get; set; } - public string UrlSample9 { get; set; } - public string UrlSample10 { get; set; } - public string UrlCampaignBanner { get; set; } - public string UrlSupportIndex { get; set; } - public string UrlPhotoupAuthorize { get; set; } - public string UrlApiA { get; set; } - public string UrlApiB { get; set; } - public string UrlIndex { get; set; } - public string UrlCampaign { get; set; } - public string UrlChargeB { get; set; } - public string UrlCompanionImage { get; set; } - - public GameLogicSetting() - { - } - - void ValidateSettings() - { - if (RookiesRingBonus < 0) - { - RookiesRingBonus = 1.0; - } - if (AdditionalProductionSpeedFactor < 0) - { - CraftConsumableProductionTimesMax = 1; - } - if (AdditionalCostPerformanceFactor < 0) - { - CraftConsumableProductionTimesMax = 1; - } - if (CraftConsumableProductionTimesMax < 1) - { - CraftConsumableProductionTimesMax = 10; - } - if (GameClockTimescale <= 0) - { - GameClockTimescale = 90; - } - if (PawnCatchupMultiplier < 0) - { - PawnCatchupMultiplier = 1.0; - } - if (EnemyExpModifier < 0) - { - EnemyExpModifier = 1.0; - } - if (QuestExpModifier < 0) - { - QuestExpModifier = 1.0; - } - if (PpModifier < 0) - { - PpModifier = 1.0; - } - if (GoldModifier < 0) - { - GoldModifier = 1.0; - } - if (RiftModifier < 0) - { - RiftModifier = 1.0; - } - if (BoModifier < 0) - { - BoModifier = 1.0; - } - if (HoModifier < 0) - { - HoModifier = 1.0; - } - if (JpModifier < 0) - { - JpModifier = 1.0; - } - } - } -} diff --git a/Arrowgene.Ddon.Server/ScriptedServerSettings.cs b/Arrowgene.Ddon.Server/ScriptedServerSettings.cs deleted file mode 100644 index 11119c17..00000000 --- a/Arrowgene.Ddon.Server/ScriptedServerSettings.cs +++ /dev/null @@ -1,145 +0,0 @@ -using Arrowgene.Ddon.Shared; -using Arrowgene.Ddon.Shared.Model; -using Arrowgene.Logging; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Scripting; -using Microsoft.CodeAnalysis.Scripting; -using System; -using System.Collections.Generic; -using System.IO; - -namespace Arrowgene.Ddon.Server -{ - public class ScriptedServerSettings - { - private static readonly ServerLogger Logger = LogProvider.Logger(typeof(ScriptedServerSettings)); - - public class Globals - { - public GameLogicSetting GameLogicSetting { get; set; } - } - - private string ScriptsRoot { get; set; } - private Dictionary CompiledScripts; - public GameLogicSetting GameLogicSetting { get; private set; } - public FileSystemWatcher Watcher { get; private set; } - - public Script GameLogicSettings - { - get - { - lock (CompiledScripts) - { - return CompiledScripts["GameLogicSettings"]; - } - } - set - { - lock (CompiledScripts) - { - CompiledScripts["GameLogicSettings"] = value; - } - } - } - - public ScriptedServerSettings(string AssetsPath) - { - ScriptsRoot = $"{AssetsPath}\\scripts"; - - GameLogicSetting = new GameLogicSetting(); - - CompiledScripts = new Dictionary(); - - var ScriptsDirectory = new DirectoryInfo(ScriptsRoot); - if (!ScriptsDirectory.Exists) - { - return; - } - - Watcher = SetupFileWatcher(); - } - - public void LoadSettings() - { - string settingsPath = $"{ScriptsRoot}\\GameLogicSettings.csx"; - - var options = ScriptOptions.Default - .AddReferences(MetadataReference.CreateFromFile(typeof(GameLogicSetting).Assembly.Location)) - .AddReferences(MetadataReference.CreateFromFile(typeof(WalletType).Assembly.Location)) - .AddImports("System", "System.Collections", "System.Collections.Generic") - .AddImports("Arrowgene.Ddon.Shared.Model") - .AddImports("Arrowgene.Ddon.Shared.Model.Quest"); - - Globals globals = new Globals() - { - GameLogicSetting = GameLogicSetting - }; - - Logger.Info($"Loading Scriptable game settings from {ScriptsRoot}"); - Logger.Info($"{settingsPath}"); - - var code = Util.ReadAllText(settingsPath); - - // Load The Game Settings - GameLogicSettings = CSharpScript.Create( - code: code, - options: options, - globalsType: typeof(Globals) - ); - - if (GameLogicSettings != null) - { - // Execute the script file to populate the settings - GameLogicSettings.RunAsync(globals); - } - } - - private FileSystemWatcher SetupFileWatcher() - { - var watcher = new FileSystemWatcher(ScriptsRoot); - watcher.Filter = "GameLogicSettings.csx"; - - watcher.NotifyFilter = (NotifyFilters.LastWrite); - - watcher.Changed += OnChanged; - watcher.Error += OnError; - watcher.EnableRaisingEvents = true; - - return watcher; - } - - private void OnChanged(object sender, FileSystemEventArgs e) - { - if (e.ChangeType != WatcherChangeTypes.Changed) - { - return; - } - - Logger.Info($"Reloading {e.FullPath}"); - - try - { - Watcher.EnableRaisingEvents = false; - - LoadSettings(); - } - finally - { - Watcher.EnableRaisingEvents = true; - } - } - - private void OnError(object sender, ErrorEventArgs e) => - PrintException(e.GetException()); - - private void PrintException(Exception ex) - { - if (ex != null) - { - Logger.Error($"{ex.Message}"); - Logger.Error($"Stacktrace:"); - PrintException(ex.InnerException); - } - } - } -} diff --git a/Arrowgene.Ddon.GameServer/Scripting/ScriptManager.cs b/Arrowgene.Ddon.Server/Scripting/ScriptManager.cs similarity index 74% rename from Arrowgene.Ddon.GameServer/Scripting/ScriptManager.cs rename to Arrowgene.Ddon.Server/Scripting/ScriptManager.cs index 343ce266..a61b5f9e 100644 --- a/Arrowgene.Ddon.GameServer/Scripting/ScriptManager.cs +++ b/Arrowgene.Ddon.Server/Scripting/ScriptManager.cs @@ -1,55 +1,40 @@ +using Arrowgene.Ddon.GameServer.Scripting; using Arrowgene.Ddon.Server; -using Arrowgene.Ddon.Shared; using Arrowgene.Logging; using Microsoft.CodeAnalysis.CSharp.Scripting; +using Microsoft.CodeAnalysis.CSharp.Syntax; using System; using System.Collections.Generic; using System.IO; -using System.Linq; +using static Arrowgene.Ddon.Server.ServerScriptManager; -namespace Arrowgene.Ddon.GameServer.Scripting +namespace Arrowgene.Ddon.Shared.Scripting { - public class ScriptManager + public abstract class ScriptManager { - private static readonly ServerLogger Logger = LogProvider.Logger(typeof(ScriptManager)); - public class GlobalVariables + private static readonly ServerLogger Logger = LogProvider.Logger(typeof(ScriptManager)); + + protected Dictionary ScriptModules { get; private set; } + public string ScriptsRoot { get; private set; } + public T GlobalVariables { get; protected set; } + + public ScriptManager(string assetsPath) { - public GlobalVariables(DdonGameServer server) - { - Server = server; - } - - public DdonGameServer Server { get; } - }; - - public NpcExtendedFacilityModule NpcExtendedFacilityModule { get; private set; } = new NpcExtendedFacilityModule(); - - private Dictionary ScriptModules; - - public ScriptManager(DdonGameServer server) - { - Server = server; - ScriptsRoot = $"{server.AssetRepository.AssetsPath}\\scripts"; - - ScriptModules = new Dictionary() - { - {NpcExtendedFacilityModule.ModuleRoot, NpcExtendedFacilityModule} - }; - - Globals = new GlobalVariables(Server); + ScriptModules = new Dictionary(); + ScriptsRoot = $"{assetsPath}\\scripts"; } - private DdonGameServer Server { get; } - private string ScriptsRoot { get; } - private GlobalVariables Globals { get; } + public abstract void Initialize(); - public void Initialize() + protected void Initialize(T globalVariables) { + GlobalVariables = globalVariables; + CompileScripts(); SetupFileWatchers(); } - private void CompileScript(ScriptModule module, string path) + protected void CompileScript(ScriptModule module, string path) { try { @@ -58,11 +43,11 @@ namespace Arrowgene.Ddon.GameServer.Scripting var script = CSharpScript.Create( code: Util.ReadAllText(path), options: module.Options(), - globalsType: typeof(GlobalVariables) + globalsType: typeof(T) ); - var result = script.RunAsync(Globals).Result; - if (!module.EvaluateResult(result)) + var result = script.RunAsync(GlobalVariables).Result; + if (!module.EvaluateResult(path, result)) { Logger.Error($"Failed to evaluate the result of executing '{path}'"); } @@ -74,7 +59,7 @@ namespace Arrowgene.Ddon.GameServer.Scripting } } - private void CompileScripts() + protected void CompileScripts() { foreach (var module in ScriptModules.Values) { @@ -83,6 +68,11 @@ namespace Arrowgene.Ddon.GameServer.Scripting Logger.Info($"Compiling scripts for module '{module.ModuleRoot}'"); foreach (var file in Directory.EnumerateFiles(path)) { + if (Path.GetExtension(file) != ".csx") + { + continue; + } + module.Scripts.Add(file); CompileScript(module, file); } @@ -93,6 +83,11 @@ namespace Arrowgene.Ddon.GameServer.Scripting { foreach (var module in ScriptModules.Values) { + if (!module.EnableHotLoad) + { + continue; + } + var watcher = new FileSystemWatcher($"{ScriptsRoot}\\{module.ModuleRoot}"); watcher.Filter = module.Filter; watcher.NotifyFilter = (NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.CreationTime); @@ -108,11 +103,13 @@ namespace Arrowgene.Ddon.GameServer.Scripting // Enable all the watchers foreach (var module in ScriptModules.Values) { - module.Watcher.EnableRaisingEvents = true; + if (module.EnableHotLoad) + { + module.Watcher.EnableRaisingEvents = true; + } } } - - private void OnChanged(object sender, FileSystemEventArgs e) + protected void OnChanged(object sender, FileSystemEventArgs e) { if (e.ChangeType != WatcherChangeTypes.Changed) { diff --git a/Arrowgene.Ddon.GameServer/Scripting/ScriptModule.cs b/Arrowgene.Ddon.Server/Scripting/ScriptModule.cs similarity index 61% rename from Arrowgene.Ddon.GameServer/Scripting/ScriptModule.cs rename to Arrowgene.Ddon.Server/Scripting/ScriptModule.cs index f37f24ee..a0484bfa 100644 --- a/Arrowgene.Ddon.GameServer/Scripting/ScriptModule.cs +++ b/Arrowgene.Ddon.Server/Scripting/ScriptModule.cs @@ -11,6 +11,13 @@ namespace Arrowgene.Ddon.GameServer.Scripting public abstract bool ScanSubdirectories { get; } public FileSystemWatcher Watcher { get; set; } + /// + /// Determines if this module is able to be hot-loadable or not. + /// If a module is not hot-loadable, it will only be evaluated once + /// when the server first loads. + /// + public abstract bool EnableHotLoad { get; } + public HashSet Scripts { get; set; } public ScriptModule() @@ -27,8 +34,9 @@ namespace Arrowgene.Ddon.GameServer.Scripting /// /// Evaluates the result returned by the script. /// - /// + /// Path to the script that was executed + /// The result object of the script that executed /// - public abstract bool EvaluateResult(ScriptState result); + public abstract bool EvaluateResult(string path, ScriptState result); } } diff --git a/Arrowgene.Ddon.GameServer/Scripting/ScriptUtils.cs b/Arrowgene.Ddon.Server/Scripting/ScriptUtils.cs similarity index 92% rename from Arrowgene.Ddon.GameServer/Scripting/ScriptUtils.cs rename to Arrowgene.Ddon.Server/Scripting/ScriptUtils.cs index 1634879c..3635c9e6 100644 --- a/Arrowgene.Ddon.GameServer/Scripting/ScriptUtils.cs +++ b/Arrowgene.Ddon.Server/Scripting/ScriptUtils.cs @@ -2,8 +2,6 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Arrowgene.Ddon.GameServer.Scripting { diff --git a/Arrowgene.Ddon.Server/Scripting/interfaces/GameLogicSetting.cs b/Arrowgene.Ddon.Server/Scripting/interfaces/GameLogicSetting.cs new file mode 100644 index 00000000..6d72a861 --- /dev/null +++ b/Arrowgene.Ddon.Server/Scripting/interfaces/GameLogicSetting.cs @@ -0,0 +1,649 @@ +using Arrowgene.Ddon.Server.Scripting.utils; +using Arrowgene.Ddon.Shared.Model; +using System.Collections.Generic; + +namespace Arrowgene.Ddon.Server.Scripting.interfaces +{ + public class GameLogicSetting + { + private ScriptableSettings SettingsData { get; set; } + public GameLogicSetting(ScriptableSettings settingsData) + { + SettingsData = settingsData; + } + + private T GetSetting(string key) + { + return SettingsData.Get("GameLogicSettings", key); + } + + /// + /// Additional factor to change how long crafting a recipe will take to finish. + /// + public double AdditionalProductionSpeedFactor + { + get + { + return GetSetting("AdditionalProductionSpeedFactor"); + } + } + + /// + /// Additional factor to change how much a recipe will cost. + /// + public double AdditionalCostPerformanceFactor + { + get + { + return GetSetting("AdditionalCostPerformanceFactor"); + } + } + + /// + /// Sets the maximim level that the exp ring will reward a bonus. + /// + public uint RookiesRingMaxLevel + { + get + { + return GetSetting("RookiesRingMaxLevel"); + } + } + + /// + /// The multiplier applied to the bonus amount of exp rewarded. + /// Must be a non-negtive value. If it is less than 0.0, a default of 1.0 + /// will be selected. + /// + public double RookiesRingBonus + { + get + { + return GetSetting("RookiesRingBonus"); + } + } + + /// + /// Controls whether to pass lobby context packets on demand or only on entry to the server. + /// True = Server entry only. Lower packet load, but also causes invisible people in lobbies. + /// False = On-demand. May cause performance issues due to packet load. + /// + public bool NaiveLobbyContextHandling + { + get + { + return GetSetting("NaiveLobbyContextHandling"); + } + } + + /// + /// Determines the maximum amount of consumable items that can be crafted in one go with a pawn. + /// The default is a value of 10 which is equivalent to the original game's behavior. + /// + public byte CraftConsumableProductionTimesMax + { + get + { + return GetSetting("CraftConsumableProductionTimesMax"); + } + } + + /// + /// Configures if party exp is adjusted based on level differences of members. + /// + public bool EnableAdjustPartyEnemyExp + { + get + { + return GetSetting("EnableAdjustPartyEnemyExp"); + } + } + + /// + /// List of the inclusive ranges of (MinLv, Maxlv, ExpMultiplier). ExpMultiplier is a value + /// from (0.0 - 1.0) which is multipled into the base exp amount to determine the adjusted exp. + /// The minlv and maxlv determine the relative level range that this multiplier should be applied to. + /// + public List<(uint MinLv, uint MaxLv, double ExpMultiplier)> AdjustPartyEnemyExpTiers + { + get + { + return GetSetting>("AdjustPartyEnemyExpTiers"); + } + } + + /// + /// Configures if exp is adjusted based on level differences of members vs target level. + /// + public bool EnableAdjustTargetLvEnemyExp + { + get + { + return GetSetting("EnableAdjustTargetLvEnemyExp"); + } + } + + /// + /// List of the inclusive ranges of (MinLv, Maxlv, ExpMultiplier). ExpMultiplier is a value from + /// (0.0 - 1.0) which is multipled into the base exp amount to determine the adjusted exp. + /// The minlv and maxlv determine the relative level range that this multiplier should be applied to. + /// + public List<(uint MinLv, uint MaxLv, double ExpMultiplier)> AdjustTargetLvEnemyExpTiers + { + get + { + return GetSetting>("AdjustTargetLvEnemyExpTiers"); + } + } + + /// + /// The number of real world minutes that make up an in-game day. + /// + public uint GameClockTimescale + { + get + { + return GetSetting("GameClockTimescale"); + } + } + + /// + /// Use a poisson process to randomly generate a weather cycle containing this many events, using the statistics in WeatherStatistics. + /// + public uint WeatherSequenceLength + { + get + { + return GetSetting("WeatherSequenceLength"); + } + } + + /// + /// Statistics that drive semirandom weather generation. List is expected to be in (Fair, Cloudy, Rainy) order. + /// meanLength: Average length of the weather, in seconds, when it gets rolled. + /// weight: Relative weight of rolling that weather. Set to 0 to disable. + /// + public List<(uint MeanLength, uint Weight)> WeatherStatistics + { + get + { + return GetSetting>("WeatherStatistics"); + } + } + + /// + /// Configures if the Pawn Exp Catchup mechanic is enabled. This mechanic still rewards the player pawn EXP when the pawn is outside + /// the allowed level range and a lower level than the owner. + /// + public bool EnablePawnCatchup + { + get + { + return GetSetting("EnablePawnCatchup"); + } + } + + /// + /// If the flag EnablePawnCatchup=true, this is the multiplier value used when calculating exp to catch the pawns level back up to the player. + /// + public double PawnCatchupMultiplier + { + get + { + return GetSetting("PawnCatchupMultiplier"); + } + } + + /// + /// If the flag EnablePawnCatchup=true, this is the range of level that the pawn falls behind the player before the catchup mechanic kicks in. + /// + public uint PawnCatchupLvDiff + { + get + { + return GetSetting("PawnCatchupLvDiff"); + } + } + + /// + /// Configures the default time in seconds a latern is active after igniting it. + /// + public uint LaternBurnTimeInSeconds + { + get + { + return GetSetting("LaternBurnTimeInSeconds"); + } + } + + /// + /// Maximum amount of play points the client will display in the UI. + /// Play points past this point will also trigger a chat log message saying you've reached the cap. + /// + public uint PlayPointMax + { + get + { + return GetSetting("PlayPointMax"); + } + } + + /// + /// Maximum level for each job. + /// Shared with the login server. + /// + public uint JobLevelMax + { + get + { + return GetSetting("JobLevelMax"); + } + } + + /// + /// Maximum number of members in a single clan. + /// Shared with the login server. + /// + public uint ClanMemberMax + { + get + { + return GetSetting("ClanMemberMax"); + } + } + + /// + /// Maximum number of characters per account. + /// Shared with the login server. + /// + public byte CharacterNumMax + { + get + { + return GetSetting("CharacterNumMax"); + } + } + + /// + /// Toggles the visual equip set for all characters. + /// Shared with the login server. + /// + public bool EnableVisualEquip + { + get + { + return GetSetting("EnableVisualEquip"); + } + } + + /// + /// Maximum entries in the friends list. + /// Shared with the login server. + /// + public uint FriendListMax + { + get + { + return GetSetting("FriendListMax"); + } + } + + /// + /// Limits for each wallet type. + /// + public Dictionary WalletLimits + { + get + { + return GetSetting>("WalletLimits"); + } + } + + /// + /// Number of bazaar entries that are given to new characters. + /// + public uint DefaultMaxBazaarExhibits + { + get + { + return GetSetting("DefaultMaxBazaarExhibits"); + } + } + + /// + /// Number of favorite warps that are given to new characters. + /// + public uint DefaultWarpFavorites + { + get + { + return GetSetting("DefaultWarpFavorites"); + } + } + + /// + /// Disables the exp correction if all party members are owned by the same character. + /// + public bool DisableExpCorrectionForMyPawn + { + get + { + return GetSetting("DisableExpCorrectionForMyPawn"); + } + } + + /// + /// Global modifier for enemy exp calculations to scale up or down. + /// + public double EnemyExpModifier + { + get + { + return GetSetting("EnemyExpModifier"); + } + } + + /// + /// Global modifier for quest exp calculations to scale up or down. + /// + public double QuestExpModifier + { + get + { + return GetSetting("QuestExpModifier"); + } + } + + /// + /// Global modifier for pp calculations to scale up or down. + /// + public double PpModifier + { + get + { + return GetSetting("PpModifier"); + } + } + + /// + /// Global modifier for Gold calculations to scale up or down. + /// + public double GoldModifier + { + get + { + return GetSetting("GoldModifier"); + } + } + + /// + /// Global modifier for Rift calculations to scale up or down. + /// + public double RiftModifier + { + get + { + return GetSetting("RiftModifier"); + } + } + + /// + /// Global modifier for BO calculations to scale up or down. + /// + public double BoModifier + { + get + { + return GetSetting("BoModifier"); + } + } + + /// + /// Global modifier for HO calculations to scale up or down. + /// + public double HoModifier + { + get + { + return GetSetting("HoModifier"); + } + } + + /// + /// Global modifier for JP calculations to scale up or down. + /// + public double JpModifier + { + get + { + return GetSetting("JpModifier"); + } + } + + /// + /// Configures the maximum amount of reward box slots. + /// + public byte RewardBoxMax + { + get + { + return GetSetting("RewardBoxMax"); + } + } + + /// + /// Configures the maximum amount of quests that can be ordered at one time. + /// + public byte QuestOrderMax + { + get + { + return GetSetting("QuestOrderMax"); + } + } + + /// + /// Configures if epitaph rewards are limited once per weekly reset. + /// + public bool EnableEpitaphWeeklyRewards + { + get + { + return GetSetting("EnableEpitaphWeeklyRewards"); + } + } + + /// + /// Enables main pawns in party to gain EXP and JP from quests + /// Original game apparantly did not have pawns share quest reward, so will set to false for default, + /// change as needed + /// + public bool EnableMainPartyPawnsQuestRewards + { + get + { + return GetSetting("EnableMainPartyPawnsQuestRewards"); + } + } + + /// + /// Specifies the time in seconds that a bazaar exhibit will last. + /// By default, the equivalent of 3 days + /// + public ulong BazaarExhibitionTimeSeconds + { + get + { + return GetSetting("BazaarExhibitionTimeSeconds"); + } + } + + /// + /// Specifies the time in seconds that a slot in the bazaar won't be able to be used again. + /// By default, the equivalent of 1 day + /// + public ulong BazaarCooldownTimeSeconds + { + get + { + return GetSetting("BazaarCooldownTimeSeconds"); + } + } + + /// + /// Various URLs used by the client. + /// Shared with the login server. + /// + public string UrlManual + { + get + { + return GetSetting("UrlManual"); + } + } + + public string UrlShopDetail + { + get + { + return GetSetting("UrlShopDetail"); + } + } + + public string UrlShopCounterA + { + get + { + return GetSetting("UrlShopCounterA"); + } + } + + public string UrlShopAttention + { + get + { + return GetSetting("UrlShopAttention"); + } + } + + public string UrlShopStoneLimit + { + get + { + return GetSetting("UrlShopStoneLimit"); + } + } + + public string UrlShopCounterB + { + get + { + return GetSetting("UrlShopCounterB"); + } + } + + public string UrlChargeCallback + { + get + { + return GetSetting("UrlChargeCallback"); + } + } + + public string UrlChargeA + { + get + { + return GetSetting("UrlChargeA"); + } + } + + public string UrlSample9 + { + get + { + return GetSetting("UrlSample9"); + } + } + + public string UrlSample10 + { + get + { + return GetSetting("UrlSample10"); + } + } + + public string UrlCampaignBanner + { + get + { + return GetSetting("UrlCampaignBanner"); + } + } + + public string UrlSupportIndex + { + get + { + return GetSetting("UrlSupportIndex"); + } + } + + public string UrlPhotoupAuthorize + { + get + { + return GetSetting("UrlPhotoupAuthorize"); + } + } + + public string UrlApiA + { + get + { + return GetSetting("UrlApiA"); + } + } + + public string UrlApiB + { + get + { + return GetSetting("UrlApiB"); + } + } + + public string UrlIndex + { + get + { + return GetSetting("UrlIndex"); + } + } + + public string UrlCampaign + { + get + { + return GetSetting("UrlCampaign"); + } + } + + public string UrlChargeB + { + get + { + return GetSetting("UrlChargeB"); + } + } + + public string UrlCompanionImage + { + get + { + return GetSetting("UrlCompanionImage"); + } + } + } +} diff --git a/Arrowgene.Ddon.Server/Scripting/modules/GameServerSettingsModule.cs b/Arrowgene.Ddon.Server/Scripting/modules/GameServerSettingsModule.cs new file mode 100644 index 00000000..01b01fca --- /dev/null +++ b/Arrowgene.Ddon.Server/Scripting/modules/GameServerSettingsModule.cs @@ -0,0 +1,52 @@ +using Arrowgene.Ddon.GameServer.Scripting; +using Arrowgene.Ddon.Server.Scripting.interfaces; +using Arrowgene.Ddon.Server.Scripting.utils; +using Arrowgene.Ddon.Shared.Model; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Scripting; +using System.IO; + +namespace Arrowgene.Ddon.Server.Scripting.modules +{ + public class GameServerSettingsModule : ScriptModule + { + public override string ModuleRoot => "settings"; + public override string Filter => "*.csx"; + public override bool ScanSubdirectories => true; + public override bool EnableHotLoad => true; + + /// + /// Settings data is organized as ScriptName.FieldName + /// + private ScriptableSettings SettingsData { get; set; } + public GameLogicSetting GameLogicSetting { get; private set; } + + public GameServerSettingsModule() + { + SettingsData = new ScriptableSettings(); + GameLogicSetting = new GameLogicSetting(SettingsData); + } + + public override ScriptOptions Options() + { + return ScriptOptions.Default + .AddReferences(MetadataReference.CreateFromFile(typeof(GameLogicSetting).Assembly.Location)) + .AddReferences(MetadataReference.CreateFromFile(typeof(WalletType).Assembly.Location)) + .AddImports("System", "System.Collections", "System.Collections.Generic") + .AddImports("Arrowgene.Ddon.Shared.Model") + .AddImports("Arrowgene.Ddon.Shared.Model.Quest"); + } + + public override bool EvaluateResult(string path, ScriptState result) + { + var scriptName = Path.GetFileNameWithoutExtension(path); + + foreach (var variable in result.Variables) + { + SettingsData.Set(scriptName, variable.Name, variable.Value); + } + + return true; + } + } +} diff --git a/Arrowgene.Ddon.Server/Scripting/utils/ScriptableSettings.cs b/Arrowgene.Ddon.Server/Scripting/utils/ScriptableSettings.cs new file mode 100644 index 00000000..4e3be7e7 --- /dev/null +++ b/Arrowgene.Ddon.Server/Scripting/utils/ScriptableSettings.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; + +namespace Arrowgene.Ddon.Server.Scripting.utils +{ + public class ScriptableSettings + { + /// + /// Settings data is organized as ScriptName.FieldName + /// + private Dictionary> Data { get; set; } + + public ScriptableSettings() + { + Data = new Dictionary>(); + } + + public Dictionary GetScriptData(string scriptName) + { + if (!Data.ContainsKey(scriptName)) + { + throw new Exception($"The script '{scriptName}' doesn't exist"); + } + + return Data[scriptName]; + } + + public T Get(string scriptName, string variableName) + { + if (!Data.ContainsKey(scriptName)) + { + throw new Exception($"The script '{scriptName}' doesn't exist"); + } + + if (!Data[scriptName].ContainsKey(variableName)) + { + throw new Exception($"The setting '{scriptName}.{variableName}' doesn't exist"); + } + return (T)Data[scriptName][variableName]; + } + + public void Set(string scriptName, string variableName, T value) + { + if (!Data.ContainsKey(scriptName)) + { + Data[scriptName] = new Dictionary(); + } + + Data[scriptName][variableName] = value; + } + } +} diff --git a/Arrowgene.Ddon.Server/ServerScriptManager.cs b/Arrowgene.Ddon.Server/ServerScriptManager.cs new file mode 100644 index 00000000..60b03ce8 --- /dev/null +++ b/Arrowgene.Ddon.Server/ServerScriptManager.cs @@ -0,0 +1,36 @@ +using Arrowgene.Ddon.Server.Scripting.interfaces; +using Arrowgene.Ddon.Server.Scripting.modules; +using Arrowgene.Ddon.Shared.Scripting; +using Arrowgene.Logging; + +namespace Arrowgene.Ddon.Server +{ + public class Globals + { + // Currently no script globals are required + // but leave this class as a way to introduce + // globals if needed. + } + + public class ServerScriptManager : ScriptManager + { + private static readonly ServerLogger Logger = LogProvider.Logger(typeof(ServerScriptManager)); + + private Globals Globals { get; set; } + + public GameServerSettingsModule GameServerSettings { get; private set; } = new GameServerSettingsModule(); + + public ServerScriptManager(string assetsPath) : base(assetsPath) + { + Globals = new Globals(); + + // Add modules to the list so the generic logic can iterate over all scripting modules + ScriptModules[GameServerSettings.ModuleRoot] = GameServerSettings; + } + + public override void Initialize() + { + base.Initialize(Globals); + } + } +} diff --git a/Arrowgene.Ddon.Shared/Arrowgene.Ddon.Shared.csproj b/Arrowgene.Ddon.Shared/Arrowgene.Ddon.Shared.csproj index 339cf536..4c85d409 100644 --- a/Arrowgene.Ddon.Shared/Arrowgene.Ddon.Shared.csproj +++ b/Arrowgene.Ddon.Shared/Arrowgene.Ddon.Shared.csproj @@ -38,25 +38,8 @@ - - - - - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - + + PreserveNewest + diff --git a/Arrowgene.Ddon.Shared/Files/Assets/scripts/GameLogicSettings.csx b/Arrowgene.Ddon.Shared/Files/Assets/scripts/GameLogicSettings.csx deleted file mode 100644 index 9f31d788..00000000 --- a/Arrowgene.Ddon.Shared/Files/Assets/scripts/GameLogicSettings.csx +++ /dev/null @@ -1,163 +0,0 @@ -/** - * Settings file for Server customization. - * This file supports hotloading. - */ - -// Generic Server Settings -GameLogicSetting.NaiveLobbyContextHandling = true; - -// Crafting Settings -GameLogicSetting.AdditionalProductionSpeedFactor = 1.0; -GameLogicSetting.AdditionalCostPerformanceFactor = 1.0; -GameLogicSetting.CraftConsumableProductionTimesMax = 10; - -// Exp Ring Settings -GameLogicSetting.RookiesRingMaxLevel = 89; -GameLogicSetting.RookiesRingBonus = 1.0; - -// EXP Penalty Settings - -/** - * @brief Handles EXP penalties for the party based on the - * difference between the lowest leveled member and highest - * leveled member of the party. If the range is larger than - * the last entry in AdjustPartyEnemyExpTiers, a 0% exp rate - * is automatically applied. - * - * Can be turned on/off by configuring AdjustPartyEnemyExp. - */ -GameLogicSetting.EnableAdjustPartyEnemyExp = true; -GameLogicSetting.AdjustPartyEnemyExpTiers = new List<(uint MinLv, uint MaxLv, double ExpMultiplier)>() -{ - // MinLv and MaxLv define the relative level difference between the levels of the lowest and - // highest members in the party. - // The ExpMultiplier value can be a value between [0.0, 1.0] (1.0 = 100%, 0.0 = 0%) - // - // MinLv, MaxLv, ExpMultiplier - ( 0, 2, 1.0), - ( 3, 4, 0.9), - ( 5, 6, 0.8), - ( 7, 8, 0.6), - ( 9, 10, 0.5), -}; - -/** - * @brief Handles EXP penalties based on the highest leveled member - * in the party and the level of the target enemy. If the range is - * larger than the last entry in AdjustTargetLvEnemyExpTiers, a 0% - * exp rate is automatically applied. - * - * Can be turned on/off by configuring AdjustTargetLvEnemyExp. - */ -GameLogicSetting.EnableAdjustTargetLvEnemyExp = false; -GameLogicSetting.AdjustTargetLvEnemyExpTiers = new List<(uint MinLv, uint MaxLv, double ExpMultiplier)>() -{ - // MinLv and MaxLv define the relative level difference between the target and highest member in the party. - // The ExpMultiplier value can be a value between [0.0, 1.0] (1.0 = 100%, 0.0 = 0%) - // - // MinLv, MaxLv, ExpMultiplier - ( 0, 2, 1.0), - ( 3, 4, 0.9), - ( 5, 6, 0.8), - ( 7, 8, 0.6), - ( 9, 10, 0.5), -}; - -// Pawn Catchup Settings -GameLogicSetting.EnablePawnCatchup = true; -GameLogicSetting.PawnCatchupMultiplier = 1.5; -GameLogicSetting.PawnCatchupLvDiff = 5; - -// Game Time Settings -GameLogicSetting.GameClockTimescale = 90; - -// Weather Settings -GameLogicSetting.WeatherSequenceLength = 20; -GameLogicSetting.WeatherStatistics = new List<(uint MeanLength, uint Weight)>() -{ - (60 * 30, 1), // Fair - (60 * 30, 1), // Cloudy - (60 * 30, 1), // Rainy -}; - -// Account Settings -GameLogicSetting.CharacterNumMax = 4; -GameLogicSetting.FriendListMax = 200; - -// Player Settings -GameLogicSetting.JobLevelMax = 120; -GameLogicSetting.EnableVisualEquip = true; -GameLogicSetting.DefaultWarpFavorites = 3; -GameLogicSetting.LaternBurnTimeInSeconds = 1500; - -// Pawn Settings -GameLogicSetting.EnableMainPartyPawnsQuestRewards = false; - -// Bazaar Settings -GameLogicSetting.DefaultMaxBazaarExhibits = 5; -GameLogicSetting.BazaarExhibitionTimeSeconds = (ulong) TimeSpan.FromDays(3).TotalSeconds; -GameLogicSetting.BazaarCooldownTimeSeconds = (ulong) TimeSpan.FromDays(1).TotalSeconds; - -// Clan Settings -GameLogicSetting.ClanMemberMax = 100; - -// Epitaph Settings -GameLogicSetting.EnableEpitaphWeeklyRewards = false; - -// Point Settings -GameLogicSetting.PlayPointMax = 2000; - -// Global Point Modifiers -GameLogicSetting.EnemyExpModifier = 1; -GameLogicSetting.QuestExpModifier = 1; -GameLogicSetting.PpModifier = 1; -GameLogicSetting.GoldModifier = 1; -GameLogicSetting.RiftModifier = 1; -GameLogicSetting.BoModifier = 1; -GameLogicSetting.HoModifier = 1; -GameLogicSetting.JpModifier = 1; -GameLogicSetting.RewardBoxMax = 100; -GameLogicSetting.QuestOrderMax = 20; - -// Wallet Settings -GameLogicSetting.WalletLimits = new Dictionary() -{ - {WalletType.Gold, 999999999}, - {WalletType.RiftPoints, 999999999}, - {WalletType.BloodOrbs, 500000}, - {WalletType.SilverTickets, 999999999}, - {WalletType.GoldenGemstones, 99999}, - {WalletType.RentalPoints, 99999}, - {WalletType.ResetJobPoints, 99}, - {WalletType.ResetCraftSkills, 99}, - {WalletType.HighOrbs, 5000}, - {WalletType.DominionPoints, 999999999}, - {WalletType.AdventurePassPoints, 80}, - {WalletType.UnknownTickets, 999999999}, - {WalletType.BitterblackMazeResetTicket, 3}, - {WalletType.GoldenDragonMark, 30}, - {WalletType.SilverDragonMark, 150}, - {WalletType.RedDragonMark, 99999}, -}; - -// URL Settings -string urlDomain = $"http://localhost:{52099}"; -GameLogicSetting.UrlManual = $"{urlDomain}/manual_nfb/"; -GameLogicSetting.UrlShopDetail = $"{urlDomain}/shop/ingame/stone/detail"; -GameLogicSetting.UrlShopCounterA = $"{urlDomain}/shop/ingame/counter?"; -GameLogicSetting.UrlShopAttention = $"{urlDomain}/shop/ingame/attention?"; -GameLogicSetting.UrlShopStoneLimit = $"{urlDomain}/shop/ingame/stone/limit"; -GameLogicSetting.UrlShopCounterB = $"{urlDomain}/shop/ingame/counter?"; -GameLogicSetting.UrlChargeCallback = $"{urlDomain}/opening/entry/ddo/cog_callback/charge"; -GameLogicSetting.UrlChargeA = $"{urlDomain}/sp_ingame/charge/"; -GameLogicSetting.UrlSample9 = "http://sample09.html"; -GameLogicSetting.UrlSample10 = "http://sample10.html"; -GameLogicSetting.UrlCampaignBanner = $"{urlDomain}/sp_ingame/campaign/bnr/bnr01.html?"; -GameLogicSetting.UrlSupportIndex = $"{urlDomain}/sp_ingame/support/index.html"; -GameLogicSetting.UrlPhotoupAuthorize = $"{urlDomain}/api/photoup/authorize"; -GameLogicSetting.UrlApiA = $"{urlDomain}/link/api"; -GameLogicSetting.UrlApiB = $"{urlDomain}/link/api"; -GameLogicSetting.UrlIndex = $"{urlDomain}/sp_ingame/link/index.html"; -GameLogicSetting.UrlCampaign = $"{urlDomain}/sp_ingame/campaign/bnr/slide.html"; -GameLogicSetting.UrlChargeB = $"{urlDomain}/sp_ingame/charge/"; -GameLogicSetting.UrlCompanionImage = $"{urlDomain}/"; diff --git a/Arrowgene.Ddon.Shared/Files/Assets/scripts/README.md b/Arrowgene.Ddon.Shared/Files/Assets/scripts/README.md new file mode 100644 index 00000000..c24f887c --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/scripts/README.md @@ -0,0 +1,12 @@ +# DDON Scripting + +The DDON scripting is intended to expose certain server internal details of the game server that a server admin wish to be configure. +While initially implemented as JSON file, as more complex features come into the picture, a more complex configuration archirecture +is required. + +The scripting root is `scripts` directory inside the assets directory. Internally the functions which perform reverse lookups for modules will +terminate once reaching the root. + +Each directory inside scripts defines the scripting module. A module name should be all lowercase. Inside each module, there should be a `README.md` +file which describes the purpose and usage of the module. It should also describe any guidelines required. When implementing a module, be aware if +you want the module to be hotloadable. If you do, make sure to program in such a way that the settings can reflect as such after an update. diff --git a/Arrowgene.Ddon.Shared/Files/Assets/scripts/extended_facilities/README.md b/Arrowgene.Ddon.Shared/Files/Assets/scripts/extended_facilities/README.md new file mode 100644 index 00000000..23f16c77 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/scripts/extended_facilities/README.md @@ -0,0 +1,10 @@ +# NPC Extended Facilities + +It is possible to inject new NPC options by defining what the game calls `NpcExtendedFacilities`. The type of menu options what can be added are defined in the class [NpcFunction.cs](https://github.com/sebastian-heinz/Arrowgene.DragonsDogmaOnline/blob/develop/Arrowgene.Ddon.Shared/Model/NpcFunction.cs). + +## Guidelines + +- Name the file after the named constant in [NpcId.cs](https://github.com/sebastian-heinz/Arrowgene.DragonsDogmaOnline/blob/develop/Arrowgene.Ddon.Shared/Model/NpcId.cs) +- The abstract class `INpcExtendedFacility` is used as the interface module. +- When extending `INpcExtendedFacility` keep it simple and name it `NpcExtendedFacility`. The script engine will mangle the object name this avoiding any sort of class name conflicts. +- At the bottom of the file return a new `NpcExtendedFacility`. \ No newline at end of file diff --git a/Arrowgene.Ddon.Shared/Files/Assets/scripts/settings/GameLogicSettings.csx b/Arrowgene.Ddon.Shared/Files/Assets/scripts/settings/GameLogicSettings.csx new file mode 100644 index 00000000..b370ce50 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/scripts/settings/GameLogicSettings.csx @@ -0,0 +1,159 @@ +/** + * Settings file for Server customization. + * This file supports hotloading. + */ + +// Generic Server Settings +bool NaiveLobbyContextHandling = true; +uint GameClockTimescale = 90; + +// Game Settings +byte RewardBoxMax = 100; +byte QuestOrderMax = 20; +byte CharacterNumMax = 4; +uint FriendListMax = 200; +uint JobLevelMax = 120; +bool EnableVisualEquip = true; +uint DefaultWarpFavorites = 3; +uint LaternBurnTimeInSeconds = 1500; + +// Crafting Settings +double AdditionalProductionSpeedFactor = 1.0; +double AdditionalCostPerformanceFactor = 1.0; +double CraftConsumableProductionTimesMax = 10; + +// Exp Ring Settings +bool EnableRookiesRing = false; +uint RookiesRingMaxLevel = 89; +double RookiesRingBonus = 1.0; + +// EXP Penalty Settings + +/** + * @brief Handles EXP penalties for the party based on the + * difference between the lowest leveled member and highest + * leveled member of the party. If the range is larger than + * the last entry in AdjustPartyEnemyExpTiers, a 0% exp rate + * is automatically applied. + * + * Can be turned on/off by configuring AdjustPartyEnemyExp. + */ +bool EnableAdjustPartyEnemyExp = true; +var AdjustPartyEnemyExpTiers = new List<(uint MinLv, uint MaxLv, double ExpMultiplier)>() +{ + // MinLv and MaxLv define the relative level difference between the levels of the lowest and + // highest members in the party. + // The ExpMultiplier value can be a value between [0.0, 1.0] (1.0 = 100%, 0.0 = 0%) + // + // MinLv, MaxLv, ExpMultiplier + ( 0, 2, 1.0), + ( 3, 4, 0.9), + ( 5, 6, 0.8), + ( 7, 8, 0.6), + ( 9, 10, 0.5), +}; + +/** + * @brief Handles EXP penalties based on the highest leveled member + * in the party and the level of the target enemy. If the range is + * larger than the last entry in AdjustTargetLvEnemyExpTiers, a 0% + * exp rate is automatically applied. + * + * Can be turned on/off by configuring AdjustTargetLvEnemyExp. + */ +bool EnableAdjustTargetLvEnemyExp = false; +var AdjustTargetLvEnemyExpTiers = new List<(uint MinLv, uint MaxLv, double ExpMultiplier)>() +{ + // MinLv and MaxLv define the relative level difference between the target and highest member in the party. + // The ExpMultiplier value can be a value between [0.0, 1.0] (1.0 = 100%, 0.0 = 0%) + // + // MinLv, MaxLv, ExpMultiplier + ( 0, 2, 1.0), + ( 3, 4, 0.9), + ( 5, 6, 0.8), + ( 7, 8, 0.6), + ( 9, 10, 0.5), +}; + +// Weather Settings +uint WeatherSequenceLength = 20; +var WeatherStatistics = new List<(uint MeanLength, uint Weight)>() +{ + (60 * 30, 1), // Fair + (60 * 30, 1), // Cloudy + (60 * 30, 1), // Rainy +}; + +// Pawn Settings +bool EnableMainPartyPawnsQuestRewards = false; +bool DisableExpCorrectionForMyPawn = true; +bool EnablePawnCatchup = true; +double PawnCatchupMultiplier = 1.5; +uint PawnCatchupLvDiff = 5; + +// Bazaar Settings +uint DefaultMaxBazaarExhibits = 5; +ulong BazaarExhibitionTimeSeconds = (ulong) TimeSpan.FromDays(3).TotalSeconds; +ulong BazaarCooldownTimeSeconds = (ulong) TimeSpan.FromDays(1).TotalSeconds; + +// Clan Settings +uint ClanMemberMax = 100; + +// Epitaph Settings +bool EnableEpitaphWeeklyRewards = false; + +// Point Settings +uint PlayPointMax = 2000; + +// Global Point Modifiers +double EnemyExpModifier = 1; +double QuestExpModifier = 1; +double PpModifier = 1; +double GoldModifier = 1; +double RiftModifier = 1; +double BoModifier = 1; +double HoModifier = 1; +double JpModifier = 1; + +// Wallet Settings +var WalletLimits = new Dictionary() +{ + {WalletType.Gold, 999999999}, + {WalletType.RiftPoints, 999999999}, + {WalletType.BloodOrbs, 500000}, + {WalletType.SilverTickets, 999999999}, + {WalletType.GoldenGemstones, 99999}, + {WalletType.RentalPoints, 99999}, + {WalletType.ResetJobPoints, 99}, + {WalletType.ResetCraftSkills, 99}, + {WalletType.HighOrbs, 5000}, + {WalletType.DominionPoints, 999999999}, + {WalletType.AdventurePassPoints, 80}, + {WalletType.UnknownTickets, 999999999}, + {WalletType.BitterblackMazeResetTicket, 3}, + {WalletType.GoldenDragonMark, 30}, + {WalletType.SilverDragonMark, 150}, + {WalletType.RedDragonMark, 99999}, +}; + +// URL Settings +string urlDomain = $"http://localhost:{52099}"; +string UrlManual = $"{urlDomain}/manual_nfb/"; +string UrlShopDetail = $"{urlDomain}/shop/ingame/stone/detail"; +string UrlShopCounterA = $"{urlDomain}/shop/ingame/counter?"; +string UrlShopAttention = $"{urlDomain}/shop/ingame/attention?"; +string UrlShopStoneLimit = $"{urlDomain}/shop/ingame/stone/limit"; +string UrlShopCounterB = $"{urlDomain}/shop/ingame/counter?"; +string UrlChargeCallback = $"{urlDomain}/opening/entry/ddo/cog_callback/charge"; +string UrlChargeA = $"{urlDomain}/sp_ingame/charge/"; +string UrlSample9 = "http://sample09.html"; +string UrlSample10 = "http://sample10.html"; +string UrlCampaignBanner = $"{urlDomain}/sp_ingame/campaign/bnr/bnr01.html?"; +string UrlSupportIndex = $"{urlDomain}/sp_ingame/support/index.html"; +string UrlPhotoupAuthorize = $"{urlDomain}/api/photoup/authorize"; +string UrlApiA = $"{urlDomain}/link/api"; +string UrlApiB = $"{urlDomain}/link/api"; +string UrlIndex = $"{urlDomain}/sp_ingame/link/index.html"; +string UrlCampaign = $"{urlDomain}/sp_ingame/campaign/bnr/slide.html"; +string UrlChargeB = $"{urlDomain}/sp_ingame/charge/"; +string UrlCompanionImage = $"{urlDomain}/"; diff --git a/Arrowgene.Ddon.Shared/Files/Assets/scripts/settings/README.md b/Arrowgene.Ddon.Shared/Files/Assets/scripts/settings/README.md new file mode 100644 index 00000000..14bd7fe2 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/scripts/settings/README.md @@ -0,0 +1,18 @@ +# Scriptable Settings + +This file attemps to establish some guidelines when defining new settings which are parsed by the scripting engine. + +## General Considerations + +While implementing new GameLogicSettings, consider the possibility that the file can be hotlaoded after the server starts and code according to that assumption. +If the settings interacts with a feature which uses some caching mechanism, make sure to invalid all those caches when the file is reloaded. + +## Naming Guidelines + +#### Enable Variables + +All settings which can enable a feature when set to `true`, should start with the prefix `Enable`. + +### Disable Variables + +All settings which can disable a feature when set to `true`, should start with the prefix `Disable`. diff --git a/Arrowgene.Ddon.Test/GameServer/Characters/CraftManagerTest.cs b/Arrowgene.Ddon.Test/GameServer/Characters/CraftManagerTest.cs index 4c554c8b..7365fcbd 100644 --- a/Arrowgene.Ddon.Test/GameServer/Characters/CraftManagerTest.cs +++ b/Arrowgene.Ddon.Test/GameServer/Characters/CraftManagerTest.cs @@ -1,6 +1,8 @@ using System.Collections.Generic; using Arrowgene.Ddon.GameServer.Scripting; using Arrowgene.Ddon.Server; +using Arrowgene.Ddon.Server.Scripting.interfaces; +using Arrowgene.Ddon.Server.Scripting.utils; using Arrowgene.Ddon.Shared; using Arrowgene.Ddon.Shared.Entity.Structure; using Arrowgene.Ddon.Shared.Model; @@ -13,22 +15,23 @@ public class CraftManagerTest { private readonly DdonGameServer _mockServer; private readonly CraftManager _craftManager; + private readonly ScriptableSettings _scriptableSettings; public CraftManagerTest() { - var settings = new GameServerSetting(); - var gameLogicSetting = new GameLogicSetting(); - gameLogicSetting.GameClockTimescale = 90; - gameLogicSetting.WeatherSequenceLength = 20; - gameLogicSetting.WeatherStatistics = new List<(uint MeanLength, uint Weight)>() + _scriptableSettings = new ScriptableSettings(); + _scriptableSettings.Set("GameLogicSettings", "GameClockTimescale", 90); + _scriptableSettings.Set("GameLogicSettings", "WeatherSequenceLength", 20); + _scriptableSettings.Set("GameLogicSettings", "WeatherStatistics", new List<(uint MeanLength, uint Weight)>() { (60 * 30, 1), // Fair (60 * 30, 1), // Cloudy (60 * 30, 1), // Rainy - }; + }); + var gameLogicSetting = new GameLogicSetting(_scriptableSettings); _mockServer = new DdonGameServer(settings, gameLogicSetting, new MockDatabase(), new AssetRepository("TestFiles")); _craftManager = new CraftManager(_mockServer); } @@ -37,7 +40,7 @@ public class CraftManagerTest public void GetCraftingTimeReductionRate_ShouldReturnCorrectValue() { List productionSpeedLevels = new List { 10, 20, 30 }; - _mockServer.GameLogicSettings.AdditionalProductionSpeedFactor = 1.0; + _scriptableSettings.Set("GameLogicSettings", "AdditionalProductionSpeedFactor", 1.0); double result = _craftManager.GetCraftingTimeReductionRate(productionSpeedLevels); @@ -49,7 +52,7 @@ public class CraftManagerTest { List productionSpeedLevels = new List { 70, 70, 70, 70 }; const uint recipeTime = 100; - _mockServer.GameLogicSettings.AdditionalProductionSpeedFactor = 1.0; + _scriptableSettings.Set("GameLogicSettings", "AdditionalProductionSpeedFactor", 1.0); uint result = _craftManager.CalculateRecipeProductionSpeed(recipeTime, productionSpeedLevels); @@ -61,7 +64,7 @@ public class CraftManagerTest { List productionSpeedLevels = new List { 70, 70, 70, 70 }; const uint recipeTime = 100; - _mockServer.GameLogicSettings.AdditionalProductionSpeedFactor = 100; + _scriptableSettings.Set("GameLogicSettings", "AdditionalProductionSpeedFactor", 100.0); uint result = _craftManager.CalculateRecipeProductionSpeed(recipeTime, productionSpeedLevels);