From 0448cc8c712c2eb629572b279858511a48502e1e Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Thu, 19 Dec 2024 22:25:39 -0500 Subject: [PATCH] feat: Add C# scripting support - Added Roslyn to the project. - Added an example of parsing game logic settings from a .csx file. - Converted extended NPC facilities into scripting .csx files for each NPC. --- .editorconfig | 2 +- Arrowgene.Ddon.Cli/Command/ServerCommand.cs | 1 + Arrowgene.Ddon.Cli/Program.cs | 1 + Arrowgene.Ddon.GameServer/DdonGameServer.cs | 9 +- .../GameServerSetting.cs | 2 +- .../Handler/NpcGetExtendedFacilityHandler.cs | 6 +- .../Scripting/DdonLibrary.cs | 14 + .../Scripting/INpcExtendedFacility.cs | 21 ++ .../Scripting/ScriptManager.cs | 122 ++++++++ Arrowgene.Ddon.Server/GameLogicSetting.cs | 288 ++++-------------- .../Arrowgene.Ddon.Shared.csproj | 23 ++ Arrowgene.Ddon.Shared/AssetRepository.cs | 4 + .../S2CNpcGetNpcExtendedFacilityRes.cs | 1 - .../Files/Assets/scripts/Settings.csx | 142 +++++++++ .../scripts/extended_facilities/Pehr1.csx | 13 + 15 files changed, 411 insertions(+), 238 deletions(-) create mode 100644 Arrowgene.Ddon.GameServer/Scripting/DdonLibrary.cs create mode 100644 Arrowgene.Ddon.GameServer/Scripting/INpcExtendedFacility.cs create mode 100644 Arrowgene.Ddon.GameServer/Scripting/ScriptManager.cs create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/scripts/Settings.csx create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/scripts/extended_facilities/Pehr1.csx diff --git a/.editorconfig b/.editorconfig index b344e721e..4ee64a6dc 100644 --- a/.editorconfig +++ b/.editorconfig @@ -9,7 +9,7 @@ root = true indent_style = space # Code files -[*.{cs,sql,json}] +[*.{cs,sql,json,csx}] indent_size = 4 insert_final_newline = true charset = utf-8 diff --git a/Arrowgene.Ddon.Cli/Command/ServerCommand.cs b/Arrowgene.Ddon.Cli/Command/ServerCommand.cs index d24689333..343322cca 100644 --- a/Arrowgene.Ddon.Cli/Command/ServerCommand.cs +++ b/Arrowgene.Ddon.Cli/Command/ServerCommand.cs @@ -5,6 +5,7 @@ using System.Threading; using Arrowgene.Ddon.Database; using Arrowgene.Ddon.GameServer; +using Arrowgene.Ddon.GameServer.Scripting; using Arrowgene.Ddon.LoginServer; using Arrowgene.Ddon.Rpc.Web; using Arrowgene.Ddon.Shared; diff --git a/Arrowgene.Ddon.Cli/Program.cs b/Arrowgene.Ddon.Cli/Program.cs index 1a07b2393..ace94c506 100644 --- a/Arrowgene.Ddon.Cli/Program.cs +++ b/Arrowgene.Ddon.Cli/Program.cs @@ -26,6 +26,7 @@ using System.Text; using System.Threading; using Arrowgene.Ddon.Cli.Command; +using Arrowgene.Ddon.GameServer.Scripting; using Arrowgene.Ddon.Shared; using Arrowgene.Ddon.Shared.Network; using Arrowgene.Logging; diff --git a/Arrowgene.Ddon.GameServer/DdonGameServer.cs b/Arrowgene.Ddon.GameServer/DdonGameServer.cs index a1b4bea1e..52edd1f5a 100644 --- a/Arrowgene.Ddon.GameServer/DdonGameServer.cs +++ b/Arrowgene.Ddon.GameServer/DdonGameServer.cs @@ -29,6 +29,7 @@ using Arrowgene.Ddon.GameServer.Dump; using Arrowgene.Ddon.GameServer.Handler; using Arrowgene.Ddon.GameServer.Party; +using Arrowgene.Ddon.GameServer.Scripting; using Arrowgene.Ddon.GameServer.Shop; using Arrowgene.Ddon.Server; using Arrowgene.Ddon.Server.Handler; @@ -52,7 +53,12 @@ public class DdonGameServer : DdonServer public DdonGameServer(GameServerSetting setting, IDatabase database, AssetRepository assetRepository) : base(ServerType.Game, setting.ServerSetting, database, assetRepository) { + // This must be first Setting = new GameServerSetting(setting); + // This must be second + ScriptManager = new ScriptManager(this); + + // Rest of server managers ClientLookup = new GameClientLookup(); ChatLogHandler = new ChatLogHandler(); ChatManager = new ChatManager(this); @@ -91,6 +97,7 @@ public DdonGameServer(GameServerSetting setting, IDatabase database, AssetReposi public event EventHandler ClientConnectionChangeEvent; public GameServerSetting Setting { get; } + public ScriptManager ScriptManager { get; } public ChatManager ChatManager { get; } public ItemManager ItemManager { get; } public CraftManager CraftManager { get; } @@ -136,7 +143,7 @@ public override void Start() { ScheduleManager.StartServerTasks(); } - + LoadChatHandler(); LoadPacketHandler(); base.Start(); diff --git a/Arrowgene.Ddon.GameServer/GameServerSetting.cs b/Arrowgene.Ddon.GameServer/GameServerSetting.cs index 2839e7727..05f25bd95 100644 --- a/Arrowgene.Ddon.GameServer/GameServerSetting.cs +++ b/Arrowgene.Ddon.GameServer/GameServerSetting.cs @@ -7,7 +7,7 @@ namespace Arrowgene.Ddon.GameServer public class GameServerSetting { [DataMember(Order = 1)] public ServerSetting ServerSetting { get; set; } - [DataMember(Order = 2)] public GameLogicSetting GameLogicSetting { get; set; } + public GameLogicSetting GameLogicSetting { get; set; } public GameServerSetting() { diff --git a/Arrowgene.Ddon.GameServer/Handler/NpcGetExtendedFacilityHandler.cs b/Arrowgene.Ddon.GameServer/Handler/NpcGetExtendedFacilityHandler.cs index 8733982ce..d0677d0c1 100644 --- a/Arrowgene.Ddon.GameServer/Handler/NpcGetExtendedFacilityHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/NpcGetExtendedFacilityHandler.cs @@ -22,11 +22,13 @@ public NpcGetExtendedFacilityHandler(DdonGameServer server) : base(server) public override S2CNpcGetNpcExtendedFacilityRes Handle(GameClient client, C2SNpcGetNpcExtendedFacilityReq request) { var result = new S2CNpcGetNpcExtendedFacilityRes(); - if (gNpcExtendedBehavior.ContainsKey(request.NpcId)) + + if (Server.ScriptManager.NpcExtendedFacilities.ContainsKey(request.NpcId)) { result.NpcId = request.NpcId; - gNpcExtendedBehavior[request.NpcId](Server, client, result); + Server.ScriptManager.NpcExtendedFacilities[request.NpcId].GetExtendedOptions(Server, client, result); } + return result; } diff --git a/Arrowgene.Ddon.GameServer/Scripting/DdonLibrary.cs b/Arrowgene.Ddon.GameServer/Scripting/DdonLibrary.cs new file mode 100644 index 000000000..eb2e74743 --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Scripting/DdonLibrary.cs @@ -0,0 +1,14 @@ +namespace Arrowgene.Ddon.GameServer.Scripting +{ + public class DdonLibrary + { + public DdonLibrary() + { + } + + public static uint GetValue() + { + return 1; + } + } +} diff --git a/Arrowgene.Ddon.GameServer/Scripting/INpcExtendedFacility.cs b/Arrowgene.Ddon.GameServer/Scripting/INpcExtendedFacility.cs new file mode 100644 index 000000000..212d4095f --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Scripting/INpcExtendedFacility.cs @@ -0,0 +1,21 @@ +using Arrowgene.Ddon.Shared.Entity.PacketStructure; +using Arrowgene.Ddon.Shared.Model; + +namespace Arrowgene.Ddon.GameServer.Scripting +{ + public abstract class INpcExtendedFacility + { + /// + /// NPC ID associated with the extended options. + /// + public NpcId NpcId { get; protected set; } + + /// + /// Gets extended menu options for the NPC. + /// + /// + /// + /// The result object for the extended NPC options + public abstract void GetExtendedOptions(DdonGameServer server, GameClient client, S2CNpcGetNpcExtendedFacilityRes result); + } +} diff --git a/Arrowgene.Ddon.GameServer/Scripting/ScriptManager.cs b/Arrowgene.Ddon.GameServer/Scripting/ScriptManager.cs new file mode 100644 index 000000000..0831dd52b --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Scripting/ScriptManager.cs @@ -0,0 +1,122 @@ +using Arrowgene.Ddon.Server; +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.GameServer.Scripting +{ + public class Globals + { + public DdonGameServer Server { get; set; } + public GameLogicSetting GameLogicSetting { get; set; } + } + + public class ScriptManager + { + public string ScriptsRoot { get; private set; } + public string SettingsPath { get; private set; } + public string ExtendedFacilitiesPath { get; private set; } + + public Dictionary NpcExtendedFacilities; + + public Script Settings { + get { + return CompiledScripts["Settings"]; + } + set + { + CompiledScripts["Settings"] = value; + } + } + + private Dictionary CompiledScripts; + private static readonly ServerLogger Logger = LogProvider.Logger(typeof(ScriptManager)); + private Globals Globals { get; } + private DdonGameServer Server { get; } + + public event EventHandler AssetChanged; + private readonly DirectoryInfo ScriptsDirectory; + private readonly Dictionary FileSystemWatchers; + + public ScriptManager(DdonGameServer server) + { + Server = server; + + ScriptsRoot = $"{server.AssetRepository.AssetsPath}\\scripts"; + SettingsPath = $"{ScriptsRoot}\\Settings.csx"; + ExtendedFacilitiesPath = $"{ScriptsRoot}\\extended_facilities"; + + NpcExtendedFacilities = new Dictionary(); + + Globals = new Globals() + { + Server = server, + GameLogicSetting = server.Setting.GameLogicSetting + }; + + ScriptsDirectory = new DirectoryInfo(ScriptsRoot); + if (!ScriptsDirectory.Exists) + { + Logger.Error($"Could not compile scripts, unable to locate the path '{ScriptsRoot}'"); + return; + } + + FileSystemWatchers = new Dictionary(); + + CompiledScripts = new Dictionary(); + + CompileScripts(); + } + + private void CompileScripts() + { + var options = ScriptOptions.Default + .AddReferences(MetadataReference.CreateFromFile(typeof(DdonGameServer).Assembly.Location)) + .AddReferences(MetadataReference.CreateFromFile(typeof(GameLogicSetting).Assembly.Location)) + .AddReferences(MetadataReference.CreateFromFile(typeof(ScriptManager).Assembly.Location)) + .AddReferences(MetadataReference.CreateFromFile(typeof(WalletType).Assembly.Location)) + .AddImports("System", "System.Collections", "System.Collections.Generic") + .AddImports("Arrowgene.Ddon.Shared") + .AddImports("Arrowgene.Ddon.Shared.Model") + .AddImports("Arrowgene.Ddon.GameServer") + .AddImports("Arrowgene.Ddon.GameServer.Scripting") + .AddImports("Arrowgene.Ddon.Shared.Entity.PacketStructure"); + + // Load The Game Settings + Settings = CSharpScript.Create( + code: File.ReadAllText(SettingsPath), + options: options, + globalsType: typeof(Globals) + ); + // Execute the script file to populate the settings + Settings.RunAsync(Globals); + + foreach (var file in Directory.EnumerateFiles(ExtendedFacilitiesPath)) + { + var script = CSharpScript.Create( + code: File.ReadAllText(file), + options: options, + globalsType: typeof(Globals) + ); + + var result = script.RunAsync(Globals).Result; + if (result == null) + { + Logger.Error($"Failed to parse {file}. Skipping."); + continue; + } + + INpcExtendedFacility extendedFacility = (INpcExtendedFacility)result.ReturnValue; + NpcExtendedFacilities[extendedFacility.NpcId] = extendedFacility; + } + + // TODO: Load other game functionality we want to have scripted. + } + } +} diff --git a/Arrowgene.Ddon.Server/GameLogicSetting.cs b/Arrowgene.Ddon.Server/GameLogicSetting.cs index 5b0ec0f88..639196476 100644 --- a/Arrowgene.Ddon.Server/GameLogicSetting.cs +++ b/Arrowgene.Ddon.Server/GameLogicSetting.cs @@ -6,44 +6,21 @@ namespace Arrowgene.Ddon.Server { - [DataContract] - public class DefaultDataMember - { - private T _DefaultValue; - private T? _Value; - - [DataMember] - public T Value { - get => _Value ?? _DefaultValue; - set => _Value = Value; - } - - public DefaultDataMember(T defaultValue) - { - _DefaultValue = defaultValue; - } - } - - - [DataContract] public class GameLogicSetting { /// /// Additional factor to change how long crafting a recipe will take to finish. /// - [DataMember(Order = 0)] public double AdditionalProductionSpeedFactor { get; set; } /// /// Additional factor to change how much a recipe will cost. /// - [DataMember(Order = 1)] public double AdditionalCostPerformanceFactor { get; set; } /// /// Sets the maximim level that the exp ring will reward a bonus. /// - [DataMember(Order = 2)] public uint RookiesRingMaxLevel { get; set; } /// @@ -51,7 +28,6 @@ public class GameLogicSetting /// Must be a non-negtive value. If it is less than 0.0, a default of 1.0 /// will be selected. /// - [DataMember(Order = 3)] public double RookiesRingBonus { get; set; } /// @@ -59,317 +35,233 @@ public class GameLogicSetting /// 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. /// - [DataMember(Order = 4)] 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. /// - [DataMember(Order = 5)] public byte CraftConsumableProductionTimesMax { get; set; } + public byte CraftConsumableProductionTimesMax { get; set; } /// /// Configures if party exp is adjusted based on level differences of members. /// - [DataMember(Order = 6)] public bool AdjustPartyEnemyExp { get; set; } + public bool AdjustPartyEnemyExp { 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. /// - [DataMember(Order = 7)] public List<(uint MinLv, uint MaxLv, double ExpMultiplier)> AdjustPartyEnemyExpTiers { get; set; } + 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. /// - [DataMember(Order = 8)] public bool AdjustTargetLvEnemyExp { get; set; } + public bool AdjustTargetLvEnemyExp { 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. /// - [DataMember(Order = 9)] public List<(uint MinLv, uint MaxLv, double ExpMultiplier)> AdjustTargetLvEnemyExpTiers { get; set; } + public List<(uint MinLv, uint MaxLv, double ExpMultiplier)> AdjustTargetLvEnemyExpTiers { get; set; } /// /// The number of real world minutes that make up an in-game day. /// - [DataMember(Order = 10)] public uint GameClockTimescale { get; set; } + public uint GameClockTimescale { get; set; } /// /// Use a poisson process to randomly generate a weather cycle containing this many events, using the statistics in WeatherStatistics. /// - [DataMember(Order = 11)] public uint WeatherSequenceLength { get; set; } + 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. /// - [DataMember(Order = 12)] public List<(uint MeanLength, uint Weight)> WeatherStatistics { get; set; } + 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. /// - [DataMember(Order = 13)] public bool EnablePawnCatchup { get; set; } + 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. /// - [DataMember(Order = 14)] public double PawnCatchupMultiplier { get; set; } + 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. /// - [DataMember(Order = 15)] public uint PawnCatchupLvDiff { get; set; } + public uint PawnCatchupLvDiff { get; set; } /// /// Configures the default time in seconds a latern is active after igniting it. /// - [DataMember(Order = 16)] public uint LaternBurnTimeInSeconds { get; set; } + 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. /// - [DataMember(Order = 17)] public uint PlayPointMax { get; set; } + public uint PlayPointMax { get; set; } /// /// Maximum level for each job. /// Shared with the login server. /// - [DataMember(Order = 18)] public uint JobLevelMax { get; set; } + public uint JobLevelMax { get; set; } /// /// Maximum number of members in a single clan. /// Shared with the login server. /// - [DataMember(Order = 19)] public uint ClanMemberMax { get; set; } + public uint ClanMemberMax { get; set; } /// /// Maximum number of characters per account. /// Shared with the login server. /// - [DataMember(Order = 20)] public byte CharacterNumMax { get; set; } + public byte CharacterNumMax { get; set; } /// /// Toggles the visual equip set for all characters. /// Shared with the login server. /// - [DataMember(Order = 21)] public bool EnableVisualEquip { get; set; } + public bool EnableVisualEquip { get; set; } /// /// Maximum entries in the friends list. /// Shared with the login server. /// - [DataMember(Order = 22)] public uint FriendListMax { get; set; } + public uint FriendListMax { get; set; } /// /// Limits for each wallet type. /// - [DataMember(Order = 23)] public Dictionary WalletLimits { get; set; } + public Dictionary WalletLimits { get; set; } /// /// Number of bazaar entries that are given to new characters. /// - [DataMember(Order = 24)] public uint DefaultMaxBazaarExhibits { get; set; } + public uint DefaultMaxBazaarExhibits { get; set; } /// /// Number of favorite warps that are given to new characters. /// - [DataMember(Order = 25)] public uint DefaultWarpFavorites { get; set; } + public uint DefaultWarpFavorites { get; set; } /// /// Disables the exp correction if all party members are owned by the same character. /// - [DataMember(Order = 26)] public bool DisableExpCorrectionForMyPawn { get; set; } + public bool DisableExpCorrectionForMyPawn { get; set; } /// /// Global modifier for enemy exp calculations to scale up or down. /// - [DataMember(Order = 27)] public double? EnemyExpModifier { get; set; } = 1.0; + public double? EnemyExpModifier { get; set; } /// /// Global modifier for quest exp calculations to scale up or down. /// - [DataMember(Order = 28)] public double? QuestExpModifier { get; set; } = 1.0; + public double? QuestExpModifier { get; set; } /// /// Global modifier for pp calculations to scale up or down. /// - [DataMember(Order = 29)] public double? PpModifier { get; set; } = 1.0; + public double? PpModifier { get; set; } /// /// Global modifier for Gold calculations to scale up or down. /// - [DataMember(Order = 30)] public double? GoldModifier { get; set; } = 1.0; + public double? GoldModifier { get; set; } /// /// Global modifier for Rift calculations to scale up or down. /// - [DataMember(Order = 31)] public double? RiftModifier { get; set; } = 1.0; + public double? RiftModifier { get; set; } /// /// Global modifier for BO calculations to scale up or down. /// - [DataMember(Order = 32)] public double? BoModifier { get; set; } = 1.0; + public double? BoModifier { get; set; } /// /// Global modifier for HO calculations to scale up or down. /// - [DataMember(Order = 33)] public double? HoModifier { get; set; } = 1.0; + public double? HoModifier { get; set; } /// /// Global modifier for JP calculations to scale up or down. /// - [DataMember(Order = 34)] public double? JpModifier { get; set; } = 1.0; + public double? JpModifier { get; set; } /// /// Configures the maximum amount of reward box slots. /// - [DataMember(Order = 35)] public byte? RewardBoxMax { get; set; } = 100; + public byte? RewardBoxMax { get; set; } /// /// Configures the maximum amount of quests that can be ordered at one time. /// - [DataMember(Order = 36)] public byte? QuestOrderMax { get; set; } = 20; + public byte? QuestOrderMax { get; set; } /// /// Configures if epitaph rewards are limited once per weekly reset. /// - [DataMember(Order = 37)] public bool? EnableEpitaphWeeklyRewards { get; set; } = true; + 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 /// - [DataMember(Order = 38)] public bool EnableMainPartyPawnsQuestRewards { get; set; } + public bool EnableMainPartyPawnsQuestRewards { get; set; } /// /// Specifies the time in seconds that a bazaar exhibit will last. /// By default, the equivalent of 3 days /// - [DataMember(Order = 37)] public ulong BazaarExhibitionTimeSeconds { get; set; } + 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 /// - [DataMember(Order = 38)] public ulong BazaarCooldownTimeSeconds { get; set; } + public ulong BazaarCooldownTimeSeconds { get; set; } /// /// Various URLs used by the client. /// Shared with the login server. /// - [DataMember(Order = 200)] public string UrlManual { get; set; } - [DataMember(Order = 200)] public string UrlShopDetail { get; set; } - [DataMember(Order = 200)] public string UrlShopCounterA { get; set; } - [DataMember(Order = 200)] public string UrlShopAttention { get; set; } - [DataMember(Order = 200)] public string UrlShopStoneLimit { get; set; } - [DataMember(Order = 200)] public string UrlShopCounterB { get; set; } - [DataMember(Order = 200)] public string UrlChargeCallback { get; set; } - [DataMember(Order = 200)] public string UrlChargeA { get; set; } - [DataMember(Order = 200)] public string UrlSample9 { get; set; } - [DataMember(Order = 200)] public string UrlSample10 { get; set; } - [DataMember(Order = 200)] public string UrlCampaignBanner { get; set; } - [DataMember(Order = 200)] public string UrlSupportIndex { get; set; } - [DataMember(Order = 200)] public string UrlPhotoupAuthorize { get; set; } - [DataMember(Order = 200)] public string UrlApiA { get; set; } - [DataMember(Order = 200)] public string UrlApiB { get; set; } - [DataMember(Order = 200)] public string UrlIndex { get; set; } - [DataMember(Order = 200)] public string UrlCampaign { get; set; } - [DataMember(Order = 200)] public string UrlChargeB { get; set; } - [DataMember(Order = 200)] public string UrlCompanionImage { get; set; } + 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() { - LaternBurnTimeInSeconds = 1500; - AdditionalProductionSpeedFactor = 1.0; - AdditionalCostPerformanceFactor = 1.0; - RookiesRingMaxLevel = 89; - RookiesRingBonus = 1.0; - NaiveLobbyContextHandling = true; - CraftConsumableProductionTimesMax = 10; - - AdjustPartyEnemyExp = true; - AdjustPartyEnemyExpTiers = new List<(uint MinLv, uint MaxLv, double ExpMultiplier)>() - { - (0, 2, 1.0), - (3, 4, 0.9), - (5, 6, 0.8), - (7, 8, 0.6), - (9, 10, 0.5), - }; - - AdjustTargetLvEnemyExp = false; - AdjustTargetLvEnemyExpTiers = new List<(uint MinLv, uint MaxLv, double ExpMultiplier)>() - { - (0, 2, 1.0), - (3, 4, 0.9), - (5, 6, 0.8), - (7, 8, 0.6), - (9, 10, 0.5), - }; - - EnablePawnCatchup = true; - PawnCatchupMultiplier = 1.5; - PawnCatchupLvDiff = 5; - - DisableExpCorrectionForMyPawn = true; - - GameClockTimescale = 90; - - WeatherSequenceLength = 20; - WeatherStatistics = new List<(uint MeanLength, uint Weight)> - { - (60 * 30, 1), //Fair - (60 * 30, 1), //Cloudy - (60 * 30, 1), //Rainy - }; - - PlayPointMax = 2000; - - JobLevelMax = 120; - ClanMemberMax = 100; - CharacterNumMax = 4; - EnableVisualEquip = true; - FriendListMax = 200; - - WalletLimits = DefaultWalletLimits; - - DefaultMaxBazaarExhibits = 5; - DefaultWarpFavorites = 3; - - EnableEpitaphWeeklyRewards = false; - EnableMainPartyPawnsQuestRewards = false; - - BazaarExhibitionTimeSeconds = (ulong) TimeSpan.FromDays(3).TotalSeconds; - BazaarCooldownTimeSeconds = (ulong) TimeSpan.FromDays(1).TotalSeconds; - - string urlDomain = $"http://localhost:{52099}"; - UrlManual = $"{urlDomain}/manual_nfb/"; - UrlShopDetail = $"{urlDomain}/shop/ingame/stone/detail"; - UrlShopCounterA = $"{urlDomain}/shop/ingame/counter?"; - UrlShopAttention = $"{urlDomain}/shop/ingame/attention?"; - UrlShopStoneLimit = $"{urlDomain}/shop/ingame/stone/limit"; - UrlShopCounterB = $"{urlDomain}/shop/ingame/counter?"; - UrlChargeCallback = $"{urlDomain}/opening/entry/ddo/cog_callback/charge"; - UrlChargeA = $"{urlDomain}/sp_ingame/charge/"; - UrlSample9 = "http://sample09.html"; - UrlSample10 = "http://sample10.html"; - UrlCampaignBanner = $"{urlDomain}/sp_ingame/campaign/bnr/bnr01.html?"; - UrlSupportIndex = $"{urlDomain}/sp_ingame/support/index.html"; - UrlPhotoupAuthorize = $"{urlDomain}/api/photoup/authorize"; - UrlApiA = $"{urlDomain}/link/api"; - UrlApiB = $"{urlDomain}/link/api"; - UrlIndex = $"{urlDomain}/sp_ingame/link/index.html"; - UrlCampaign = $"{urlDomain}/sp_ingame/campaign/bnr/slide.html"; - UrlChargeB = $"{urlDomain}/sp_ingame/charge/"; - UrlCompanionImage = $"{urlDomain}/"; } public GameLogicSetting(GameLogicSetting setting) @@ -440,48 +332,8 @@ public GameLogicSetting(GameLogicSetting setting) UrlCompanionImage = setting.UrlCompanionImage; } - // Note: method is called after the object is completely deserialized - constructors are skipped. - [OnDeserialized] - void OnDeserialized(StreamingContext context) + void ValidateSettings() { - // Initialize reference types so tests work properly. - AdjustPartyEnemyExpTiers ??= new(); - AdjustTargetLvEnemyExpTiers ??= new(); - WeatherStatistics ??= new(); - WalletLimits ??= new(); - UrlManual ??= string.Empty; - UrlShopDetail ??= string.Empty; - UrlShopCounterA ??= string.Empty; - UrlShopAttention ??= string.Empty; - UrlShopStoneLimit ??= string.Empty; - UrlShopCounterB ??= string.Empty; - UrlChargeCallback ??= string.Empty; - UrlChargeA ??= string.Empty; - UrlSample9 ??= string.Empty; - UrlSample10 ??= string.Empty; - UrlCampaignBanner ??= string.Empty; - UrlSupportIndex ??= string.Empty; - UrlPhotoupAuthorize ??= string.Empty; - UrlApiA ??= string.Empty; - UrlApiB ??= string.Empty; - UrlIndex ??= string.Empty; - UrlCampaign ??= string.Empty; - UrlChargeB ??= string.Empty; - UrlCompanionImage ??= string.Empty; - - EnableEpitaphWeeklyRewards ??= true; - - EnemyExpModifier ??= 1; - QuestExpModifier ??= 1; - PpModifier ??= 1; - GoldModifier ??= 1; - RiftModifier ??= 1; - BoModifier ??= 1; - HoModifier ??= 1; - JpModifier ??= 1; - RewardBoxMax ??= 100; - QuestOrderMax ??= 20; - if (RookiesRingBonus < 0) { RookiesRingBonus = 1.0; @@ -538,34 +390,6 @@ void OnDeserialized(StreamingContext context) { JpModifier = 1.0; } - - foreach (var walletMax in DefaultWalletLimits) - { - if (!WalletLimits.ContainsKey(walletMax.Key)) - { - WalletLimits.Add(walletMax.Key, walletMax.Value); - } - } } - - private static readonly Dictionary DefaultWalletLimits = new() - { - {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}, - }; } } diff --git a/Arrowgene.Ddon.Shared/Arrowgene.Ddon.Shared.csproj b/Arrowgene.Ddon.Shared/Arrowgene.Ddon.Shared.csproj index 94100155d..ae3e34b12 100644 --- a/Arrowgene.Ddon.Shared/Arrowgene.Ddon.Shared.csproj +++ b/Arrowgene.Ddon.Shared/Arrowgene.Ddon.Shared.csproj @@ -22,6 +22,7 @@ + @@ -31,4 +32,26 @@ Files\%(RecursiveDir)%(Filename)%(Extension) + + + + + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + diff --git a/Arrowgene.Ddon.Shared/AssetRepository.cs b/Arrowgene.Ddon.Shared/AssetRepository.cs index 2444d1a03..5812e3945 100644 --- a/Arrowgene.Ddon.Shared/AssetRepository.cs +++ b/Arrowgene.Ddon.Shared/AssetRepository.cs @@ -16,6 +16,8 @@ namespace Arrowgene.Ddon.Shared { public class AssetRepository { + public string AssetsPath { get; private set; } + // Client data public const string ClientErrorCodesKey = "ClientErrorCodes.csv"; public const string ItemListKey = "itemlist.csv"; @@ -70,6 +72,8 @@ public AssetRepository(string folder) return; } + AssetsPath = folder; + _fileSystemWatchers = new Dictionary(); ClientErrorCodes = new List(); diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CNpcGetNpcExtendedFacilityRes.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CNpcGetNpcExtendedFacilityRes.cs index 24c85883c..919758528 100644 --- a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CNpcGetNpcExtendedFacilityRes.cs +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CNpcGetNpcExtendedFacilityRes.cs @@ -3,7 +3,6 @@ using Arrowgene.Ddon.Shared.Model; using Arrowgene.Ddon.Shared.Network; using System.Collections.Generic; -using System.Diagnostics.Contracts; namespace Arrowgene.Ddon.Shared.Entity.PacketStructure { diff --git a/Arrowgene.Ddon.Shared/Files/Assets/scripts/Settings.csx b/Arrowgene.Ddon.Shared/Files/Assets/scripts/Settings.csx new file mode 100644 index 000000000..89b09177b --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/scripts/Settings.csx @@ -0,0 +1,142 @@ +/** + * Settings file for Server customization. + */ + +// 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 +GameLogicSetting.AdjustPartyEnemyExp = true; +GameLogicSetting.AdjustPartyEnemyExpTiers = new List<(uint MinLv, uint MaxLv, double ExpMultiplier)>() +{ + // 1.0 = 100%, 0 = 0% + // If the range is larger than the last entry, + // a 0% exp rate is automatically applied if + // AdjustPartyEnemyExp = true + // + // MinLv, MaxLv, ExpMultiplier + ( 0, 2, 1.0), + ( 3, 4, 0.9), + ( 5, 6, 0.8), + ( 7, 8, 0.6), + ( 9, 10, 0.5), +}; + +GameLogicSetting.AdjustTargetLvEnemyExp = false; +GameLogicSetting.AdjustTargetLvEnemyExpTiers = new List<(uint MinLv, uint MaxLv, double ExpMultiplier)>() +{ + // 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/extended_facilities/Pehr1.csx b/Arrowgene.Ddon.Shared/Files/Assets/scripts/extended_facilities/Pehr1.csx new file mode 100644 index 000000000..ae9cc34e4 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/scripts/extended_facilities/Pehr1.csx @@ -0,0 +1,13 @@ +public class Perh1 : INpcExtendedFacility +{ + public Perh1() + { + NpcId = NpcId.Pehr1; + } + + public override void GetExtendedOptions(DdonGameServer server, GameClient client, S2CNpcGetNpcExtendedFacilityRes result) + { + } +} + +return new Perh1();