From ee1a11f25bdaa1ae2999af01efdeccb1cd516039 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Thu, 19 Dec 2024 22:25:39 -0500 Subject: [PATCH 1/6] feat: Add C# scripting support - Added Roslyn to the project. - Added an example of parsing game logic settings from a .csx file. - Made GameLogicSettings.csx hotloadable. - Adjusted exp point modifier settings for enemies and quests to be hotload friendly. - Implemented a ScriptModule interface for GameServer scripts. - Converted extended NPC facilities into scripting .csx files for each NPC and made directory hotloadable. New NPC options can be added after the server starts without a restart. --- .editorconfig | 2 +- Arrowgene.Ddon.Cli/Command/ServerCommand.cs | 17 +- Arrowgene.Ddon.Cli/Program.cs | 8 +- .../Arrowgene.Ddon.GameServer.csproj | 2 +- .../Characters/EpitaphRoadManager.cs | 16 + .../Characters/StageManager.cs | 10 +- Arrowgene.Ddon.GameServer/DdonGameServer.cs | 7 +- .../GameServerSetting.cs | 4 +- .../Handler/InstanceEnemyKillHandler.cs | 3 +- .../Handler/NpcGetExtendedFacilityHandler.cs | 86 +---- .../Quests/GenericQuest.cs | 11 +- .../MainQuests/Mq030260_HopesBitterEnd.cs | 2 +- Arrowgene.Ddon.GameServer/Quests/Quest.cs | 44 ++- .../Quests/QuestStateManager.cs | 4 +- .../Interfaces/INpcExtendedFacility.cs | 21 ++ .../Modules/NpcExtendedFacilityModule.cs | 53 +++ .../Scripting/ScriptManager.cs | 188 +++++++++++ .../Scripting/ScriptModule.cs | 34 ++ .../Scripting/ScriptUtils.cs | 27 ++ Arrowgene.Ddon.LoginServer/DdonLoginServer.cs | 2 +- Arrowgene.Ddon.Server/GameLogicSetting.cs | 311 ++++-------------- .../ScriptedServerSettings.cs | 146 ++++++++ .../Arrowgene.Ddon.Shared.csproj | 34 ++ Arrowgene.Ddon.Shared/AssetRepository.cs | 4 + .../S2CNpcGetNpcExtendedFacilityRes.cs | 1 - .../Assets/scripts/GameLogicSettings.csx | 143 ++++++++ .../scripts/extended_facilities/Anita1.csx | 22 ++ .../scripts/extended_facilities/Damad1.csx | 22 ++ .../scripts/extended_facilities/Isel1.csx | 22 ++ .../scripts/extended_facilities/Pehr1.csx | 17 + Arrowgene.Ddon.Shared/Util.cs | 22 +- .../GameServer/Characters/CraftManagerTest.cs | 13 +- Arrowgene.Ddon.Test/GameServer/SettingTest.cs | 12 - Arrowgene.DragonsDogmaOnline.sln | 26 +- 34 files changed, 931 insertions(+), 405 deletions(-) create mode 100644 Arrowgene.Ddon.GameServer/Scripting/Interfaces/INpcExtendedFacility.cs create mode 100644 Arrowgene.Ddon.GameServer/Scripting/Modules/NpcExtendedFacilityModule.cs create mode 100644 Arrowgene.Ddon.GameServer/Scripting/ScriptManager.cs create mode 100644 Arrowgene.Ddon.GameServer/Scripting/ScriptModule.cs create mode 100644 Arrowgene.Ddon.GameServer/Scripting/ScriptUtils.cs create mode 100644 Arrowgene.Ddon.Server/ScriptedServerSettings.cs create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/scripts/GameLogicSettings.csx create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/scripts/extended_facilities/Anita1.csx create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/scripts/extended_facilities/Damad1.csx create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/scripts/extended_facilities/Isel1.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..d0f24da16 100644 --- a/Arrowgene.Ddon.Cli/Command/ServerCommand.cs +++ b/Arrowgene.Ddon.Cli/Command/ServerCommand.cs @@ -1,15 +1,15 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Threading; using Arrowgene.Ddon.Database; using Arrowgene.Ddon.GameServer; using Arrowgene.Ddon.LoginServer; using Arrowgene.Ddon.Rpc.Web; +using Arrowgene.Ddon.Server; using Arrowgene.Ddon.Shared; using Arrowgene.Ddon.WebServer; using Arrowgene.Logging; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Threading; namespace Arrowgene.Ddon.Cli.Command { @@ -20,6 +20,7 @@ public class ServerCommand : ICommand private readonly Setting _setting; private DdonLoginServer _loginServer; private DdonGameServer _gameServer; + private ScriptedServerSettings _scriptServerSettings; private DdonWebServer _webServer; private RpcWebServer _rpcWebServer; private IDatabase _database; @@ -110,6 +111,12 @@ public CommandResultType Run(CommandParameter parameter) _assetRepository.Initialize(); } + if (_scriptServerSettings == null) + { + _scriptServerSettings = new ScriptedServerSettings(_setting.GameServerSetting.GameLogicSetting, _setting.AssetPath); + _scriptServerSettings.LoadSettings(); + } + if (_loginServer == null) { _loginServer = new DdonLoginServer(_setting.LoginServerSetting, _setting.GameServerSetting.GameLogicSetting, _database, _assetRepository); diff --git a/Arrowgene.Ddon.Cli/Program.cs b/Arrowgene.Ddon.Cli/Program.cs index 1a07b2393..042d3a08b 100644 --- a/Arrowgene.Ddon.Cli/Program.cs +++ b/Arrowgene.Ddon.Cli/Program.cs @@ -20,15 +20,15 @@ * along with Arrowgene.Ddon.Cli. If not, see . */ +using Arrowgene.Ddon.Cli.Command; +using Arrowgene.Ddon.Shared; +using Arrowgene.Ddon.Shared.Network; +using Arrowgene.Logging; using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Threading; -using Arrowgene.Ddon.Cli.Command; -using Arrowgene.Ddon.Shared; -using Arrowgene.Ddon.Shared.Network; -using Arrowgene.Logging; namespace Arrowgene.Ddon.Cli { diff --git a/Arrowgene.Ddon.GameServer/Arrowgene.Ddon.GameServer.csproj b/Arrowgene.Ddon.GameServer/Arrowgene.Ddon.GameServer.csproj index c1272072c..620340a1a 100644 --- a/Arrowgene.Ddon.GameServer/Arrowgene.Ddon.GameServer.csproj +++ b/Arrowgene.Ddon.GameServer/Arrowgene.Ddon.GameServer.csproj @@ -17,6 +17,6 @@ - + diff --git a/Arrowgene.Ddon.GameServer/Characters/EpitaphRoadManager.cs b/Arrowgene.Ddon.GameServer/Characters/EpitaphRoadManager.cs index 41bce2482..195abe54c 100644 --- a/Arrowgene.Ddon.GameServer/Characters/EpitaphRoadManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/EpitaphRoadManager.cs @@ -1322,6 +1322,22 @@ public List RollGatheringLoot(GameClient client, Charact return results; } + public bool CheckUnlockConditions(GameClient client, EpitaphBarrier barrier) + { + foreach (var sectionId in barrier.DependentSectionIds) + { + var sectionInfo = _Server.EpitaphRoadManager.GetSectionById(sectionId); + foreach (var unlockId in sectionInfo.UnlockDependencies) + { + if (!client.Character.EpitaphRoadState.UnlockedContent.Contains(unlockId)) + { + return false; + } + } + } + return true; + } + /// /// Called by the task manager. The main task will signal all channels /// to flush the cached information queried by the player when first diff --git a/Arrowgene.Ddon.GameServer/Characters/StageManager.cs b/Arrowgene.Ddon.GameServer/Characters/StageManager.cs index 234d6647c..45aa0ec8a 100644 --- a/Arrowgene.Ddon.GameServer/Characters/StageManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/StageManager.cs @@ -1,15 +1,9 @@ using Arrowgene.Ddon.GameServer.Dump; -using Arrowgene.Ddon.Shared.Entity.PacketStructure; using Arrowgene.Ddon.Shared.Entity; +using Arrowgene.Ddon.Shared.Entity.PacketStructure; +using Arrowgene.Ddon.Shared.Entity.Structure; using Arrowgene.Ddon.Shared.Model; -using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Arrowgene.Ddon.Shared.Entity.Structure; -using Arrowgene.Ddon.Shared.Network; -using System.IO; namespace Arrowgene.Ddon.GameServer.Characters { diff --git a/Arrowgene.Ddon.GameServer/DdonGameServer.cs b/Arrowgene.Ddon.GameServer/DdonGameServer.cs index a1b4bea1e..09f944a57 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; @@ -53,6 +54,7 @@ public DdonGameServer(GameServerSetting setting, IDatabase database, AssetReposi : base(ServerType.Game, setting.ServerSetting, database, assetRepository) { Setting = new GameServerSetting(setting); + ScriptManager = new ScriptManager(this); ClientLookup = new GameClientLookup(); ChatLogHandler = new ChatLogHandler(); ChatManager = new ChatManager(this); @@ -91,6 +93,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; } @@ -129,6 +132,8 @@ public DdonGameServer(GameServerSetting setting, IDatabase database, AssetReposi public override void Start() { + ScriptManager.Initialize(); + QuestManager.LoadQuests(this); GpCourseManager.EvaluateCourses(); @@ -136,7 +141,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 b1fba56c1..0342a4f10 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() { @@ -17,7 +17,7 @@ public GameServerSetting() public GameServerSetting(GameServerSetting setting) { ServerSetting = new ServerSetting(setting.ServerSetting); - GameLogicSetting = new GameLogicSetting(setting.GameLogicSetting); + GameLogicSetting = setting.GameLogicSetting; } // Note: method is called after the object is completely deserialized - constructors are skipped. diff --git a/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs b/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs index e891245d0..0dba104b7 100644 --- a/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs @@ -173,7 +173,8 @@ public override S2CInstanceEnemyKillRes Handle(GameClient client, C2SInstanceEne } } - uint calcExp = _gameServer.ExpManager.GetAdjustedExp(client.GameMode, RewardSource.Enemy, client.Party, enemyKilled.GetDroppedExperience(), enemyKilled.Lv); + uint baseEnemyExp = _gameServer.ExpManager.GetScaledPointAmount(RewardSource.Enemy, ExpType.ExperiencePoints, enemyKilled.GetDroppedExperience()); + uint calcExp = _gameServer.ExpManager.GetAdjustedExp(client.GameMode, RewardSource.Enemy, client.Party, baseEnemyExp, enemyKilled.Lv); uint calcPP = _gameServer.ExpManager.GetScaledPointAmount(RewardSource.Enemy, ExpType.PlayPoints, enemyKilled.GetDroppedPlayPoints()); foreach (PartyMember member in client.Party.Members) diff --git a/Arrowgene.Ddon.GameServer/Handler/NpcGetExtendedFacilityHandler.cs b/Arrowgene.Ddon.GameServer/Handler/NpcGetExtendedFacilityHandler.cs index 8733982ce..a91af35a4 100644 --- a/Arrowgene.Ddon.GameServer/Handler/NpcGetExtendedFacilityHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/NpcGetExtendedFacilityHandler.cs @@ -1,13 +1,6 @@ using Arrowgene.Ddon.Server; -using Arrowgene.Ddon.Shared.Asset; using Arrowgene.Ddon.Shared.Entity.PacketStructure; -using Arrowgene.Ddon.Shared.Entity.Structure; -using Arrowgene.Ddon.Shared.Model; -using Arrowgene.Ddon.Shared.Model.Quest; using Arrowgene.Logging; -using System; -using System.Collections.Generic; -using System.Data.Entity.Core.Metadata.Edm; namespace Arrowgene.Ddon.GameServer.Handler { @@ -22,86 +15,17 @@ public NpcGetExtendedFacilityHandler(DdonGameServer server) : base(server) public override S2CNpcGetNpcExtendedFacilityRes Handle(GameClient client, C2SNpcGetNpcExtendedFacilityReq request) { var result = new S2CNpcGetNpcExtendedFacilityRes(); - if (gNpcExtendedBehavior.ContainsKey(request.NpcId)) + + var npcExtendedFacilities = Server.ScriptManager.NpcExtendedFacilityModule.NpcExtendedFacilities; + if (npcExtendedFacilities.ContainsKey(request.NpcId)) { result.NpcId = request.NpcId; - gNpcExtendedBehavior[request.NpcId](Server, client, result); + npcExtendedFacilities[request.NpcId].GetExtendedOptions(Server, client, result); } - return result; - } - private static bool CheckUnlockConditions(DdonGameServer server, GameClient client, EpitaphBarrier barrier) - { - foreach (var sectionId in barrier.DependentSectionIds) - { - var sectionInfo = server.EpitaphRoadManager.GetSectionById(sectionId); - foreach (var unlockId in sectionInfo.UnlockDependencies) - { - if (!client.Character.EpitaphRoadState.UnlockedContent.Contains(unlockId)) - { - return false; - } - } - } - return true; + return result; } - private static readonly Dictionary> gNpcExtendedBehavior = new Dictionary>() - { - [NpcId.Pehr1] = (DdonGameServer server, GameClient client, S2CNpcGetNpcExtendedFacilityRes result) => - { - if (client.Character.CompletedQuests.ContainsKey((QuestId) 60300020) || (client.QuestState.IsQuestActive(60300020) && client.QuestState.GetQuestState(60300020).Step > 2)) - { - result.ExtendedMenuItemList.Add(new CDataNpcExtendedFacilityMenuItem() { FunctionClass = NpcFunction.WarMissions, FunctionSelect = NpcFunction.HeroicSpiritSleepingPath, Unk2 = 4452 }); - } - }, - [NpcId.Anita1] = (DdonGameServer server, GameClient client, S2CNpcGetNpcExtendedFacilityRes result) => - { - var barrier = server.EpitaphRoadManager.GetBarrier(NpcId.Anita1); - if (!client.Character.EpitaphRoadState.UnlockedContent.Contains(barrier.EpitaphId)) - { - if (!CheckUnlockConditions(server, client, barrier)) - { - return; - } - result.ExtendedMenuItemList.Add(new CDataNpcExtendedFacilityMenuItem() { FunctionClass = NpcFunction.WarMissions, FunctionSelect = NpcFunction.GiveSpirits }); - } - }, - [NpcId.Isel1] = (DdonGameServer server, GameClient client, S2CNpcGetNpcExtendedFacilityRes result) => - { - var barrier = server.EpitaphRoadManager.GetBarrier(NpcId.Isel1); - if (!client.Character.EpitaphRoadState.UnlockedContent.Contains(barrier.EpitaphId)) - { - if (!CheckUnlockConditions(server, client, barrier)) - { - return; - } - result.ExtendedMenuItemList.Add(new CDataNpcExtendedFacilityMenuItem() { FunctionClass = NpcFunction.WarMissions, FunctionSelect = NpcFunction.GiveSpirits }); - } - }, - [NpcId.Damad1] = (DdonGameServer server, GameClient client, S2CNpcGetNpcExtendedFacilityRes result) => - { - var barrier = server.EpitaphRoadManager.GetBarrier(NpcId.Damad1); - if (!client.Character.EpitaphRoadState.UnlockedContent.Contains(barrier.EpitaphId)) - { - if (!CheckUnlockConditions(server, client, barrier)) - { - return; - } - result.ExtendedMenuItemList.Add(new CDataNpcExtendedFacilityMenuItem() { FunctionClass = NpcFunction.WarMissions, FunctionSelect = NpcFunction.GiveSpirits }); - } - } -#if false - // NPC which controls entrance to Memory of Megadosys - // Currently commented out since area is not completed and personal quest is missing - [NpcId.Morgan] = new List() - { - // Memory of Megadosys - new CDataNpcExtendedFacilityMenuItem() { FunctionClass = NpcFunction.WarMissions, FunctionSelect = NpcFunction.HeroicSpiritSleepingPath, Unk2 = 4452} - }, -#endif - }; - private readonly byte[] pcap_data = new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xC2, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x11, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x43, 0x20, 0xFB, 0xE8, 0xC0, 0xA0, 0xEC}; } } diff --git a/Arrowgene.Ddon.GameServer/Quests/GenericQuest.cs b/Arrowgene.Ddon.GameServer/Quests/GenericQuest.cs index 081c757ec..73b430bf2 100644 --- a/Arrowgene.Ddon.GameServer/Quests/GenericQuest.cs +++ b/Arrowgene.Ddon.GameServer/Quests/GenericQuest.cs @@ -16,7 +16,7 @@ public class GenericQuest : Quest public static GenericQuest FromAsset(DdonGameServer server, QuestAssetData questAsset) { - var quest = new GenericQuest(questAsset.QuestId, questAsset.QuestScheduleId, questAsset.Type, questAsset.Discoverable); + var quest = new GenericQuest(server, questAsset.QuestId, questAsset.QuestScheduleId, questAsset.Type, questAsset.Discoverable); quest.QuestAreaId = questAsset.QuestAreaId; quest.NewsImageId = questAsset.NewsImageId; @@ -40,21 +40,19 @@ public static GenericQuest FromAsset(DdonGameServer server, QuestAssetData quest foreach (var pointReward in questAsset.PointRewards) { - var reward = server.ExpManager.GetScaledPointAmount(RewardSource.Quest, pointReward.ExpType, pointReward.ExpReward); quest.ExpRewards.Add(new CDataQuestExp() { Type = pointReward.ExpType, - Reward = reward + Reward = pointReward.ExpReward }); } foreach (var walletReward in questAsset.RewardCurrency) { - var amount = server.WalletManager.GetScaledWalletAmount(walletReward.WalletType, walletReward.Amount); quest.WalletRewards.Add(new CDataWalletPoint() { Type = walletReward.WalletType, - Value = amount + Value = walletReward.Amount }); } @@ -133,7 +131,8 @@ public static GenericQuest FromAsset(DdonGameServer server, QuestAssetData quest return quest; } - public GenericQuest(QuestId questId, uint questScheduleId, QuestType questType, bool discoverable) : base(questId, questScheduleId, questType, discoverable) + public GenericQuest(DdonGameServer server, QuestId questId, uint questScheduleId, QuestType questType, bool discoverable) : + base(server, questId, questScheduleId, questType, discoverable) { QuestLayoutFlagSetInfo = new List(); } diff --git a/Arrowgene.Ddon.GameServer/Quests/MainQuests/Mq030260_HopesBitterEnd.cs b/Arrowgene.Ddon.GameServer/Quests/MainQuests/Mq030260_HopesBitterEnd.cs index f6b356774..3be193595 100644 --- a/Arrowgene.Ddon.GameServer/Quests/MainQuests/Mq030260_HopesBitterEnd.cs +++ b/Arrowgene.Ddon.GameServer/Quests/MainQuests/Mq030260_HopesBitterEnd.cs @@ -23,7 +23,7 @@ public class Mq030260_HopesBitterEnd : Quest { private static readonly ServerLogger Logger = LogProvider.Logger(typeof(Mq030260_HopesBitterEnd)); - public Mq030260_HopesBitterEnd() : base(QuestId.HopesBitterEnd, (uint) QuestId.HopesBitterEnd, QuestType.Main) + public Mq030260_HopesBitterEnd() : base(null, QuestId.HopesBitterEnd, (uint) QuestId.HopesBitterEnd, QuestType.Main) { } diff --git a/Arrowgene.Ddon.GameServer/Quests/Quest.cs b/Arrowgene.Ddon.GameServer/Quests/Quest.cs index 25f4a70e4..b93fe313f 100644 --- a/Arrowgene.Ddon.GameServer/Quests/Quest.cs +++ b/Arrowgene.Ddon.GameServer/Quests/Quest.cs @@ -56,7 +56,7 @@ public class QuestEnemyHunt public abstract class Quest { private static readonly ServerLogger Logger = LogProvider.Logger(typeof(Quest)); - + private DdonGameServer Server { get; set; } protected List Processes { get; set; } public readonly QuestId QuestId; public readonly bool IsDiscoverable; @@ -73,8 +73,8 @@ public abstract class Quest public bool SaveWorkAsStep { get; protected set; } public List OrderConditions { get; protected set; } public QuestRewardParams RewardParams { get; protected set; } - public List WalletRewards { get; protected set; } - public List ExpRewards { get; protected set; } + protected List WalletRewards { get; set; } + protected List ExpRewards { get; set; } public List ItemRewards { get; protected set; } public List SelectableRewards { get; protected set; } public List ContentsReleaseRewards { get; protected set; } @@ -97,9 +97,37 @@ public bool IsPersonal { get || QuestType == QuestType.Tutorial; } } + public List ScaledWalletRewards() + { + var result = new List(); + foreach (var walletPoint in WalletRewards) + { + result.Add(new CDataWalletPoint() + { + Type = walletPoint.Type, + Value = Server.WalletManager.GetScaledWalletAmount(walletPoint.Type, walletPoint.Value) + }); + } + return result; + } + + public List ScaledExpRewards() + { + var result = new List(); + foreach (var pointReward in ExpRewards) + { + result.Add(new CDataQuestExp() + { + Type = pointReward.Type, + Reward = Server.ExpManager.GetScaledPointAmount(RewardSource.Quest, pointReward.Type, pointReward.Reward) + }); + } + return result; + } - public Quest(QuestId questId, uint questScheduleId, QuestType questType, bool isDiscoverable = false) + public Quest(DdonGameServer server, QuestId questId, uint questScheduleId, QuestType questType, bool isDiscoverable = false) { + Server = server; QuestId = questId; QuestType = questType; QuestScheduleId = questScheduleId; @@ -261,8 +289,8 @@ public virtual CDataQuestList ToCDataQuestList(uint step) BaseLevel = BaseLevel, ContentJoinItemRank = MinimumItemRank, IsClientOrder = step > 0, - BaseExp = ExpRewards, - BaseWalletPoints = WalletRewards, + BaseExp = ScaledExpRewards(), + BaseWalletPoints = ScaledWalletRewards(), FixedRewardItemList = GetQuestFixedRewards(), FixedRewardSelectItemList = GetQuestSelectableRewards(), QuestOrderConditionParamList = GetQuestOrderConditions(), @@ -308,8 +336,8 @@ public virtual CDataQuestOrderList ToCDataQuestOrderList(uint step) IsClientOrder = step > 0, IsEnable = true, CanProgress = true, - BaseExp = ExpRewards, - BaseWalletPoints = WalletRewards, + BaseExp = ScaledExpRewards(), + BaseWalletPoints = ScaledWalletRewards(), FixedRewardItem = GetQuestFixedRewards(), FixedRewardSelectItem = GetQuestSelectableRewards(), QuestOrderConditionParam = GetQuestOrderConditions(), diff --git a/Arrowgene.Ddon.GameServer/Quests/QuestStateManager.cs b/Arrowgene.Ddon.GameServer/Quests/QuestStateManager.cs index 2e9643ee7..e7b3448f7 100644 --- a/Arrowgene.Ddon.GameServer/Quests/QuestStateManager.cs +++ b/Arrowgene.Ddon.GameServer/Quests/QuestStateManager.cs @@ -582,7 +582,7 @@ protected PacketQueue SendWalletRewards(DdonGameServer server, GameClient client UpdateType = ItemNoticeType.Quest }; - foreach (var walletReward in quest.WalletRewards) + foreach (var walletReward in quest.ScaledWalletRewards()) { updateCharacterItemNtc.UpdateWalletList.Add(server.WalletManager.AddToWallet( client.Character, @@ -597,7 +597,7 @@ protected PacketQueue SendWalletRewards(DdonGameServer server, GameClient client client.Enqueue(updateCharacterItemNtc, packets); } - foreach (var point in quest.ExpRewards) + foreach (var point in quest.ScaledExpRewards()) { uint amount = CalculateTotalPointAmount(server, client, point); if (amount == 0) diff --git a/Arrowgene.Ddon.GameServer/Scripting/Interfaces/INpcExtendedFacility.cs b/Arrowgene.Ddon.GameServer/Scripting/Interfaces/INpcExtendedFacility.cs new file mode 100644 index 000000000..a497ba35b --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Scripting/Interfaces/INpcExtendedFacility.cs @@ -0,0 +1,21 @@ +using Arrowgene.Ddon.Shared.Entity.PacketStructure; +using Arrowgene.Ddon.Shared.Model; + +namespace Arrowgene.Ddon.GameServer.Scripting.Interfaces +{ + 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/Modules/NpcExtendedFacilityModule.cs b/Arrowgene.Ddon.GameServer/Scripting/Modules/NpcExtendedFacilityModule.cs new file mode 100644 index 000000000..b4c11fce8 --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Scripting/Modules/NpcExtendedFacilityModule.cs @@ -0,0 +1,53 @@ +using Arrowgene.Ddon.GameServer.Scripting.Interfaces; +using Arrowgene.Ddon.Shared; +using Arrowgene.Ddon.Shared.Model; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Scripting; +using System.Collections.Generic; + +namespace Arrowgene.Ddon.GameServer.Scripting +{ + public class NpcExtendedFacilityModule : ScriptModule + { + public override string ModuleRoot => "extended_facilities"; + public override string Filter => "*.csx"; + public override bool ScanSubdirectories => true; + + public Dictionary NpcExtendedFacilities { get; private set; } + + public NpcExtendedFacilityModule() + { + NpcExtendedFacilities = new Dictionary(); + } + + public override ScriptOptions Options() + { + return ScriptOptions.Default + .AddReferences(MetadataReference.CreateFromFile(typeof(DdonGameServer).Assembly.Location)) + .AddReferences(MetadataReference.CreateFromFile(typeof(AssetRepository).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.Characters") + .AddImports("Arrowgene.Ddon.GameServer.Scripting") + .AddImports("Arrowgene.Ddon.GameServer.Scripting.Interfaces") + .AddImports("Arrowgene.Ddon.Shared.Entity.PacketStructure") + .AddImports("Arrowgene.Ddon.Shared.Entity.Structure") + .AddImports("Arrowgene.Ddon.Shared.Model.Quest"); + } + + public override bool EvaluateResult(ScriptState result) + { + if (result == null) + { + return false; + } + + INpcExtendedFacility extendedFacility = (INpcExtendedFacility)result.ReturnValue; + NpcExtendedFacilities[extendedFacility.NpcId] = extendedFacility; + + return true; + } + } +} diff --git a/Arrowgene.Ddon.GameServer/Scripting/ScriptManager.cs b/Arrowgene.Ddon.GameServer/Scripting/ScriptManager.cs new file mode 100644 index 000000000..343ce2668 --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Scripting/ScriptManager.cs @@ -0,0 +1,188 @@ +using Arrowgene.Ddon.Server; +using Arrowgene.Ddon.Shared; +using Arrowgene.Logging; +using Microsoft.CodeAnalysis.CSharp.Scripting; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Arrowgene.Ddon.GameServer.Scripting +{ + public class ScriptManager + { + private static readonly ServerLogger Logger = LogProvider.Logger(typeof(ScriptManager)); + public class GlobalVariables + { + 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); + } + + private DdonGameServer Server { get; } + private string ScriptsRoot { get; } + private GlobalVariables Globals { get; } + + public void Initialize() + { + CompileScripts(); + SetupFileWatchers(); + } + + private void CompileScript(ScriptModule module, string path) + { + try + { + Logger.Info(path); + + var script = CSharpScript.Create( + code: Util.ReadAllText(path), + options: module.Options(), + globalsType: typeof(GlobalVariables) + ); + + var result = script.RunAsync(Globals).Result; + if (!module.EvaluateResult(result)) + { + Logger.Error($"Failed to evaluate the result of executing '{path}'"); + } + } + catch (Exception ex) + { + Logger.Error($"Failed to compile and execute '{path}'. Skipping."); + Logger.Error(ex.ToString()); + } + } + + private void CompileScripts() + { + foreach (var module in ScriptModules.Values) + { + var path = $"{ScriptsRoot}\\{module.ModuleRoot}"; + + Logger.Info($"Compiling scripts for module '{module.ModuleRoot}'"); + foreach (var file in Directory.EnumerateFiles(path)) + { + module.Scripts.Add(file); + CompileScript(module, file); + } + } + } + + private void SetupFileWatchers() + { + foreach (var module in ScriptModules.Values) + { + var watcher = new FileSystemWatcher($"{ScriptsRoot}\\{module.ModuleRoot}"); + watcher.Filter = module.Filter; + watcher.NotifyFilter = (NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.CreationTime); + watcher.IncludeSubdirectories = module.ScanSubdirectories; + + watcher.Changed += OnChanged; + watcher.Created += OnCreate; + watcher.Error += OnError; + + module.Watcher = watcher; + } + + // Enable all the watchers + foreach (var module in ScriptModules.Values) + { + module.Watcher.EnableRaisingEvents = true; + } + } + + private void OnChanged(object sender, FileSystemEventArgs e) + { + if (e.ChangeType != WatcherChangeTypes.Changed) + { + return; + } + + Logger.Info($"Reloading {e.FullPath}"); + + ScriptModule module = null; + foreach (var m in ScriptModules.Values) + { + if (m.Scripts.Contains(e.FullPath)) + { + module = m; + break; + } + } + + if (module == null) + { + // No module associated with this script file + return; + } + + try + { + module.Watcher.EnableRaisingEvents = false; + CompileScript(module, e.FullPath); + } + finally + { + module.Watcher.EnableRaisingEvents = true; + } + } + + private void OnCreate(object sender, FileSystemEventArgs e) + { + var module = ScriptUtils.FindModule(e.FullPath, ScriptModules); + if (module == null) + { + Logger.Error($"Unable to find module associated with '{e.FullPath}'. You may need to reload the server or fix error."); + return; + } + + // Add this file to be tracked for the module + module.Scripts.Add(e.FullPath); + + Logger.Info($"Compiling script for module '{module.ModuleRoot}'"); + try + { + module.Watcher.EnableRaisingEvents = false; + CompileScript(module, e.FullPath); + } + finally + { + module.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/ScriptModule.cs b/Arrowgene.Ddon.GameServer/Scripting/ScriptModule.cs new file mode 100644 index 000000000..f37f24ee3 --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Scripting/ScriptModule.cs @@ -0,0 +1,34 @@ +using Microsoft.CodeAnalysis.Scripting; +using System.Collections.Generic; +using System.IO; + +namespace Arrowgene.Ddon.GameServer.Scripting +{ + public abstract class ScriptModule + { + public abstract string ModuleRoot { get; } + public abstract string Filter { get; } + public abstract bool ScanSubdirectories { get; } + public FileSystemWatcher Watcher { get; set; } + + public HashSet Scripts { get; set; } + + public ScriptModule() + { + Scripts = new HashSet(); + } + + /// + /// Options passed into the compilation unit. + /// + /// + public abstract ScriptOptions Options(); + + /// + /// Evaluates the result returned by the script. + /// + /// + /// + public abstract bool EvaluateResult(ScriptState result); + } +} diff --git a/Arrowgene.Ddon.GameServer/Scripting/ScriptUtils.cs b/Arrowgene.Ddon.GameServer/Scripting/ScriptUtils.cs new file mode 100644 index 000000000..1634879cf --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Scripting/ScriptUtils.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Arrowgene.Ddon.GameServer.Scripting +{ + public class ScriptUtils + { + public static ScriptModule FindModule(string path, Dictionary modules) + { + string[] directories = Path.GetDirectoryName(path).Split(Path.DirectorySeparatorChar); + + foreach (var directory in directories.Reverse()) + { + if (modules.ContainsKey(directory)) + { + return modules[directory]; + } + } + + return null; + } + } +} diff --git a/Arrowgene.Ddon.LoginServer/DdonLoginServer.cs b/Arrowgene.Ddon.LoginServer/DdonLoginServer.cs index f581c0047..6dd09507d 100644 --- a/Arrowgene.Ddon.LoginServer/DdonLoginServer.cs +++ b/Arrowgene.Ddon.LoginServer/DdonLoginServer.cs @@ -41,7 +41,7 @@ public DdonLoginServer(LoginServerSetting setting, GameLogicSetting gameSetting, : base(ServerType.Login, setting.ServerSetting, database, assetRepository) { Setting = new LoginServerSetting(setting); - GameSetting = new GameLogicSetting(gameSetting); + GameSetting = gameSetting; ClientLookup = new LoginClientLookup(); LoadPacketHandler(); } diff --git a/Arrowgene.Ddon.Server/GameLogicSetting.cs b/Arrowgene.Ddon.Server/GameLogicSetting.cs index 3b67942ab..05cdcaba6 100644 --- a/Arrowgene.Ddon.Server/GameLogicSetting.cs +++ b/Arrowgene.Ddon.Server/GameLogicSetting.cs @@ -5,25 +5,21 @@ namespace Arrowgene.Ddon.Server { - [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; } /// @@ -31,7 +27,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; } /// @@ -39,401 +34,235 @@ 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; } + public double EnemyExpModifier { get; set; } /// /// Global modifier for quest exp calculations to scale up or down. /// - - [DataMember(Order = 28)] public double QuestExpModifier { get; set; } + public double QuestExpModifier { get; set; } /// /// Global modifier for pp calculations to scale up or down. /// - [DataMember(Order = 29)] public double PpModifier { get; set; } + public double PpModifier { get; set; } /// /// Global modifier for Gold calculations to scale up or down. /// - [DataMember(Order = 30)] public double GoldModifier { get; set; } + public double GoldModifier { get; set; } /// /// Global modifier for Rift calculations to scale up or down. /// - [DataMember(Order = 31)] public double RiftModifier { get; set; } + public double RiftModifier { get; set; } /// /// Global modifier for BO calculations to scale up or down. /// - [DataMember(Order = 32)] public double BoModifier { get; set; } + public double BoModifier { get; set; } /// /// Global modifier for HO calculations to scale up or down. /// - [DataMember(Order = 33)] public double HoModifier { get; set; } + public double HoModifier { get; set; } /// /// Global modifier for JP calculations to scale up or down. /// - [DataMember(Order = 34)] public double JpModifier { get; set; } + public double JpModifier { get; set; } /// /// Configures the maximum amount of reward box slots. /// - [DataMember(Order = 35)] public byte RewardBoxMax { get; set; } + 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; } + public byte QuestOrderMax { get; set; } /// /// Configures if epitaph rewards are limited once per weekly reset. /// - [DataMember(Order = 37)] public bool EnableEpitaphWeeklyRewards { get; set; } + 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() { - SetDefaultValues(); } - void SetDefaultValues() - { - 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}/"; - } - - [OnDeserializing] - void OnDeserializing(StreamingContext context) - { - SetDefaultValues(); - } - - public GameLogicSetting(GameLogicSetting setting) - { - LaternBurnTimeInSeconds = setting.LaternBurnTimeInSeconds; - AdditionalProductionSpeedFactor = setting.AdditionalProductionSpeedFactor; - AdditionalCostPerformanceFactor = setting.AdditionalCostPerformanceFactor; - RookiesRingMaxLevel = setting.RookiesRingMaxLevel; - RookiesRingBonus = setting.RookiesRingBonus; - NaiveLobbyContextHandling = setting.NaiveLobbyContextHandling; - CraftConsumableProductionTimesMax = setting.CraftConsumableProductionTimesMax; - AdjustPartyEnemyExp = setting.AdjustPartyEnemyExp; - AdjustPartyEnemyExpTiers = setting.AdjustPartyEnemyExpTiers; - AdjustTargetLvEnemyExp = setting.AdjustTargetLvEnemyExp; - AdjustTargetLvEnemyExpTiers = setting.AdjustTargetLvEnemyExpTiers; - GameClockTimescale = setting.GameClockTimescale; - WeatherSequenceLength = setting.WeatherSequenceLength; - WeatherStatistics = setting.WeatherStatistics; - EnablePawnCatchup = setting.EnablePawnCatchup; - PawnCatchupMultiplier = setting.PawnCatchupMultiplier; - PawnCatchupLvDiff = setting.PawnCatchupLvDiff; - DisableExpCorrectionForMyPawn = setting.DisableExpCorrectionForMyPawn; - PlayPointMax = setting.PlayPointMax; - JobLevelMax = setting.JobLevelMax; - ClanMemberMax = setting.ClanMemberMax; - CharacterNumMax = setting.CharacterNumMax; - EnableVisualEquip = setting.EnableVisualEquip; - FriendListMax = setting.FriendListMax; - WalletLimits = setting.WalletLimits; - DefaultMaxBazaarExhibits = setting.DefaultMaxBazaarExhibits; - DefaultWarpFavorites = setting.DefaultWarpFavorites; - - EnemyExpModifier = setting.EnemyExpModifier; - QuestExpModifier = setting.QuestExpModifier; - PpModifier = setting.PpModifier; - GoldModifier = setting.GoldModifier; - RiftModifier = setting.RiftModifier; - BoModifier = setting.BoModifier; - HoModifier = setting.HoModifier; - JpModifier = setting.JpModifier; - RewardBoxMax = setting.RewardBoxMax; - QuestOrderMax = setting.QuestOrderMax; - - EnableEpitaphWeeklyRewards = setting.EnableEpitaphWeeklyRewards; - EnableMainPartyPawnsQuestRewards = setting.EnableMainPartyPawnsQuestRewards; - - BazaarExhibitionTimeSeconds = setting.BazaarExhibitionTimeSeconds; - BazaarCooldownTimeSeconds = setting.BazaarCooldownTimeSeconds; - - UrlManual = setting.UrlManual; - UrlShopDetail = setting.UrlShopDetail; - UrlShopCounterA = setting.UrlShopCounterA; - UrlShopAttention = setting.UrlShopAttention; - UrlShopStoneLimit = setting.UrlShopStoneLimit; - UrlShopCounterB = setting.UrlShopCounterB; - UrlChargeCallback = setting.UrlChargeCallback; - UrlChargeA = setting.UrlChargeA; - UrlSample9 = setting.UrlSample9; - UrlSample10 = setting.UrlSample10; - UrlCampaignBanner = setting.UrlCampaignBanner; - UrlSupportIndex = setting.UrlSupportIndex; - UrlPhotoupAuthorize = setting.UrlPhotoupAuthorize; - UrlApiA = setting.UrlApiA; - UrlApiB = setting.UrlApiB; - UrlIndex = setting.UrlIndex; - UrlCampaign = setting.UrlCampaign; - UrlChargeB = setting.UrlChargeB; - UrlCompanionImage = setting.UrlCompanionImage; - } - - // Note: method is called after the object is completely deserialized - constructors are skipped. - [OnDeserialized] - void OnDeserialized(StreamingContext context) + void ValidateSettings() { if (RookiesRingBonus < 0) { @@ -491,34 +320,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.Server/ScriptedServerSettings.cs b/Arrowgene.Ddon.Server/ScriptedServerSettings.cs new file mode 100644 index 000000000..1a29d2613 --- /dev/null +++ b/Arrowgene.Ddon.Server/ScriptedServerSettings.cs @@ -0,0 +1,146 @@ +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; +using System.Threading; + +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; + private GameLogicSetting GameLogicSetting; + public FileSystemWatcher Watcher { get; private set; } + + public Script GameLogicSettings + { + get + { + lock (CompiledScripts) + { + return CompiledScripts["GameLogicSettings"]; + } + } + set + { + lock (CompiledScripts) + { + CompiledScripts["GameLogicSettings"] = value; + } + } + } + + public ScriptedServerSettings(GameLogicSetting gameLogicSetting, string AssetsPath) + { + ScriptsRoot = $"{AssetsPath}\\scripts"; + + GameLogicSetting = 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.Shared/Arrowgene.Ddon.Shared.csproj b/Arrowgene.Ddon.Shared/Arrowgene.Ddon.Shared.csproj index 94100155d..39890da50 100644 --- a/Arrowgene.Ddon.Shared/Arrowgene.Ddon.Shared.csproj +++ b/Arrowgene.Ddon.Shared/Arrowgene.Ddon.Shared.csproj @@ -22,6 +22,7 @@ + @@ -31,4 +32,37 @@ Files\%(RecursiveDir)%(Filename)%(Extension) + + + + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + 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/GameLogicSettings.csx b/Arrowgene.Ddon.Shared/Files/Assets/scripts/GameLogicSettings.csx new file mode 100644 index 000000000..128cde1ac --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/scripts/GameLogicSettings.csx @@ -0,0 +1,143 @@ +/** + * 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 +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/Anita1.csx b/Arrowgene.Ddon.Shared/Files/Assets/scripts/extended_facilities/Anita1.csx new file mode 100644 index 000000000..a85a22c88 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/scripts/extended_facilities/Anita1.csx @@ -0,0 +1,22 @@ +public class NpcExtendedFacility : INpcExtendedFacility +{ + public NpcExtendedFacility() + { + NpcId = NpcId.Anita1; + } + + public override void GetExtendedOptions(DdonGameServer server, GameClient client, S2CNpcGetNpcExtendedFacilityRes result) + { + var barrier = server.EpitaphRoadManager.GetBarrier(NpcId); + if (!client.Character.EpitaphRoadState.UnlockedContent.Contains(barrier.EpitaphId)) + { + if (!server.EpitaphRoadManager.CheckUnlockConditions(client, barrier)) + { + return; + } + result.ExtendedMenuItemList.Add(new CDataNpcExtendedFacilityMenuItem() { FunctionClass = NpcFunction.WarMissions, FunctionSelect = NpcFunction.GiveSpirits }); + } + } +} + +return new NpcExtendedFacility(); diff --git a/Arrowgene.Ddon.Shared/Files/Assets/scripts/extended_facilities/Damad1.csx b/Arrowgene.Ddon.Shared/Files/Assets/scripts/extended_facilities/Damad1.csx new file mode 100644 index 000000000..65af77410 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/scripts/extended_facilities/Damad1.csx @@ -0,0 +1,22 @@ +public class NpcExtendedFacility : INpcExtendedFacility +{ + public NpcExtendedFacility() + { + NpcId = NpcId.Damad1; + } + + public override void GetExtendedOptions(DdonGameServer server, GameClient client, S2CNpcGetNpcExtendedFacilityRes result) + { + var barrier = server.EpitaphRoadManager.GetBarrier(NpcId); + if (!client.Character.EpitaphRoadState.UnlockedContent.Contains(barrier.EpitaphId)) + { + if (!server.EpitaphRoadManager.CheckUnlockConditions(client, barrier)) + { + return; + } + result.ExtendedMenuItemList.Add(new CDataNpcExtendedFacilityMenuItem() { FunctionClass = NpcFunction.WarMissions, FunctionSelect = NpcFunction.GiveSpirits }); + } + } +} + +return new NpcExtendedFacility(); diff --git a/Arrowgene.Ddon.Shared/Files/Assets/scripts/extended_facilities/Isel1.csx b/Arrowgene.Ddon.Shared/Files/Assets/scripts/extended_facilities/Isel1.csx new file mode 100644 index 000000000..11d1cb6d4 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/scripts/extended_facilities/Isel1.csx @@ -0,0 +1,22 @@ +public class NpcExtendedFacility : INpcExtendedFacility +{ + public NpcExtendedFacility() + { + NpcId = NpcId.Isel1; + } + + public override void GetExtendedOptions(DdonGameServer server, GameClient client, S2CNpcGetNpcExtendedFacilityRes result) + { + var barrier = server.EpitaphRoadManager.GetBarrier(NpcId); + if (!client.Character.EpitaphRoadState.UnlockedContent.Contains(barrier.EpitaphId)) + { + if (!server.EpitaphRoadManager.CheckUnlockConditions(client, barrier)) + { + return; + } + result.ExtendedMenuItemList.Add(new CDataNpcExtendedFacilityMenuItem() { FunctionClass = NpcFunction.WarMissions, FunctionSelect = NpcFunction.GiveSpirits }); + } + } +} + +return new NpcExtendedFacility(); 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..1c311a495 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/scripts/extended_facilities/Pehr1.csx @@ -0,0 +1,17 @@ +public class NpcExtendedFacility : INpcExtendedFacility +{ + public NpcExtendedFacility() + { + NpcId = NpcId.Pehr1; + } + + public override void GetExtendedOptions(DdonGameServer server, GameClient client, S2CNpcGetNpcExtendedFacilityRes result) + { + if (client.Character.CompletedQuests.ContainsKey((QuestId) 60300020) || (client.QuestState.IsQuestActive(60300020) && client.QuestState.GetQuestState(60300020).Step > 2)) + { + result.ExtendedMenuItemList.Add(new CDataNpcExtendedFacilityMenuItem() { FunctionClass = NpcFunction.WarMissions, FunctionSelect = NpcFunction.HeroicSpiritSleepingPath, Unk2 = 4452 }); + } + } +} + +return new NpcExtendedFacility(); diff --git a/Arrowgene.Ddon.Shared/Util.cs b/Arrowgene.Ddon.Shared/Util.cs index 35150a58f..171f59700 100644 --- a/Arrowgene.Ddon.Shared/Util.cs +++ b/Arrowgene.Ddon.Shared/Util.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; @@ -446,5 +446,25 @@ public static string ToArcPath(string path) path = path.Replace(Path.AltDirectorySeparatorChar, '\\'); return path; } + + /// + /// Implements a safer way to read all lines in a file when there is contention on the file access. + /// + /// Path to a text based file to read + /// Returns the contents of the file read as a string + public static string ReadAllText(string path) + { + List content = new List(); + using (FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + using (StreamReader reader = new StreamReader(stream)) + { + while (!reader.EndOfStream) + { + content.Add(reader.ReadLine()); + } + } + + return string.Join("\n", content); + } } } diff --git a/Arrowgene.Ddon.Test/GameServer/Characters/CraftManagerTest.cs b/Arrowgene.Ddon.Test/GameServer/Characters/CraftManagerTest.cs index d6d0db9af..b958e0fbf 100644 --- a/Arrowgene.Ddon.Test/GameServer/Characters/CraftManagerTest.cs +++ b/Arrowgene.Ddon.Test/GameServer/Characters/CraftManagerTest.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Arrowgene.Ddon.GameServer.Scripting; using Arrowgene.Ddon.Shared; using Arrowgene.Ddon.Shared.Entity.Structure; using Arrowgene.Ddon.Shared.Model; @@ -14,7 +15,17 @@ public class CraftManagerTest public CraftManagerTest() { - _mockServer = new DdonGameServer(new GameServerSetting(), new MockDatabase(), new AssetRepository("TestFiles")); + var settings = new GameServerSetting(); + settings.GameLogicSetting.GameClockTimescale = 90; + settings.GameLogicSetting.WeatherSequenceLength = 20; + settings.GameLogicSetting.WeatherStatistics = new List<(uint MeanLength, uint Weight)>() + { + (60 * 30, 1), // Fair + (60 * 30, 1), // Cloudy + (60 * 30, 1), // Rainy + }; + + _mockServer = new DdonGameServer(settings, new MockDatabase(), new AssetRepository("TestFiles")); _craftManager = new CraftManager(_mockServer); } diff --git a/Arrowgene.Ddon.Test/GameServer/SettingTest.cs b/Arrowgene.Ddon.Test/GameServer/SettingTest.cs index 9f13ea76c..07c58a52c 100644 --- a/Arrowgene.Ddon.Test/GameServer/SettingTest.cs +++ b/Arrowgene.Ddon.Test/GameServer/SettingTest.cs @@ -13,9 +13,6 @@ public void Serialize_DefaultSetting_ObjectShouldMatchJson() string json = Setting.Serialize(setting); Assert.Contains("\"LogPath\": \"Logs\"", json); - Assert.Contains("\"GameLogicSetting\":", json); - Assert.Contains("\"AdditionalProductionSpeedFactor\": 1", json); - Assert.Contains("\"AdditionalCostPerformanceFactor\": 1", json); } [Fact] @@ -29,10 +26,6 @@ public void Deserialize_ValidJson_ShouldReturnCorrectObject() ""Id"": 10, ""Name"": ""CustomServerName"", ""ServerPort"": 42000 - }, - ""GameLogicSetting"": { - ""AdditionalProductionSpeedFactor"": 1.5, - ""AdditionalCostPerformanceFactor"": 1.2 } } }"; @@ -43,8 +36,6 @@ public void Deserialize_ValidJson_ShouldReturnCorrectObject() Assert.Equal("C:\\Assets", setting.AssetPath); Assert.Equal("CustomServerName", setting.GameServerSetting.ServerSetting.Name); Assert.Equal(42000, setting.GameServerSetting.ServerSetting.ServerPort); - Assert.Equal(1.5, setting.GameServerSetting.GameLogicSetting.AdditionalProductionSpeedFactor); - Assert.Equal(1.2, setting.GameServerSetting.GameLogicSetting.AdditionalCostPerformanceFactor); } [Fact] @@ -58,7 +49,6 @@ public void Deserialize_MissingFields_ShouldAssignDefaultValues() Assert.NotNull(setting.GameServerSetting); Assert.Equal("Game", setting.GameServerSetting.ServerSetting.Name); Assert.Equal(52000, setting.GameServerSetting.ServerSetting.ServerPort); - Assert.Equal(1.0, setting.GameServerSetting.GameLogicSetting.AdditionalProductionSpeedFactor); } [Fact] @@ -69,8 +59,6 @@ public void RoundTrip_SerializeAndDeserialize_ShouldMaintainObjectState() string json = Setting.Serialize(originalSetting); Setting deserializedSetting = Setting.Deserialize(json); - - Assert.Equal(2.0, deserializedSetting.GameServerSetting.GameLogicSetting.AdditionalProductionSpeedFactor); Assert.Equal(originalSetting.LogPath, deserializedSetting.LogPath); } } diff --git a/Arrowgene.DragonsDogmaOnline.sln b/Arrowgene.DragonsDogmaOnline.sln index d5d35f3bd..95bce9303 100644 --- a/Arrowgene.DragonsDogmaOnline.sln +++ b/Arrowgene.DragonsDogmaOnline.sln @@ -1,29 +1,29 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30204.135 +# Visual Studio Version 17 +VisualStudioVersion = 17.10.35013.160 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Arrowgene.Ddon.LoginServer", "Arrowgene.Ddon.LoginServer\Arrowgene.Ddon.LoginServer.csproj", "{5C0F18BB-B735-4284-82A2-27506DFC9E56}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arrowgene.Ddon.LoginServer", "Arrowgene.Ddon.LoginServer\Arrowgene.Ddon.LoginServer.csproj", "{5C0F18BB-B735-4284-82A2-27506DFC9E56}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Arrowgene.Ddon.Cli", "Arrowgene.Ddon.Cli\Arrowgene.Ddon.Cli.csproj", "{72B2D640-DEA0-4BA7-A3B5-899954BFBBF5}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arrowgene.Ddon.Cli", "Arrowgene.Ddon.Cli\Arrowgene.Ddon.Cli.csproj", "{72B2D640-DEA0-4BA7-A3B5-899954BFBBF5}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Arrowgene.Ddon.WebServer", "Arrowgene.Ddon.WebServer\Arrowgene.Ddon.WebServer.csproj", "{B6813ECD-CF44-4899-838B-94D5F85B39A6}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arrowgene.Ddon.WebServer", "Arrowgene.Ddon.WebServer\Arrowgene.Ddon.WebServer.csproj", "{B6813ECD-CF44-4899-838B-94D5F85B39A6}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Arrowgene.Ddon.Database", "Arrowgene.Ddon.Database\Arrowgene.Ddon.Database.csproj", "{E8FD9C06-57D7-4117-97AC-DE7779A73FB1}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arrowgene.Ddon.Database", "Arrowgene.Ddon.Database\Arrowgene.Ddon.Database.csproj", "{E8FD9C06-57D7-4117-97AC-DE7779A73FB1}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Arrowgene.Ddon.Shared", "Arrowgene.Ddon.Shared\Arrowgene.Ddon.Shared.csproj", "{840B5617-2B22-461F-96F5-9062303F08E0}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arrowgene.Ddon.Shared", "Arrowgene.Ddon.Shared\Arrowgene.Ddon.Shared.csproj", "{840B5617-2B22-461F-96F5-9062303F08E0}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Arrowgene.Ddon.Server", "Arrowgene.Ddon.Server\Arrowgene.Ddon.Server.csproj", "{7D39A3F8-4E2E-4E6F-9136-86EC13D64328}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arrowgene.Ddon.Server", "Arrowgene.Ddon.Server\Arrowgene.Ddon.Server.csproj", "{7D39A3F8-4E2E-4E6F-9136-86EC13D64328}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Arrowgene.Ddon.GameServer", "Arrowgene.Ddon.GameServer\Arrowgene.Ddon.GameServer.csproj", "{87B8E9EB-CCCF-481F-8D01-74676ED62AAF}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arrowgene.Ddon.GameServer", "Arrowgene.Ddon.GameServer\Arrowgene.Ddon.GameServer.csproj", "{87B8E9EB-CCCF-481F-8D01-74676ED62AAF}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Arrowgene.Ddon.Test", "Arrowgene.Ddon.Test\Arrowgene.Ddon.Test.csproj", "{86D48D8F-AA02-4BB8-9B5C-1313C8310F5F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arrowgene.Ddon.Test", "Arrowgene.Ddon.Test\Arrowgene.Ddon.Test.csproj", "{86D48D8F-AA02-4BB8-9B5C-1313C8310F5F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Arrowgene.Ddon.Rpc", "Arrowgene.Ddon.Rpc\Arrowgene.Ddon.Rpc.csproj", "{2DE5385B-B8ED-491D-AC6C-895DC154C79E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arrowgene.Ddon.Rpc", "Arrowgene.Ddon.Rpc\Arrowgene.Ddon.Rpc.csproj", "{2DE5385B-B8ED-491D-AC6C-895DC154C79E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Arrowgene.Ddon.Rpc.Web", "Arrowgene.Ddon.Rpc.Web\Arrowgene.Ddon.Rpc.Web.csproj", "{A98B686D-6C3D-473D-B69D-940ED343D7AF}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arrowgene.Ddon.Rpc.Web", "Arrowgene.Ddon.Rpc.Web\Arrowgene.Ddon.Rpc.Web.csproj", "{A98B686D-6C3D-473D-B69D-940ED343D7AF}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Arrowgene.Ddon.Client", "Arrowgene.Ddon.Client\Arrowgene.Ddon.Client.csproj", "{DAC06C22-BD06-4592-A60A-3990B85EF7A8}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arrowgene.Ddon.Client", "Arrowgene.Ddon.Client\Arrowgene.Ddon.Client.csproj", "{DAC06C22-BD06-4592-A60A-3990B85EF7A8}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution From 1410825163f8b0395739590628da6faadc296160 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sat, 21 Dec 2024 16:26:22 -0500 Subject: [PATCH 2/6] code review feedback Replaced File.ReadAllText with Util.ReadAllText. --- .../AssetReader/BitterblackMazeAssetDeserializer.cs | 2 +- .../AssetReader/BonusDungeonAssetDeserializer.cs | 2 +- .../AssetReader/CostExpScalingAssetDeserializer.cs | 2 +- .../AssetReader/EnemySpawnAssetDeserializer.cs | 2 +- .../AssetReader/EpitaphRoadAssertDeserializer.cs | 2 +- .../AssetReader/EpitaphTrialAssetDeserializer.cs | 2 +- .../AssetReader/EventDropAssetDeserializer.cs | 2 +- Arrowgene.Ddon.Shared/AssetReader/GPCourseInfoDeserializer.cs | 2 +- .../AssetReader/LearnedNormalSkillsDeserializer.cs | 2 +- .../AssetReader/NamedParamAssetDeserializer.cs | 4 ++-- .../AssetReader/PawnCostReductionAssetDeserializer.cs | 2 +- Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs | 2 +- .../AssetReader/QuestDropAssetDeserializer.cs | 2 +- .../AssetReader/RecruitmentBoardCategoryDeserializer.cs | 2 +- .../AssetReader/SecretAbilityDeserializer.cs | 2 +- Arrowgene.Ddon.Shared/AssetReader/SpecialShopDeserializer.cs | 2 +- 16 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Arrowgene.Ddon.Shared/AssetReader/BitterblackMazeAssetDeserializer.cs b/Arrowgene.Ddon.Shared/AssetReader/BitterblackMazeAssetDeserializer.cs index 8c9c1cc9a..f591c19a6 100644 --- a/Arrowgene.Ddon.Shared/AssetReader/BitterblackMazeAssetDeserializer.cs +++ b/Arrowgene.Ddon.Shared/AssetReader/BitterblackMazeAssetDeserializer.cs @@ -22,7 +22,7 @@ public BitterblackMazeAsset ReadPath(string path) BitterblackMazeAsset asset = new BitterblackMazeAsset(); - string json = File.ReadAllText(path); + string json = Util.ReadAllText(path); JsonDocument document = JsonDocument.Parse(json); var configurations = document.RootElement.GetProperty("maze_configurations").EnumerateObject(); diff --git a/Arrowgene.Ddon.Shared/AssetReader/BonusDungeonAssetDeserializer.cs b/Arrowgene.Ddon.Shared/AssetReader/BonusDungeonAssetDeserializer.cs index 6c436375b..a6225c127 100644 --- a/Arrowgene.Ddon.Shared/AssetReader/BonusDungeonAssetDeserializer.cs +++ b/Arrowgene.Ddon.Shared/AssetReader/BonusDungeonAssetDeserializer.cs @@ -18,7 +18,7 @@ public BonusDungeonAsset ReadPath(string path) BonusDungeonAsset asset = new BonusDungeonAsset(); - string json = File.ReadAllText(path); + string json = Util.ReadAllText(path); JsonDocument document = JsonDocument.Parse(json); foreach (var jCategory in document.RootElement.EnumerateArray()) diff --git a/Arrowgene.Ddon.Shared/AssetReader/CostExpScalingAssetDeserializer.cs b/Arrowgene.Ddon.Shared/AssetReader/CostExpScalingAssetDeserializer.cs index 7a95b1b0b..4a75139fc 100644 --- a/Arrowgene.Ddon.Shared/AssetReader/CostExpScalingAssetDeserializer.cs +++ b/Arrowgene.Ddon.Shared/AssetReader/CostExpScalingAssetDeserializer.cs @@ -18,7 +18,7 @@ public CostExpScalingAsset ReadPath(string path) CostExpScalingAsset asset = new CostExpScalingAsset(); - string json = File.ReadAllText(path); + string json = Util.ReadAllText(path); JsonDocument document = JsonDocument.Parse(json); var costExpInfoElements = document.RootElement.EnumerateArray().ToList(); diff --git a/Arrowgene.Ddon.Shared/AssetReader/EnemySpawnAssetDeserializer.cs b/Arrowgene.Ddon.Shared/AssetReader/EnemySpawnAssetDeserializer.cs index 78887f608..de7ef59a0 100644 --- a/Arrowgene.Ddon.Shared/AssetReader/EnemySpawnAssetDeserializer.cs +++ b/Arrowgene.Ddon.Shared/AssetReader/EnemySpawnAssetDeserializer.cs @@ -36,7 +36,7 @@ public EnemySpawnAsset ReadPath(string path) EnemySpawnAsset asset = new EnemySpawnAsset(); - string json = File.ReadAllText(path); + string json = Util.ReadAllText(path); JsonDocument document = JsonDocument.Parse(json); JsonElement schemasElement = document.RootElement.GetProperty("schemas"); diff --git a/Arrowgene.Ddon.Shared/AssetReader/EpitaphRoadAssertDeserializer.cs b/Arrowgene.Ddon.Shared/AssetReader/EpitaphRoadAssertDeserializer.cs index cfb391bf0..057308e40 100644 --- a/Arrowgene.Ddon.Shared/AssetReader/EpitaphRoadAssertDeserializer.cs +++ b/Arrowgene.Ddon.Shared/AssetReader/EpitaphRoadAssertDeserializer.cs @@ -20,7 +20,7 @@ public EpitaphRoadAsset ReadPath(string path) EpitaphRoadAsset asset = new EpitaphRoadAsset(); - string json = File.ReadAllText(path); + string json = Util.ReadAllText(path); JsonDocument document = JsonDocument.Parse(json); foreach (var jEpiPath in document.RootElement.GetProperty("paths").EnumerateArray()) diff --git a/Arrowgene.Ddon.Shared/AssetReader/EpitaphTrialAssetDeserializer.cs b/Arrowgene.Ddon.Shared/AssetReader/EpitaphTrialAssetDeserializer.cs index 97410e173..f40f171d1 100644 --- a/Arrowgene.Ddon.Shared/AssetReader/EpitaphTrialAssetDeserializer.cs +++ b/Arrowgene.Ddon.Shared/AssetReader/EpitaphTrialAssetDeserializer.cs @@ -37,7 +37,7 @@ public bool LoadTrialsFromDirectory(string path, EpitaphTrialAsset trialAssets) { Logger.Info($"{file.FullName}"); - string json = File.ReadAllText(file.FullName); + string json = Util.ReadAllText(file.FullName); JsonDocument document = JsonDocument.Parse(json); var assetData = new EpitaphTrial(); diff --git a/Arrowgene.Ddon.Shared/AssetReader/EventDropAssetDeserializer.cs b/Arrowgene.Ddon.Shared/AssetReader/EventDropAssetDeserializer.cs index ea9d0dbb0..a5fd073f8 100644 --- a/Arrowgene.Ddon.Shared/AssetReader/EventDropAssetDeserializer.cs +++ b/Arrowgene.Ddon.Shared/AssetReader/EventDropAssetDeserializer.cs @@ -19,7 +19,7 @@ public EventDropsAsset ReadPath(string path) EventDropsAsset asset = new EventDropsAsset(); - string json = File.ReadAllText(path); + string json = Util.ReadAllText(path); JsonDocument document = JsonDocument.Parse(json); foreach (var jEventItem in document.RootElement.EnumerateArray()) diff --git a/Arrowgene.Ddon.Shared/AssetReader/GPCourseInfoDeserializer.cs b/Arrowgene.Ddon.Shared/AssetReader/GPCourseInfoDeserializer.cs index 404e9d460..a418b7865 100644 --- a/Arrowgene.Ddon.Shared/AssetReader/GPCourseInfoDeserializer.cs +++ b/Arrowgene.Ddon.Shared/AssetReader/GPCourseInfoDeserializer.cs @@ -17,7 +17,7 @@ public GPCourseInfoAsset ReadPath(string path) GPCourseInfoAsset asset = new GPCourseInfoAsset(); - string json = File.ReadAllText(path); + string json = Util.ReadAllText(path); JsonDocument document = JsonDocument.Parse(json); var ValidCourses = document.RootElement.GetProperty("valid_courses").EnumerateArray().ToList(); diff --git a/Arrowgene.Ddon.Shared/AssetReader/LearnedNormalSkillsDeserializer.cs b/Arrowgene.Ddon.Shared/AssetReader/LearnedNormalSkillsDeserializer.cs index 99ea5ad48..e2e045279 100644 --- a/Arrowgene.Ddon.Shared/AssetReader/LearnedNormalSkillsDeserializer.cs +++ b/Arrowgene.Ddon.Shared/AssetReader/LearnedNormalSkillsDeserializer.cs @@ -18,7 +18,7 @@ public LearnedNormalSkillsAsset ReadPath(string path) LearnedNormalSkillsAsset asset = new LearnedNormalSkillsAsset(); - string json = File.ReadAllText(path); + string json = Util.ReadAllText(path); JsonDocument document = JsonDocument.Parse(json); // List Keys = document.RootElement.EnumerateObject().ToList diff --git a/Arrowgene.Ddon.Shared/AssetReader/NamedParamAssetDeserializer.cs b/Arrowgene.Ddon.Shared/AssetReader/NamedParamAssetDeserializer.cs index eb311eecb..7d98500af 100644 --- a/Arrowgene.Ddon.Shared/AssetReader/NamedParamAssetDeserializer.cs +++ b/Arrowgene.Ddon.Shared/AssetReader/NamedParamAssetDeserializer.cs @@ -16,7 +16,7 @@ public Dictionary ReadPath(string path) Dictionary namedParams = new Dictionary(); - string json = File.ReadAllText(path); + string json = Util.ReadAllText(path); JsonDocument document = JsonDocument.Parse(json); JsonElement namedParamListElement = document.RootElement.GetProperty("namedParamList"); foreach (JsonElement namedParamListEntryElement in namedParamListElement.EnumerateArray()) @@ -54,4 +54,4 @@ public Dictionary ReadPath(string path) return namedParams; } } -} \ No newline at end of file +} diff --git a/Arrowgene.Ddon.Shared/AssetReader/PawnCostReductionAssetDeserializer.cs b/Arrowgene.Ddon.Shared/AssetReader/PawnCostReductionAssetDeserializer.cs index 8c13d74fa..168b29683 100644 --- a/Arrowgene.Ddon.Shared/AssetReader/PawnCostReductionAssetDeserializer.cs +++ b/Arrowgene.Ddon.Shared/AssetReader/PawnCostReductionAssetDeserializer.cs @@ -18,7 +18,7 @@ public PawnCostReductionAsset ReadPath(string path) var asset = new PawnCostReductionAsset(); - string json = File.ReadAllText(path); + string json = Util.ReadAllText(path); using (JsonDocument document = JsonDocument.Parse(json)) { var costReductionElements = document.RootElement.EnumerateArray().ToList(); diff --git a/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs b/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs index ed18ca899..0f4baf9d0 100644 --- a/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs +++ b/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs @@ -39,7 +39,7 @@ public bool LoadQuestsFromDirectory(string path, QuestAsset questAssets) { Logger.Info($"{file.FullName}"); - string json = File.ReadAllText(file.FullName); + string json = Util.ReadAllText(file.FullName); JsonDocument document = JsonDocument.Parse(json); var jQuest = document.RootElement; diff --git a/Arrowgene.Ddon.Shared/AssetReader/QuestDropAssetDeserializer.cs b/Arrowgene.Ddon.Shared/AssetReader/QuestDropAssetDeserializer.cs index 9efd15b1d..e61579a21 100644 --- a/Arrowgene.Ddon.Shared/AssetReader/QuestDropAssetDeserializer.cs +++ b/Arrowgene.Ddon.Shared/AssetReader/QuestDropAssetDeserializer.cs @@ -19,7 +19,7 @@ public QuestDropItemAsset ReadPath(string path) QuestDropItemAsset asset = new QuestDropItemAsset(); - string json = File.ReadAllText(path); + string json = Util.ReadAllText(path); JsonDocument document = JsonDocument.Parse(json); JsonElement dropsTablesElement = document.RootElement.GetProperty("dropsTables"); diff --git a/Arrowgene.Ddon.Shared/AssetReader/RecruitmentBoardCategoryDeserializer.cs b/Arrowgene.Ddon.Shared/AssetReader/RecruitmentBoardCategoryDeserializer.cs index 579e49fbc..4ac1b6d12 100644 --- a/Arrowgene.Ddon.Shared/AssetReader/RecruitmentBoardCategoryDeserializer.cs +++ b/Arrowgene.Ddon.Shared/AssetReader/RecruitmentBoardCategoryDeserializer.cs @@ -16,7 +16,7 @@ public RecruitmentBoardCategoryAsset ReadPath(string path) RecruitmentBoardCategoryAsset asset = new RecruitmentBoardCategoryAsset(); - string json = File.ReadAllText(path); + string json = Util.ReadAllText(path); JsonDocument document = JsonDocument.Parse(json); foreach (var category in document.RootElement.EnumerateArray().ToList()) diff --git a/Arrowgene.Ddon.Shared/AssetReader/SecretAbilityDeserializer.cs b/Arrowgene.Ddon.Shared/AssetReader/SecretAbilityDeserializer.cs index 4016e15ab..7f46a66b7 100644 --- a/Arrowgene.Ddon.Shared/AssetReader/SecretAbilityDeserializer.cs +++ b/Arrowgene.Ddon.Shared/AssetReader/SecretAbilityDeserializer.cs @@ -18,7 +18,7 @@ public SecretAbilityAsset ReadPath(string path) SecretAbilityAsset asset = new SecretAbilityAsset(); - string json = File.ReadAllText(path); + string json = Util.ReadAllText(path); JsonDocument document = JsonDocument.Parse(json); var secretAbilities = document.RootElement.GetProperty("default_abilitiles").EnumerateArray().ToList(); diff --git a/Arrowgene.Ddon.Shared/AssetReader/SpecialShopDeserializer.cs b/Arrowgene.Ddon.Shared/AssetReader/SpecialShopDeserializer.cs index f54f4c426..4849b4f12 100644 --- a/Arrowgene.Ddon.Shared/AssetReader/SpecialShopDeserializer.cs +++ b/Arrowgene.Ddon.Shared/AssetReader/SpecialShopDeserializer.cs @@ -23,7 +23,7 @@ public SpecialShopAsset ReadPath(string path) SpecialShopAsset asset = new SpecialShopAsset(); - string json = File.ReadAllText(path); + string json = Util.ReadAllText(path); JsonDocument document = JsonDocument.Parse(json); uint categoryId = 0; From cec89b8da7ed3e3e99f98afd9c814182eed9e098 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Mon, 23 Dec 2024 10:40:55 -0500 Subject: [PATCH 3/6] code review: Remove GameLogicSettings from deserialization Moved game logic settings from the server settings object. --- Arrowgene.Ddon.Cli/Command/ServerCommand.cs | 6 +-- Arrowgene.Ddon.GameServer/BazaarManager.cs | 6 +-- .../Characters/CharacterManager.cs | 2 +- .../Characters/CraftManager.cs | 2 +- .../Characters/EpitaphRoadManager.cs | 2 +- .../Characters/ExpManager.cs | 10 ++-- .../Characters/HubManager.cs | 4 +- .../Characters/PlayPointManager.cs | 2 +- .../Characters/RewardManager.cs | 2 +- .../Characters/WalletManager.cs | 10 ++-- Arrowgene.Ddon.GameServer/DdonGameServer.cs | 10 ++-- .../GameServerSetting.cs | 6 --- .../Handler/CraftGetCraftSettingHandler.cs | 2 +- .../Handler/InstanceEnemyKillHandler.cs | 4 +- .../Handler/ItemUseBagItemHandler.cs | 2 +- .../QuestGetCycleContentsStateListHandler.cs | 4 +- .../ServerGameTimeGetBaseinfoHandler.cs | 2 +- .../Handler/ServerGetGameSettingHandler.cs | 52 +++++++++---------- .../ServerWeatherForecastGetHandler.cs | 2 +- .../Quests/QuestStateManager.cs | 4 +- .../Tasks/EpitaphSchedulerTask.cs | 2 +- Arrowgene.Ddon.GameServer/WeatherManager.cs | 8 +-- Arrowgene.Ddon.Server/GameLogicSetting.cs | 4 +- .../ScriptedServerSettings.cs | 7 ++- .../Arrowgene.Ddon.Shared.csproj | 6 --- .../Assets/scripts/GameLogicSettings.csx | 32 +++++++++--- .../GameServer/Characters/CraftManagerTest.cs | 18 ++++--- Arrowgene.Ddon.Test/GameServer/SettingTest.cs | 1 - 28 files changed, 112 insertions(+), 100 deletions(-) diff --git a/Arrowgene.Ddon.Cli/Command/ServerCommand.cs b/Arrowgene.Ddon.Cli/Command/ServerCommand.cs index d0f24da16..7c480c844 100644 --- a/Arrowgene.Ddon.Cli/Command/ServerCommand.cs +++ b/Arrowgene.Ddon.Cli/Command/ServerCommand.cs @@ -113,13 +113,13 @@ public CommandResultType Run(CommandParameter parameter) if (_scriptServerSettings == null) { - _scriptServerSettings = new ScriptedServerSettings(_setting.GameServerSetting.GameLogicSetting, _setting.AssetPath); + _scriptServerSettings = new ScriptedServerSettings(_setting.AssetPath); _scriptServerSettings.LoadSettings(); } if (_loginServer == null) { - _loginServer = new DdonLoginServer(_setting.LoginServerSetting, _setting.GameServerSetting.GameLogicSetting, _database, _assetRepository); + _loginServer = new DdonLoginServer(_setting.LoginServerSetting, _scriptServerSettings.GameLogicSetting, _database, _assetRepository); } if (_webServer == null) @@ -129,7 +129,7 @@ public CommandResultType Run(CommandParameter parameter) if (_gameServer == null) { - _gameServer = new DdonGameServer(_setting.GameServerSetting, _database, _assetRepository); + _gameServer = new DdonGameServer(_setting.GameServerSetting, _scriptServerSettings.GameLogicSetting, _database, _assetRepository); } if (_rpcWebServer == null) diff --git a/Arrowgene.Ddon.GameServer/BazaarManager.cs b/Arrowgene.Ddon.GameServer/BazaarManager.cs index 2fd5ad086..061a5a53e 100644 --- a/Arrowgene.Ddon.GameServer/BazaarManager.cs +++ b/Arrowgene.Ddon.GameServer/BazaarManager.cs @@ -39,7 +39,7 @@ public ulong Exhibit(GameClient client, StorageType storageType, string itemUID, exhibition.Info.ItemInfo.ExhibitionTime = now; exhibition.Info.State = BazaarExhibitionState.OnSale; exhibition.Info.Proceeds = calculateProceeds(exhibition.Info.ItemInfo.ItemBaseInfo); - exhibition.Info.Expire = now.AddSeconds(Server.Setting.GameLogicSetting.BazaarExhibitionTimeSeconds); + exhibition.Info.Expire = now.AddSeconds(Server.GameLogicSettings.BazaarExhibitionTimeSeconds); ulong bazaarId = Server.Database.InsertBazaarExhibition(exhibition); return bazaarId; @@ -59,7 +59,7 @@ public ulong ReExhibit(ulong bazaarId, uint newPrice) exhibition.Info.ItemInfo.ItemBaseInfo.Price = newPrice; exhibition.Info.ItemInfo.ExhibitionTime = now; exhibition.Info.Proceeds = calculateProceeds(exhibition.Info.ItemInfo.ItemBaseInfo); - exhibition.Info.Expire = now.AddSeconds(Server.Setting.GameLogicSetting.BazaarExhibitionTimeSeconds); + exhibition.Info.Expire = now.AddSeconds(Server.GameLogicSettings.BazaarExhibitionTimeSeconds); Server.Database.UpdateBazaarExhibiton(exhibition); return exhibition.Info.ItemInfo.BazaarId; @@ -157,7 +157,7 @@ public uint ReceiveProceeds(GameClient client) ulong totalCooldown; try { - totalCooldown = Server.Setting.GameLogicSetting.BazaarCooldownTimeSeconds - Server.GpCourseManager.BazaarReExhibitShorten(); + totalCooldown = Server.GameLogicSettings.BazaarCooldownTimeSeconds - Server.GpCourseManager.BazaarReExhibitShorten(); } catch (OverflowException _) { diff --git a/Arrowgene.Ddon.GameServer/Characters/CharacterManager.cs b/Arrowgene.Ddon.GameServer/Characters/CharacterManager.cs index 247655845..9d984446a 100644 --- a/Arrowgene.Ddon.GameServer/Characters/CharacterManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/CharacterManager.cs @@ -61,7 +61,7 @@ public Character SelectCharacter(uint characterId, DbConnection? connectionIn = character.EpitaphRoadState.UnlockedContent = _Server.Database.GetEpitaphRoadUnlocks(character.CharacterId, connectionIn); - if (_Server.Setting.GameLogicSetting.EnableEpitaphWeeklyRewards) + if (_Server.GameLogicSettings.EnableEpitaphWeeklyRewards) { character.EpitaphRoadState.WeeklyRewardsClaimed = _Server.Database.GetEpitaphClaimedWeeklyRewards(character.CharacterId, connectionIn); } diff --git a/Arrowgene.Ddon.GameServer/Characters/CraftManager.cs b/Arrowgene.Ddon.GameServer/Characters/CraftManager.cs index e3603fb84..8a3b3f728 100644 --- a/Arrowgene.Ddon.GameServer/Characters/CraftManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/CraftManager.cs @@ -122,7 +122,7 @@ public double GetCraftingTimeReductionRate(List productionSpeedLevels) { return Math.Clamp( productionSpeedLevels.Select(level => level * ProductionSpeedIncrementPerLevel + ProductionSpeedMinimumPerPawn).Sum() * - _server.Setting.GameLogicSetting.AdditionalProductionSpeedFactor, 0, 100); + _server.GameLogicSettings.AdditionalProductionSpeedFactor, 0, 100); } public uint CalculateRecipeProductionSpeed(uint recipeTime, List productionSpeedLevels) diff --git a/Arrowgene.Ddon.GameServer/Characters/EpitaphRoadManager.cs b/Arrowgene.Ddon.GameServer/Characters/EpitaphRoadManager.cs index 195abe54c..b16a874e9 100644 --- a/Arrowgene.Ddon.GameServer/Characters/EpitaphRoadManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/EpitaphRoadManager.cs @@ -1290,7 +1290,7 @@ public List RollGatheringLoot(GameClient client, Charact { results.AddRange(RollWeeklyChestReward(dungeonInfo, reward)); - if (_Server.Setting.GameLogicSetting.EnableEpitaphWeeklyRewards) + if (_Server.GameLogicSettings.EnableEpitaphWeeklyRewards) { character.EpitaphRoadState.WeeklyRewardsClaimed.Add(reward.EpitaphId); _Server.Database.InsertEpitaphWeeklyReward(character.CharacterId, reward.EpitaphId); diff --git a/Arrowgene.Ddon.GameServer/Characters/ExpManager.cs b/Arrowgene.Ddon.GameServer/Characters/ExpManager.cs index b85978659..634b035b6 100644 --- a/Arrowgene.Ddon.GameServer/Characters/ExpManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/ExpManager.cs @@ -399,7 +399,7 @@ public ExpManager(DdonGameServer server, GameClientLookup gameClientLookup) { this._Server = server; this._gameClientLookup = gameClientLookup; - this._GameSettings = server.Setting.GameLogicSetting; + this._GameSettings = server.GameLogicSettings; } private DdonGameServer _Server; @@ -460,7 +460,7 @@ public PacketQueue AddExp(GameClient client, CharacterCommon characterToAddExpTo PacketQueue packets = new(); var lvCap = (client.GameMode == GameMode.Normal) - ? _Server.Setting.GameLogicSetting.JobLevelMax + ? _Server.GameLogicSettings.JobLevelMax : BitterblackMazeManager.LevelCap(client.Character.BbmProgress); CDataCharacterJobData? activeCharacterJobData = characterToAddExpTo.ActiveCharacterJobData; @@ -846,7 +846,7 @@ private bool AllMembersOwnedBySameCharacter(PartyGroup party) private double CalculatePartyRangeMultipler(GameMode gameMode, PartyGroup party) { - if (!_GameSettings.AdjustPartyEnemyExp || gameMode == GameMode.BitterblackMaze) + if (!_GameSettings.EnableAdjustPartyEnemyExp || gameMode == GameMode.BitterblackMaze) { return 1.0; } @@ -873,7 +873,7 @@ private double CalculatePartyRangeMultipler(GameMode gameMode, PartyGroup party) private double CalculateTargetLvMultiplier(GameMode gameMode, PartyGroup party, uint targetLv) { - if (!_GameSettings.AdjustTargetLvEnemyExp || gameMode == GameMode.BitterblackMaze) + if (!_GameSettings.EnableAdjustTargetLvEnemyExp || gameMode == GameMode.BitterblackMaze) { return 1.0; } @@ -992,7 +992,7 @@ public bool RequiresPawnCatchup(GameMode gameMode, PartyGroup party, Pawn pawn) private double CalculatePawnCatchupTargetLvMultiplier(GameMode gameMode, Pawn pawn, uint targetLv) { - if (!_GameSettings.AdjustTargetLvEnemyExp || gameMode == GameMode.BitterblackMaze) + if (!_GameSettings.EnableAdjustTargetLvEnemyExp || gameMode == GameMode.BitterblackMaze) { return 1.0; } diff --git a/Arrowgene.Ddon.GameServer/Characters/HubManager.cs b/Arrowgene.Ddon.GameServer/Characters/HubManager.cs index 884b8e375..3473d117b 100644 --- a/Arrowgene.Ddon.GameServer/Characters/HubManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/HubManager.cs @@ -31,7 +31,7 @@ public HashSet GetClientsInHub(StageId stageId) public HashSet GetClientsInHub(uint stageId) { - if (Server.Setting.GameLogicSetting.NaiveLobbyContextHandling) + if (Server.GameLogicSettings.NaiveLobbyContextHandling) { return Server.ClientLookup.GetAll().Distinct().ToHashSet(); } @@ -54,7 +54,7 @@ public HashSet GetClientsInHub(uint stageId) public void UpdateLobbyContextOnStageChange(GameClient client, uint previousStageId, uint targetStageId) { // Fallback to naive method. - if (Server.Setting.GameLogicSetting.NaiveLobbyContextHandling) + if (Server.GameLogicSettings.NaiveLobbyContextHandling) { NaiveLobbyHandling(client, previousStageId); return; diff --git a/Arrowgene.Ddon.GameServer/Characters/PlayPointManager.cs b/Arrowgene.Ddon.GameServer/Characters/PlayPointManager.cs index 805329b46..7c5de644d 100644 --- a/Arrowgene.Ddon.GameServer/Characters/PlayPointManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/PlayPointManager.cs @@ -15,7 +15,7 @@ public class PlayPointManager private uint PP_MAX { get { - return _Server.Setting.GameLogicSetting.PlayPointMax; + return _Server.GameLogicSettings.PlayPointMax; } } diff --git a/Arrowgene.Ddon.GameServer/Characters/RewardManager.cs b/Arrowgene.Ddon.GameServer/Characters/RewardManager.cs index b922f3c2c..5806e1116 100644 --- a/Arrowgene.Ddon.GameServer/Characters/RewardManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/RewardManager.cs @@ -22,7 +22,7 @@ public bool AddQuestRewards(GameClient client, Quest quest, DbConnection? connec var rewards = quest.GenerateBoxRewards(); var currentRewards = GetQuestBoxRewards(client, connectionIn); - if (currentRewards.Count >= _Server.Setting.GameLogicSetting.RewardBoxMax) + if (currentRewards.Count >= _Server.GameLogicSettings.RewardBoxMax) { return false; } diff --git a/Arrowgene.Ddon.GameServer/Characters/WalletManager.cs b/Arrowgene.Ddon.GameServer/Characters/WalletManager.cs index 91e407be5..00091d1e5 100644 --- a/Arrowgene.Ddon.GameServer/Characters/WalletManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/WalletManager.cs @@ -22,7 +22,7 @@ public class WalletManager public WalletManager(DdonGameServer server) { Server = server; - WalletLimits = server.Setting.GameLogicSetting.WalletLimits; + WalletLimits = server.GameLogicSettings.WalletLimits; } public bool AddToWalletNtc(Client Client, Character Character, WalletType Type, uint Amount, uint BonusAmount = 0, ItemNoticeType updateType = ItemNoticeType.Default, DbConnection? connectionIn = null) { @@ -107,16 +107,16 @@ public uint GetScaledWalletAmount(WalletType type, uint amount) switch (type) { case WalletType.Gold: - modifier = Server.Setting.GameLogicSetting.GoldModifier; + modifier = Server.GameLogicSettings.GoldModifier; break; case WalletType.RiftPoints: - modifier = Server.Setting.GameLogicSetting.RiftModifier; + modifier = Server.GameLogicSettings.RiftModifier; break; case WalletType.BloodOrbs: - modifier = Server.Setting.GameLogicSetting.BoModifier; + modifier = Server.GameLogicSettings.BoModifier; break; case WalletType.HighOrbs: - modifier = Server.Setting.GameLogicSetting.HoModifier; + modifier = Server.GameLogicSettings.HoModifier; break; default: modifier = 1.0; diff --git a/Arrowgene.Ddon.GameServer/DdonGameServer.cs b/Arrowgene.Ddon.GameServer/DdonGameServer.cs index 09f944a57..fdf3f6cd9 100644 --- a/Arrowgene.Ddon.GameServer/DdonGameServer.cs +++ b/Arrowgene.Ddon.GameServer/DdonGameServer.cs @@ -50,10 +50,11 @@ public class DdonGameServer : DdonServer { private static readonly ServerLogger Logger = LogProvider.Logger(typeof(DdonGameServer)); - public DdonGameServer(GameServerSetting setting, IDatabase database, AssetRepository assetRepository) + public DdonGameServer(GameServerSetting setting, GameLogicSetting gameLogicSettings, IDatabase database, AssetRepository assetRepository) : base(ServerType.Game, setting.ServerSetting, database, assetRepository) { - Setting = new GameServerSetting(setting); + ServerSetting = new GameServerSetting(setting); + GameLogicSettings = gameLogicSettings; ScriptManager = new ScriptManager(this); ClientLookup = new GameClientLookup(); ChatLogHandler = new ChatLogHandler(); @@ -92,7 +93,8 @@ public DdonGameServer(GameServerSetting setting, IDatabase database, AssetReposi } public event EventHandler ClientConnectionChangeEvent; - public GameServerSetting Setting { get; } + public GameServerSetting ServerSetting { get; } + public GameLogicSetting GameLogicSettings { get; } public ScriptManager ScriptManager { get; } public ChatManager ChatManager { get; } public ItemManager ItemManager { get; } @@ -188,7 +190,7 @@ ClientConnectionChangeArgs connectionChangeEventArgs public override GameClient NewClient(ITcpSocket socket) { GameClient newClient = new GameClient(socket, - new PacketFactory(Setting.ServerSetting, PacketIdResolver.GamePacketIdResolver), this); + new PacketFactory(ServerSetting.ServerSetting, PacketIdResolver.GamePacketIdResolver), this); ClientLookup.Add(newClient); return newClient; } diff --git a/Arrowgene.Ddon.GameServer/GameServerSetting.cs b/Arrowgene.Ddon.GameServer/GameServerSetting.cs index 0342a4f10..1f37bc799 100644 --- a/Arrowgene.Ddon.GameServer/GameServerSetting.cs +++ b/Arrowgene.Ddon.GameServer/GameServerSetting.cs @@ -7,7 +7,6 @@ namespace Arrowgene.Ddon.GameServer public class GameServerSetting { [DataMember(Order = 1)] public ServerSetting ServerSetting { get; set; } - public GameLogicSetting GameLogicSetting { get; set; } public GameServerSetting() { @@ -17,7 +16,6 @@ public GameServerSetting() public GameServerSetting(GameServerSetting setting) { ServerSetting = new ServerSetting(setting.ServerSetting); - GameLogicSetting = setting.GameLogicSetting; } // Note: method is called after the object is completely deserialized - constructors are skipped. @@ -32,8 +30,6 @@ void OnDeserialized(StreamingContext context) ServerSetting.ServerPort = 52000; ServerSetting.ServerSocketSettings.Identity = "Game"; } - - GameLogicSetting ??= new GameLogicSetting(); } [OnDeserializing] @@ -49,8 +45,6 @@ void SetDefaultValues() ServerSetting.Name = "Game"; ServerSetting.ServerPort = 52000; ServerSetting.ServerSocketSettings.Identity = "Game"; - - GameLogicSetting = new GameLogicSetting(); } } } diff --git a/Arrowgene.Ddon.GameServer/Handler/CraftGetCraftSettingHandler.cs b/Arrowgene.Ddon.GameServer/Handler/CraftGetCraftSettingHandler.cs index fdbff38fc..c40d6bcf5 100644 --- a/Arrowgene.Ddon.GameServer/Handler/CraftGetCraftSettingHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/CraftGetCraftSettingHandler.cs @@ -381,7 +381,7 @@ public override void Handle(GameClient client, StructurePacket 0) { // Drop BO - uint gainedBo = (uint) (enemyKilled.BloodOrbs * _gameServer.Setting.GameLogicSetting.BoModifier); + uint gainedBo = (uint) (enemyKilled.BloodOrbs * _gameServer.GameLogicSettings.BoModifier); uint bonusBo = (uint) (gainedBo * _gameServer.GpCourseManager.EnemyBloodOrbBonus()); CDataUpdateWalletPoint boUpdateWalletPoint = _gameServer.WalletManager.AddToWallet(memberClient.Character, WalletType.BloodOrbs, gainedBo + bonusBo, bonusBo, connectionIn: connectionIn); updateCharacterItemNtc.UpdateWalletList.Add(boUpdateWalletPoint); @@ -219,7 +219,7 @@ public override S2CInstanceEnemyKillRes Handle(GameClient client, C2SInstanceEne if (enemyKilled.HighOrbs > 0) { // Drop HO - uint gainedHo = (uint)(enemyKilled.HighOrbs * _gameServer.Setting.GameLogicSetting.HoModifier); + uint gainedHo = (uint)(enemyKilled.HighOrbs * _gameServer.GameLogicSettings.HoModifier); CDataUpdateWalletPoint hoUpdateWalletPoint = _gameServer.WalletManager.AddToWallet(memberClient.Character, WalletType.HighOrbs, gainedHo, connectionIn: connectionIn); updateCharacterItemNtc.UpdateWalletList.Add(hoUpdateWalletPoint); } diff --git a/Arrowgene.Ddon.GameServer/Handler/ItemUseBagItemHandler.cs b/Arrowgene.Ddon.GameServer/Handler/ItemUseBagItemHandler.cs index 074b7efad..024b03984 100644 --- a/Arrowgene.Ddon.GameServer/Handler/ItemUseBagItemHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/ItemUseBagItemHandler.cs @@ -96,7 +96,7 @@ public override void Handle(GameClient client, StructurePacket x.QuestType == QuestType.Main); diff --git a/Arrowgene.Ddon.GameServer/Handler/ServerGameTimeGetBaseinfoHandler.cs b/Arrowgene.Ddon.GameServer/Handler/ServerGameTimeGetBaseinfoHandler.cs index bc4e72006..7b55428d9 100644 --- a/Arrowgene.Ddon.GameServer/Handler/ServerGameTimeGetBaseinfoHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/ServerGameTimeGetBaseinfoHandler.cs @@ -17,7 +17,7 @@ public override S2CServerGameTimeGetBaseInfoRes Handle(GameClient client, C2SSer var res = new S2CServerGameTimeGetBaseInfoRes(); res.GameTimeBaseInfo.OriginalGameTimeSec = WeatherManager.OriginalGameTimeSec; res.GameTimeBaseInfo.OriginalRealTimeSec = WeatherManager.OriginalRealTimeSec; - res.GameTimeBaseInfo.GameTimeOneDayMin = Server.Setting.GameLogicSetting.GameClockTimescale; + res.GameTimeBaseInfo.GameTimeOneDayMin = Server.GameLogicSettings.GameClockTimescale; res.WeatherLoop = Server.WeatherManager.WeatherLoopList; res.MoonAgeLoopSec = WeatherManager.MoonAgeLoopSec; diff --git a/Arrowgene.Ddon.GameServer/Handler/ServerGetGameSettingHandler.cs b/Arrowgene.Ddon.GameServer/Handler/ServerGetGameSettingHandler.cs index 50e3ecd06..6633687e8 100644 --- a/Arrowgene.Ddon.GameServer/Handler/ServerGetGameSettingHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/ServerGetGameSettingHandler.cs @@ -20,40 +20,40 @@ public override S2CServerGetGameSettingRes Handle(GameClient client, C2SServerGe { var res = new S2CServerGetGameSettingRes.Serializer().Read(GameDump.Dump_10.AsBuffer()); - res.GameSetting.JobLevelMax = Server.Setting.GameLogicSetting.JobLevelMax; + res.GameSetting.JobLevelMax = Server.GameLogicSettings.JobLevelMax; res.GameSetting.ExpRequiredPerLevel[0].ExpList = res.GameSetting.ExpRequiredPerLevel[0].ExpList.Take((int)res.GameSetting.JobLevelMax).ToList(); - res.GameSetting.EnableVisualEquip = Server.Setting.GameLogicSetting.EnableVisualEquip; + res.GameSetting.EnableVisualEquip = Server.GameLogicSettings.EnableVisualEquip; - res.GameSetting.ClanMemberMax = Server.Setting.GameLogicSetting.ClanMemberMax; - res.GameSetting.CharacterNumMax = Server.Setting.GameLogicSetting.CharacterNumMax; - res.GameSetting.FriendListMax = Server.Setting.GameLogicSetting.FriendListMax; + res.GameSetting.ClanMemberMax = Server.GameLogicSettings.ClanMemberMax; + res.GameSetting.CharacterNumMax = Server.GameLogicSettings.CharacterNumMax; + res.GameSetting.FriendListMax = Server.GameLogicSettings.FriendListMax; res.GameSetting.UrlInfoList = new List() { - new() {Type = 1, URL = Server.Setting.GameLogicSetting.UrlManual}, - new() {Type = 2, URL = Server.Setting.GameLogicSetting.UrlShopDetail}, - new() {Type = 3, URL = Server.Setting.GameLogicSetting.UrlShopCounterA}, - new() {Type = 4, URL = Server.Setting.GameLogicSetting.UrlShopAttention}, - new() {Type = 5, URL = Server.Setting.GameLogicSetting.UrlShopStoneLimit}, - new() {Type = 6, URL = Server.Setting.GameLogicSetting.UrlShopCounterB}, - new() {Type = 7, URL = Server.Setting.GameLogicSetting.UrlChargeCallback}, - new() {Type = 8, URL = Server.Setting.GameLogicSetting.UrlChargeA}, - new() {Type = 9, URL = Server.Setting.GameLogicSetting.UrlSample9}, - new() {Type = 10, URL = Server.Setting.GameLogicSetting.UrlSample10}, - new() {Type = 11, URL = Server.Setting.GameLogicSetting.UrlCampaignBanner}, - new() {Type = 12, URL = Server.Setting.GameLogicSetting.UrlSupportIndex}, - new() {Type = 13, URL = Server.Setting.GameLogicSetting.UrlPhotoupAuthorize}, - new() {Type = 14, URL = Server.Setting.GameLogicSetting.UrlApiA}, - new() {Type = 15, URL = Server.Setting.GameLogicSetting.UrlApiB}, - new() {Type = 16, URL = Server.Setting.GameLogicSetting.UrlIndex}, - new() {Type = 17, URL = Server.Setting.GameLogicSetting.UrlCampaign}, - new() {Type = 19, URL = Server.Setting.GameLogicSetting.UrlChargeB}, - new() {Type = 20, URL = Server.Setting.GameLogicSetting.UrlCompanionImage}, + new() {Type = 1, URL = Server.GameLogicSettings.UrlManual}, + new() {Type = 2, URL = Server.GameLogicSettings.UrlShopDetail}, + new() {Type = 3, URL = Server.GameLogicSettings.UrlShopCounterA}, + new() {Type = 4, URL = Server.GameLogicSettings.UrlShopAttention}, + new() {Type = 5, URL = Server.GameLogicSettings.UrlShopStoneLimit}, + new() {Type = 6, URL = Server.GameLogicSettings.UrlShopCounterB}, + new() {Type = 7, URL = Server.GameLogicSettings.UrlChargeCallback}, + new() {Type = 8, URL = Server.GameLogicSettings.UrlChargeA}, + new() {Type = 9, URL = Server.GameLogicSettings.UrlSample9}, + new() {Type = 10, URL = Server.GameLogicSettings.UrlSample10}, + new() {Type = 11, URL = Server.GameLogicSettings.UrlCampaignBanner}, + new() {Type = 12, URL = Server.GameLogicSettings.UrlSupportIndex}, + new() {Type = 13, URL = Server.GameLogicSettings.UrlPhotoupAuthorize}, + new() {Type = 14, URL = Server.GameLogicSettings.UrlApiA}, + new() {Type = 15, URL = Server.GameLogicSettings.UrlApiB}, + new() {Type = 16, URL = Server.GameLogicSettings.UrlIndex}, + new() {Type = 17, URL = Server.GameLogicSettings.UrlCampaign}, + new() {Type = 19, URL = Server.GameLogicSettings.UrlChargeB}, + new() {Type = 20, URL = Server.GameLogicSettings.UrlCompanionImage}, }; - res.GameSetting.PlayPointMax = Server.Setting.GameLogicSetting.PlayPointMax; - res.GameSetting.WalletLimits = Server.Setting.GameLogicSetting.WalletLimits.Select(x => new CDataWalletLimit() + res.GameSetting.PlayPointMax = Server.GameLogicSettings.PlayPointMax; + res.GameSetting.WalletLimits = Server.GameLogicSettings.WalletLimits.Select(x => new CDataWalletLimit() { WalletType = x.Key, MaxValue = x.Value, diff --git a/Arrowgene.Ddon.GameServer/Handler/ServerWeatherForecastGetHandler.cs b/Arrowgene.Ddon.GameServer/Handler/ServerWeatherForecastGetHandler.cs index 02352deed..1c4142afb 100644 --- a/Arrowgene.Ddon.GameServer/Handler/ServerWeatherForecastGetHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/ServerWeatherForecastGetHandler.cs @@ -17,7 +17,7 @@ public override S2CServerWeatherForecastGetRes Handle(GameClient client, C2SServ var res = new S2CServerWeatherForecastGetRes() { IntervalGameHour = WeatherManager.ForecastIntervalGameHour, - GameDayToEarthMin = Server.Setting.GameLogicSetting.GameClockTimescale, + GameDayToEarthMin = Server.GameLogicSettings.GameClockTimescale, ForecastList = Server.WeatherManager.GetForecast() }; diff --git a/Arrowgene.Ddon.GameServer/Quests/QuestStateManager.cs b/Arrowgene.Ddon.GameServer/Quests/QuestStateManager.cs index e7b3448f7..56fe919da 100644 --- a/Arrowgene.Ddon.GameServer/Quests/QuestStateManager.cs +++ b/Arrowgene.Ddon.GameServer/Quests/QuestStateManager.cs @@ -609,7 +609,7 @@ protected PacketQueue SendWalletRewards(DdonGameServer server, GameClient client { case ExpType.ExperiencePoints: packets.AddRange(server.ExpManager.AddExp(client, client.Character, amount, RewardSource.Quest, quest.QuestType, connectionIn)); - if (server.Setting.GameLogicSetting.EnableMainPartyPawnsQuestRewards) + if (server.GameLogicSettings.EnableMainPartyPawnsQuestRewards) { foreach (PartyMember member in client.Party.Members) { @@ -622,7 +622,7 @@ protected PacketQueue SendWalletRewards(DdonGameServer server, GameClient client break; case ExpType.JobPoints: packets.AddRange(server.ExpManager.AddJp(client, client.Character, amount, RewardSource.Quest, quest.QuestType, connectionIn)); - if (server.Setting.GameLogicSetting.EnableMainPartyPawnsQuestRewards) + if (server.GameLogicSettings.EnableMainPartyPawnsQuestRewards) { foreach (PartyMember member in client.Party.Members) { diff --git a/Arrowgene.Ddon.GameServer/Tasks/EpitaphSchedulerTask.cs b/Arrowgene.Ddon.GameServer/Tasks/EpitaphSchedulerTask.cs index a47fd3dc1..2a5282f14 100644 --- a/Arrowgene.Ddon.GameServer/Tasks/EpitaphSchedulerTask.cs +++ b/Arrowgene.Ddon.GameServer/Tasks/EpitaphSchedulerTask.cs @@ -17,7 +17,7 @@ public EpitaphSchedulerTask(DayOfWeek day, uint hour, uint minute) : base(TaskTy public override bool IsEnabled(DdonGameServer server) { - return server.Setting.GameLogicSetting.EnableEpitaphWeeklyRewards; + return server.GameLogicSettings.EnableEpitaphWeeklyRewards; } public override void RunTask(DdonGameServer server) diff --git a/Arrowgene.Ddon.GameServer/WeatherManager.cs b/Arrowgene.Ddon.GameServer/WeatherManager.cs index 92cfb3383..7912ca09c 100644 --- a/Arrowgene.Ddon.GameServer/WeatherManager.cs +++ b/Arrowgene.Ddon.GameServer/WeatherManager.cs @@ -34,7 +34,7 @@ public class WeatherManager public WeatherManager(DdonGameServer server) { _Server = server; - GameClockTimescale = server.Setting.GameLogicSetting.GameClockTimescale; + GameClockTimescale = server.GameLogicSettings.GameClockTimescale; GenerateWeatherSequence(); } @@ -65,7 +65,7 @@ public uint GetMoonPhase() public uint GetMoonPhase(DateTimeOffset time) { - ulong secondsPerLestanianDay = _Server.Setting.GameLogicSetting.GameClockTimescale * 60; + ulong secondsPerLestanianDay = _Server.GameLogicSettings.GameClockTimescale * 60; ulong secondsPerMoonAge = MoonAgeLoopSec / GameTimeMoonAges; ulong secondsElapsed = (ulong)(time.ToUnixTimeSeconds() - OriginalRealTimeSec); @@ -140,8 +140,8 @@ private void GenerateWeatherSequence() { List weatherLoop = new List(); - uint seqLength = _Server.Setting.GameLogicSetting.WeatherSequenceLength; - List<(uint MeanLength, uint Weight)> seqStats = _Server.Setting.GameLogicSetting.WeatherStatistics; + uint seqLength = _Server.GameLogicSettings.WeatherSequenceLength; + List<(uint MeanLength, uint Weight)> seqStats = _Server.GameLogicSettings.WeatherStatistics; if (!seqStats.Where(x => x.Weight > 0).Any()) { diff --git a/Arrowgene.Ddon.Server/GameLogicSetting.cs b/Arrowgene.Ddon.Server/GameLogicSetting.cs index 05cdcaba6..6ff990843 100644 --- a/Arrowgene.Ddon.Server/GameLogicSetting.cs +++ b/Arrowgene.Ddon.Server/GameLogicSetting.cs @@ -45,7 +45,7 @@ public class GameLogicSetting /// /// Configures if party exp is adjusted based on level differences of members. /// - public bool AdjustPartyEnemyExp { get; set; } + public bool EnableAdjustPartyEnemyExp { get; set; } /// /// List of the inclusive ranges of (MinLv, Maxlv, ExpMultiplier). ExpMultiplier is a value @@ -57,7 +57,7 @@ public class GameLogicSetting /// /// Configures if exp is adjusted based on level differences of members vs target level. /// - public bool AdjustTargetLvEnemyExp { get; set; } + public bool EnableAdjustTargetLvEnemyExp { get; set; } /// /// List of the inclusive ranges of (MinLv, Maxlv, ExpMultiplier). ExpMultiplier is a value from diff --git a/Arrowgene.Ddon.Server/ScriptedServerSettings.cs b/Arrowgene.Ddon.Server/ScriptedServerSettings.cs index 1a29d2613..11119c170 100644 --- a/Arrowgene.Ddon.Server/ScriptedServerSettings.cs +++ b/Arrowgene.Ddon.Server/ScriptedServerSettings.cs @@ -7,7 +7,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Threading; namespace Arrowgene.Ddon.Server { @@ -22,7 +21,7 @@ public class Globals private string ScriptsRoot { get; set; } private Dictionary CompiledScripts; - private GameLogicSetting GameLogicSetting; + public GameLogicSetting GameLogicSetting { get; private set; } public FileSystemWatcher Watcher { get; private set; } public Script GameLogicSettings @@ -43,11 +42,11 @@ public Script GameLogicSettings } } - public ScriptedServerSettings(GameLogicSetting gameLogicSetting, string AssetsPath) + public ScriptedServerSettings(string AssetsPath) { ScriptsRoot = $"{AssetsPath}\\scripts"; - GameLogicSetting = gameLogicSetting; + GameLogicSetting = new GameLogicSetting(); CompiledScripts = new Dictionary(); diff --git a/Arrowgene.Ddon.Shared/Arrowgene.Ddon.Shared.csproj b/Arrowgene.Ddon.Shared/Arrowgene.Ddon.Shared.csproj index 39890da50..339cf536c 100644 --- a/Arrowgene.Ddon.Shared/Arrowgene.Ddon.Shared.csproj +++ b/Arrowgene.Ddon.Shared/Arrowgene.Ddon.Shared.csproj @@ -58,11 +58,5 @@ PreserveNewest - - PreserveNewest - - - PreserveNewest - diff --git a/Arrowgene.Ddon.Shared/Files/Assets/scripts/GameLogicSettings.csx b/Arrowgene.Ddon.Shared/Files/Assets/scripts/GameLogicSettings.csx index 128cde1ac..9f31d788f 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/scripts/GameLogicSettings.csx +++ b/Arrowgene.Ddon.Shared/Files/Assets/scripts/GameLogicSettings.csx @@ -16,13 +16,22 @@ GameLogicSetting.RookiesRingMaxLevel = 89; GameLogicSetting.RookiesRingBonus = 1.0; // EXP Penalty Settings -GameLogicSetting.AdjustPartyEnemyExp = true; + +/** + * @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)>() { - // 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 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), @@ -32,9 +41,20 @@ GameLogicSetting.AdjustPartyEnemyExpTiers = new List<(uint MinLv, uint MaxLv, do ( 9, 10, 0.5), }; -GameLogicSetting.AdjustTargetLvEnemyExp = false; +/** + * @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), diff --git a/Arrowgene.Ddon.Test/GameServer/Characters/CraftManagerTest.cs b/Arrowgene.Ddon.Test/GameServer/Characters/CraftManagerTest.cs index b958e0fbf..4c554c8b5 100644 --- a/Arrowgene.Ddon.Test/GameServer/Characters/CraftManagerTest.cs +++ b/Arrowgene.Ddon.Test/GameServer/Characters/CraftManagerTest.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Arrowgene.Ddon.GameServer.Scripting; +using Arrowgene.Ddon.Server; using Arrowgene.Ddon.Shared; using Arrowgene.Ddon.Shared.Entity.Structure; using Arrowgene.Ddon.Shared.Model; @@ -15,17 +16,20 @@ public class CraftManagerTest public CraftManagerTest() { + var settings = new GameServerSetting(); - settings.GameLogicSetting.GameClockTimescale = 90; - settings.GameLogicSetting.WeatherSequenceLength = 20; - settings.GameLogicSetting.WeatherStatistics = new List<(uint MeanLength, uint Weight)>() + + var gameLogicSetting = new GameLogicSetting(); + gameLogicSetting.GameClockTimescale = 90; + gameLogicSetting.WeatherSequenceLength = 20; + gameLogicSetting.WeatherStatistics = new List<(uint MeanLength, uint Weight)>() { (60 * 30, 1), // Fair (60 * 30, 1), // Cloudy (60 * 30, 1), // Rainy }; - _mockServer = new DdonGameServer(settings, new MockDatabase(), new AssetRepository("TestFiles")); + _mockServer = new DdonGameServer(settings, gameLogicSetting, new MockDatabase(), new AssetRepository("TestFiles")); _craftManager = new CraftManager(_mockServer); } @@ -33,7 +37,7 @@ public CraftManagerTest() public void GetCraftingTimeReductionRate_ShouldReturnCorrectValue() { List productionSpeedLevels = new List { 10, 20, 30 }; - _mockServer.Setting.GameLogicSetting.AdditionalProductionSpeedFactor = 1.0; + _mockServer.GameLogicSettings.AdditionalProductionSpeedFactor = 1.0; double result = _craftManager.GetCraftingTimeReductionRate(productionSpeedLevels); @@ -45,7 +49,7 @@ public void CalculateRecipeProductionSpeed_ShouldReturnReducedTime() { List productionSpeedLevels = new List { 70, 70, 70, 70 }; const uint recipeTime = 100; - _mockServer.Setting.GameLogicSetting.AdditionalProductionSpeedFactor = 1.0; + _mockServer.GameLogicSettings.AdditionalProductionSpeedFactor = 1.0; uint result = _craftManager.CalculateRecipeProductionSpeed(recipeTime, productionSpeedLevels); @@ -57,7 +61,7 @@ public void CalculateRecipeProductionSpeed_ShouldReturnZeroCraftTime_AdditionalF { List productionSpeedLevels = new List { 70, 70, 70, 70 }; const uint recipeTime = 100; - _mockServer.Setting.GameLogicSetting.AdditionalProductionSpeedFactor = 100; + _mockServer.GameLogicSettings.AdditionalProductionSpeedFactor = 100; uint result = _craftManager.CalculateRecipeProductionSpeed(recipeTime, productionSpeedLevels); diff --git a/Arrowgene.Ddon.Test/GameServer/SettingTest.cs b/Arrowgene.Ddon.Test/GameServer/SettingTest.cs index 07c58a52c..97d673d50 100644 --- a/Arrowgene.Ddon.Test/GameServer/SettingTest.cs +++ b/Arrowgene.Ddon.Test/GameServer/SettingTest.cs @@ -55,7 +55,6 @@ public void Deserialize_MissingFields_ShouldAssignDefaultValues() public void RoundTrip_SerializeAndDeserialize_ShouldMaintainObjectState() { Setting originalSetting = new Setting(); - originalSetting.GameServerSetting.GameLogicSetting.AdditionalProductionSpeedFactor = 2.0; string json = Setting.Serialize(originalSetting); Setting deserializedSetting = Setting.Deserialize(json); From 53be7537ce9b37091e7df81d30de170e8dcb8531 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Mon, 23 Dec 2024 14:40:57 -0500 Subject: [PATCH 4/6] 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 | 75 +- .../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, 1105 insertions(+), 714 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 7c480c844..23b4ce5a6 100644 --- a/Arrowgene.Ddon.Cli/Command/ServerCommand.cs +++ b/Arrowgene.Ddon.Cli/Command/ServerCommand.cs @@ -20,7 +20,7 @@ public class ServerCommand : ICommand 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 @@ public CommandResultType Run(CommandParameter parameter) _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 @@ public CommandResultType Run(CommandParameter parameter) 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 634b035b6..70f265cf1 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 fdf3f6cd9..e2f09c93d 100644 --- a/Arrowgene.Ddon.GameServer/DdonGameServer.cs +++ b/Arrowgene.Ddon.GameServer/DdonGameServer.cs @@ -34,6 +34,7 @@ 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 @@ public DdonGameServer(GameServerSetting setting, GameLogicSetting gameLogicSetti { 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 @@ public DdonGameServer(GameServerSetting setting, GameLogicSetting gameLogicSetti 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 000000000..4ac0010b6 --- /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 b4c11fce8..f71a3fbcd 100644 --- a/Arrowgene.Ddon.GameServer/Scripting/Modules/NpcExtendedFacilityModule.cs +++ b/Arrowgene.Ddon.GameServer/Scripting/Modules/NpcExtendedFacilityModule.cs @@ -12,6 +12,7 @@ public class NpcExtendedFacilityModule : ScriptModule 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 @@ public override ScriptOptions Options() .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 6dd09507d..e9769b6a2 100644 --- a/Arrowgene.Ddon.LoginServer/DdonLoginServer.cs +++ b/Arrowgene.Ddon.LoginServer/DdonLoginServer.cs @@ -26,6 +26,7 @@ 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 fda61b6b2..6d91efd53 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 b3241f519..69edc55dd 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 6ff990843..000000000 --- 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 11119c170..000000000 --- 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 343ce2668..a61b5f9e1 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 - { - public GlobalVariables(DdonGameServer server) - { - Server = server; - } - - public DdonGameServer Server { get; } - }; - - public NpcExtendedFacilityModule NpcExtendedFacilityModule { get; private set; } = new NpcExtendedFacilityModule(); + private static readonly ServerLogger Logger = LogProvider.Logger(typeof(ScriptManager)); - private Dictionary ScriptModules; + protected Dictionary ScriptModules { get; private set; } + public string ScriptsRoot { get; private set; } + public T GlobalVariables { get; protected set; } - public ScriptManager(DdonGameServer server) + public ScriptManager(string assetsPath) { - 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 @@ private void CompileScript(ScriptModule module, string path) 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 @@ private void CompileScript(ScriptModule module, string path) } } - private void CompileScripts() + protected void CompileScripts() { foreach (var module in ScriptModules.Values) { @@ -83,6 +68,11 @@ private void CompileScripts() 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 @@ private void SetupFileWatchers() { 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 @@ private void SetupFileWatchers() // 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 f37f24ee3..a0484bfa4 100644 --- a/Arrowgene.Ddon.GameServer/Scripting/ScriptModule.cs +++ b/Arrowgene.Ddon.Server/Scripting/ScriptModule.cs @@ -11,6 +11,13 @@ public abstract class ScriptModule 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 @@ public ScriptModule() /// /// 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 1634879cf..3635c9e6c 100644 --- a/Arrowgene.Ddon.GameServer/Scripting/ScriptUtils.cs +++ b/Arrowgene.Ddon.Server/Scripting/ScriptUtils.cs @@ -2,8 +2,6 @@ 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 000000000..6d72a861c --- /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 000000000..01b01fca4 --- /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 000000000..4e3be7e78 --- /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 000000000..60b03ce85 --- /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 339cf536c..4c85d4096 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 9f31d788f..000000000 --- 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 000000000..c24f887cb --- /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 000000000..23f16c77b --- /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 000000000..b370ce50c --- /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 000000000..14bd7fe2a --- /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 4c554c8b5..7365fcbd3 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 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 void CalculateRecipeProductionSpeed_ShouldReturnReducedTime() { 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 void CalculateRecipeProductionSpeed_ShouldReturnZeroCraftTime_AdditionalF { 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); From f832406f739df5300a54f6e712c83b85117062cf Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Mon, 23 Dec 2024 17:38:08 -0500 Subject: [PATCH 5/6] feat: Implement 'game_item' module - Created a new 'game_item' module. - Created RookiesRing.csx in the 'game_item' module. - Moved Rookie's Ring settings from general server logic to specific RookiesRing.csx settings file. - Implemented a new 'constant' and 'dynamic' mode for the Rookie's Ring. - Added a new option to completely disable the Rookie's Ring. --- .../Characters/ExpManager.cs | 23 +++--- .../Handler/ItemUseBagItemHandler.cs | 12 +-- .../Scripting/GameServerScriptManager.cs | 2 + .../Scripting/Interfaces/IGameItem.cs | 21 +++++ .../Scripting/Modules/GameItemModule.cs | 78 +++++++++++++++++++ .../Scripting/ScriptManager.cs | 16 ++-- .../Scripting/interfaces/GameLogicSetting.cs | 29 ++----- .../Files/Assets/scripts/game_items/README.md | 7 ++ .../Assets/scripts/game_items/RookiesRing.csx | 70 +++++++++++++++++ .../scripts/settings/GameLogicSettings.csx | 5 -- .../Files/Assets/scripts/settings/README.md | 2 + .../settings/game_items/RookiesRing.csx | 38 +++++++++ Arrowgene.Ddon.Shared/Model/ItemId.cs | 14 ++++ Arrowgene.Ddon.Shared/Util.cs | 26 +++++-- .../GameServer/Characters/CraftManagerTest.cs | 29 +++++-- 15 files changed, 303 insertions(+), 69 deletions(-) create mode 100644 Arrowgene.Ddon.GameServer/Scripting/Interfaces/IGameItem.cs create mode 100644 Arrowgene.Ddon.GameServer/Scripting/Modules/GameItemModule.cs create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/scripts/game_items/README.md create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/scripts/game_items/RookiesRing.csx create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/scripts/settings/game_items/RookiesRing.csx create mode 100644 Arrowgene.Ddon.Shared/Model/ItemId.cs diff --git a/Arrowgene.Ddon.GameServer/Characters/ExpManager.cs b/Arrowgene.Ddon.GameServer/Characters/ExpManager.cs index 70f265cf1..3185d7a86 100644 --- a/Arrowgene.Ddon.GameServer/Characters/ExpManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/ExpManager.cs @@ -699,30 +699,25 @@ public static uint TotalExpToLevelUpTo(uint level, GameMode gameMode) return totalExp; } - private double RookiesRingBonus() - { - return _GameSettings.RookiesRingBonus; - } - - private uint RookiesRingMaxLevel() - { - return _GameSettings.RookiesRingMaxLevel; - } - private uint GetRookiesRingBonus(CharacterCommon characterCommon, uint baseExpAmount) { - if (characterCommon.ActiveCharacterJobData.Lv > RookiesRingMaxLevel()) + if (!_Server.GameLogicSettings.Get("RookiesRing", "EnableRookiesRing")) { return 0; } - if (!characterCommon.Equipment.GetItems(EquipType.Performance).Exists(x => x?.ItemId == 11718)) + if (!characterCommon.Equipment.GetItems(EquipType.Performance).Exists(x => x?.ItemId == (uint) ItemId.RookiesRing)) { return 0; } - double result = baseExpAmount * RookiesRingBonus(); - return (uint)result; + var rookiesRingInterface = _Server.ScriptManager.GameItemModule.GetItemInterface(ItemId.RookiesRing); + if (rookiesRingInterface == null) + { + return baseExpAmount; + } + + return (uint)(baseExpAmount * rookiesRingInterface.GetBonusMultiplier(_Server, characterCommon)); } private uint GetCourseExpBonus(CharacterCommon characterCommon, uint baseExpAmount, RewardSource source, QuestType questType) diff --git a/Arrowgene.Ddon.GameServer/Handler/ItemUseBagItemHandler.cs b/Arrowgene.Ddon.GameServer/Handler/ItemUseBagItemHandler.cs index 024b03984..3eb273d6e 100644 --- a/Arrowgene.Ddon.GameServer/Handler/ItemUseBagItemHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/ItemUseBagItemHandler.cs @@ -1,14 +1,11 @@ using Arrowgene.Ddon.Server; using Arrowgene.Ddon.Server.Network; using Arrowgene.Ddon.Shared.Entity.PacketStructure; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Model; using Arrowgene.Ddon.Shared.Network; using Arrowgene.Logging; -using Arrowgene.Ddon.GameServer.Dump; -using Arrowgene.Ddon.Shared.Entity.Structure; -using System.Collections.Generic; using System.Linq; -using Arrowgene.Ddon.Shared.Model; -using Arrowgene.Ddon.GameServer.Characters; namespace Arrowgene.Ddon.GameServer.Handler { @@ -51,6 +48,11 @@ public override void Handle(GameClient client, StructurePacket private DdonGameServer Server { get; } private GlobalVariables Globals { get; } public NpcExtendedFacilityModule NpcExtendedFacilityModule { get; private set; } = new NpcExtendedFacilityModule(); + public GameItemModule GameItemModule { get; private set; } = new GameItemModule(); public GameServerScriptManager(DdonGameServer server) : base(server.AssetRepository.AssetsPath) { @@ -29,6 +30,7 @@ public GameServerScriptManager(DdonGameServer server) : base(server.AssetReposit // Add modules to the list so the generic logic can iterate over all scripting modules ScriptModules[NpcExtendedFacilityModule.ModuleRoot] = NpcExtendedFacilityModule; + ScriptModules[GameItemModule.ModuleRoot] = GameItemModule; } public override void Initialize() diff --git a/Arrowgene.Ddon.GameServer/Scripting/Interfaces/IGameItem.cs b/Arrowgene.Ddon.GameServer/Scripting/Interfaces/IGameItem.cs new file mode 100644 index 000000000..101700f42 --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Scripting/Interfaces/IGameItem.cs @@ -0,0 +1,21 @@ +using Arrowgene.Ddon.Shared.Model; +using System.Collections.Generic; + +namespace Arrowgene.Ddon.GameServer.Scripting.Interfaces +{ + public abstract class IGameItem + { + public abstract ItemId ItemId { get; } + + /** + * @brief Called when an item is used by a player. + */ + public abstract void OnUse(DdonGameServer server, GameClient client); + + /** + * Called for items which have an impact to the player or pawn when they are + * equipped when completing certain actions such as killing enemies or completing quests. + */ + public abstract double GetBonusMultiplier(DdonGameServer server, CharacterCommon characterCommon); + } +} diff --git a/Arrowgene.Ddon.GameServer/Scripting/Modules/GameItemModule.cs b/Arrowgene.Ddon.GameServer/Scripting/Modules/GameItemModule.cs new file mode 100644 index 000000000..a442162ea --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Scripting/Modules/GameItemModule.cs @@ -0,0 +1,78 @@ +using Arrowgene.Ddon.GameServer.Scripting.Interfaces; +using Arrowgene.Ddon.Shared; +using Arrowgene.Ddon.Shared.Model; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Scripting; +using System.Collections.Generic; + +namespace Arrowgene.Ddon.GameServer.Scripting +{ + public class GameItemModule : ScriptModule + { + public override string ModuleRoot => "game_items"; + public override string Filter => "*.csx"; + public override bool ScanSubdirectories => true; + public override bool EnableHotLoad => true; + + private Dictionary Items { get; set; } + + public bool HasItem(ItemId itemId) + { + return Items.ContainsKey(itemId); + } + + public bool HasItem(uint itemId) + { + return HasItem((ItemId)itemId); + } + + public IGameItem? GetItemInterface(ItemId itemId) + { + if (!Items.ContainsKey(itemId)) + { + return null; + } + return Items[itemId]; + } + + public IGameItem? GetItemInterface(uint itemId) + { + return GetItemInterface((ItemId)itemId); + } + + public GameItemModule() + { + Items = new Dictionary(); + } + + public override ScriptOptions Options() + { + return ScriptOptions.Default + .AddReferences(MetadataReference.CreateFromFile(typeof(DdonGameServer).Assembly.Location)) + .AddReferences(MetadataReference.CreateFromFile(typeof(AssetRepository).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.Characters") + .AddImports("Arrowgene.Ddon.GameServer.Scripting") + .AddImports("Arrowgene.Ddon.GameServer.Scripting.Interfaces") + .AddImports("Arrowgene.Ddon.Shared.Entity.PacketStructure") + .AddImports("Arrowgene.Ddon.Shared.Entity.Structure") + .AddImports("Arrowgene.Ddon.Shared.Model.Quest"); + } + + public override bool EvaluateResult(string path, ScriptState result) + { + if (result == null) + { + return false; + } + + IGameItem item = (IGameItem)result.ReturnValue; + Items[item.ItemId] = item; + + return true; + } + } +} diff --git a/Arrowgene.Ddon.Server/Scripting/ScriptManager.cs b/Arrowgene.Ddon.Server/Scripting/ScriptManager.cs index a61b5f9e1..d2885e695 100644 --- a/Arrowgene.Ddon.Server/Scripting/ScriptManager.cs +++ b/Arrowgene.Ddon.Server/Scripting/ScriptManager.cs @@ -3,8 +3,10 @@ using Arrowgene.Logging; using Microsoft.CodeAnalysis.CSharp.Scripting; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Scripting; using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using static Arrowgene.Ddon.Server.ServerScriptManager; @@ -34,19 +36,20 @@ protected void Initialize(T globalVariables) SetupFileWatchers(); } - protected void CompileScript(ScriptModule module, string path) + protected async void CompileScript(ScriptModule module, string path) { try { Logger.Info(path); + var code = Util.ReadAllText(path); var script = CSharpScript.Create( - code: Util.ReadAllText(path), + code: code, options: module.Options(), globalsType: typeof(T) ); - var result = script.RunAsync(GlobalVariables).Result; + var result = await script.RunAsync(GlobalVariables); if (!module.EvaluateResult(path, result)) { Logger.Error($"Failed to evaluate the result of executing '{path}'"); @@ -66,13 +69,8 @@ protected void CompileScripts() var path = $"{ScriptsRoot}\\{module.ModuleRoot}"; Logger.Info($"Compiling scripts for module '{module.ModuleRoot}'"); - foreach (var file in Directory.EnumerateFiles(path)) + foreach (var file in Directory.GetFiles(path, "*.csx", SearchOption.AllDirectories)) { - if (Path.GetExtension(file) != ".csx") - { - continue; - } - module.Scripts.Add(file); CompileScript(module, file); } diff --git a/Arrowgene.Ddon.Server/Scripting/interfaces/GameLogicSetting.cs b/Arrowgene.Ddon.Server/Scripting/interfaces/GameLogicSetting.cs index 6d72a861c..f1947c498 100644 --- a/Arrowgene.Ddon.Server/Scripting/interfaces/GameLogicSetting.cs +++ b/Arrowgene.Ddon.Server/Scripting/interfaces/GameLogicSetting.cs @@ -17,6 +17,11 @@ private T GetSetting(string key) return SettingsData.Get("GameLogicSettings", key); } + public T Get(string scriptName, string key) + { + return SettingsData.Get(scriptName, key); + } + /// /// Additional factor to change how long crafting a recipe will take to finish. /// @@ -39,30 +44,6 @@ public double 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. diff --git a/Arrowgene.Ddon.Shared/Files/Assets/scripts/game_items/README.md b/Arrowgene.Ddon.Shared/Files/Assets/scripts/game_items/README.md new file mode 100644 index 000000000..b52cc7c51 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/scripts/game_items/README.md @@ -0,0 +1,7 @@ +# Game Items + +This module handles the implementation of items which requires the server to be involved. + +- When creating a new `game_item`, create a `csx` file which matches the name of the item. +- If an item has many settings or complex settings, create a new settings file for the item + under `settings/game_items`, named the same as the `Item.csx` diff --git a/Arrowgene.Ddon.Shared/Files/Assets/scripts/game_items/RookiesRing.csx b/Arrowgene.Ddon.Shared/Files/Assets/scripts/game_items/RookiesRing.csx new file mode 100644 index 000000000..09aad11a0 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/scripts/game_items/RookiesRing.csx @@ -0,0 +1,70 @@ +/** + * @brief Settings for this object can be found in + * /scripts/settings/game_items/RookiesRing.csx + */ +public class GameItem : IGameItem +{ + private class RingMode + { + public const uint Constant = 0; + public const uint Dynamic = 1; + } + + public override ItemId ItemId => ItemId.RookiesRing; + + public GameItem() + { + } + + public override void OnUse(DdonGameServer server, GameClient client) + { + // The rookies ring has no OnUse behavior + } + + private double CalculateConstantBonus(DdonGameServer server, CharacterCommon characterCommon) + { + if (characterCommon.ActiveCharacterJobData.Lv > server.GameLogicSettings.Get("RookiesRing", "RookiesRingMaxLevel")) + { + return 0; + } + + return server.GameLogicSettings.Get("RookiesRing", "RookiesRingBonus"); + } + + private double CalculateDynamicBonus(DdonGameServer server, CharacterCommon characterCommon) + { + var dynamicBands = server.GameLogicSettings.Get>("RookiesRing", "DynamicExpBands"); + + var characterLv = characterCommon.ActiveCharacterJobData.Lv; + + foreach (var band in dynamicBands) + { + if (characterLv >= band.MinLv && characterLv <= band.MaxLv) + { + return band.ExpMultiplier; + } + } + return 0; + } + + public override double GetBonusMultiplier(DdonGameServer server, CharacterCommon characterCommon) + { + double bonus = 0; + + var mode = server.GameLogicSettings.Get("RookiesRing", "RookiesRingMode"); + switch (mode) + { + case RingMode.Dynamic: + bonus = CalculateDynamicBonus(server, characterCommon); + break; + default: + // Mode is either 0 or an invalid value, so treat it as zero + bonus = CalculateConstantBonus(server, characterCommon); + break; + } + + return bonus; + } +} + +return new GameItem(); diff --git a/Arrowgene.Ddon.Shared/Files/Assets/scripts/settings/GameLogicSettings.csx b/Arrowgene.Ddon.Shared/Files/Assets/scripts/settings/GameLogicSettings.csx index b370ce50c..212a8b5fb 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/scripts/settings/GameLogicSettings.csx +++ b/Arrowgene.Ddon.Shared/Files/Assets/scripts/settings/GameLogicSettings.csx @@ -22,11 +22,6 @@ 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 /** diff --git a/Arrowgene.Ddon.Shared/Files/Assets/scripts/settings/README.md b/Arrowgene.Ddon.Shared/Files/Assets/scripts/settings/README.md index 14bd7fe2a..05cbaed15 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/scripts/settings/README.md +++ b/Arrowgene.Ddon.Shared/Files/Assets/scripts/settings/README.md @@ -7,6 +7,8 @@ This file attemps to establish some guidelines when defining new settings which 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. +If a certain category has many settings files, consider to create a subdirectory which contains all related or similar settings scripts. + ## Naming Guidelines #### Enable Variables diff --git a/Arrowgene.Ddon.Shared/Files/Assets/scripts/settings/game_items/RookiesRing.csx b/Arrowgene.Ddon.Shared/Files/Assets/scripts/settings/game_items/RookiesRing.csx new file mode 100644 index 000000000..96905915a --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/scripts/settings/game_items/RookiesRing.csx @@ -0,0 +1,38 @@ +/** + * @brief Controls if the Rookies Ring is enabled or not. + */ +bool EnableRookiesRing = true; + +/** + * @brief Controls the behavior of the Rookie Ring if it is enabled. + * Constant = 0 + * This is the behavior of the ring as it was in the original game. + * There is a constant exp buff until the maxiumum configured level is exceded. + * Dynamic = 1 + * This is a new custom behavior which dynamiclly adjusts the bonus of the Rookie Ring + * until the maximum level is execeded. + */ +uint RookiesRingMode = 0; + +/** + * @brief Settings used when the RookiesRingMode = 0 (Constant) + */ +uint RookiesRingMaxLevel = 89; +double RookiesRingBonus = 1.0; + +/** + * @brief Settings used when the RookiesRingMode = 1 (Dynamic) + */ +var DynamicExpBands = new List<(uint MinLv, uint MaxLv, double ExpMultiplier)>() +{ + // MinLv, MaxLv, ExpMultiplier + ( 1, 9, 1.5), + ( 10, 19, 1.0), + ( 20, 29, 0.9), + ( 30, 39, 0.8), + ( 40, 49, 0.7), + ( 50, 59, 0.6), + ( 60, 69, 0.5), + ( 70, 79, 0.4), + ( 80, 89, 0.3), +}; diff --git a/Arrowgene.Ddon.Shared/Model/ItemId.cs b/Arrowgene.Ddon.Shared/Model/ItemId.cs new file mode 100644 index 000000000..ade52d438 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Model/ItemId.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Arrowgene.Ddon.Shared.Model +{ + // TODO: Generate this file from GMD info + public enum ItemId : uint + { + RookiesRing = 11718 + } +} diff --git a/Arrowgene.Ddon.Shared/Util.cs b/Arrowgene.Ddon.Shared/Util.cs index 171f59700..d267208e2 100644 --- a/Arrowgene.Ddon.Shared/Util.cs +++ b/Arrowgene.Ddon.Shared/Util.cs @@ -449,22 +449,34 @@ public static string ToArcPath(string path) /// /// Implements a safer way to read all lines in a file when there is contention on the file access. + /// Note that, it is possible that sometimes when the file is read, due to file system quirkyness + /// it might read back a zero length string. Due to this behavior, there is a retry mechanism of + /// 4 attempts. If the file is truly empty, it will have a worse case scenario of reading the + /// empty file MAX_ATTEMPTS times. /// /// Path to a text based file to read /// Returns the contents of the file read as a string public static string ReadAllText(string path) { - List content = new List(); - using (FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) - using (StreamReader reader = new StreamReader(stream)) + uint attempts = 0; + const uint MAX_ATTEMPTS = 5; + + string content; + do { - while (!reader.EndOfStream) + List lines = new List(); + using (FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + using (StreamReader reader = new StreamReader(stream)) { - content.Add(reader.ReadLine()); + while (!reader.EndOfStream) + { + lines.Add(reader.ReadLine()); + } + content = string.Join("\n", lines); } - } + } while (content == "" && attempts++ < MAX_ATTEMPTS); - return string.Join("\n", content); + return content; } } } diff --git a/Arrowgene.Ddon.Test/GameServer/Characters/CraftManagerTest.cs b/Arrowgene.Ddon.Test/GameServer/Characters/CraftManagerTest.cs index 7365fcbd3..08d931d0e 100644 --- a/Arrowgene.Ddon.Test/GameServer/Characters/CraftManagerTest.cs +++ b/Arrowgene.Ddon.Test/GameServer/Characters/CraftManagerTest.cs @@ -22,14 +22,33 @@ public CraftManagerTest() var settings = new GameServerSetting(); _scriptableSettings = new ScriptableSettings(); - _scriptableSettings.Set("GameLogicSettings", "GameClockTimescale", 90); - _scriptableSettings.Set("GameLogicSettings", "WeatherSequenceLength", 20); + _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 }); + _scriptableSettings.Set("GameLogicSettings", "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}, + }); var gameLogicSetting = new GameLogicSetting(_scriptableSettings); _mockServer = new DdonGameServer(settings, gameLogicSetting, new MockDatabase(), new AssetRepository("TestFiles")); @@ -40,7 +59,7 @@ public CraftManagerTest() public void GetCraftingTimeReductionRate_ShouldReturnCorrectValue() { List productionSpeedLevels = new List { 10, 20, 30 }; - _scriptableSettings.Set("GameLogicSettings", "AdditionalProductionSpeedFactor", 1.0); + _scriptableSettings.Set("GameLogicSettings", "AdditionalProductionSpeedFactor", 1.0); double result = _craftManager.GetCraftingTimeReductionRate(productionSpeedLevels); @@ -52,7 +71,7 @@ public void CalculateRecipeProductionSpeed_ShouldReturnReducedTime() { List productionSpeedLevels = new List { 70, 70, 70, 70 }; const uint recipeTime = 100; - _scriptableSettings.Set("GameLogicSettings", "AdditionalProductionSpeedFactor", 1.0); + _scriptableSettings.Set("GameLogicSettings", "AdditionalProductionSpeedFactor", 1.0); uint result = _craftManager.CalculateRecipeProductionSpeed(recipeTime, productionSpeedLevels); @@ -64,7 +83,7 @@ public void CalculateRecipeProductionSpeed_ShouldReturnZeroCraftTime_AdditionalF { List productionSpeedLevels = new List { 70, 70, 70, 70 }; const uint recipeTime = 100; - _scriptableSettings.Set("GameLogicSettings", "AdditionalProductionSpeedFactor", 100.0); + _scriptableSettings.Set("GameLogicSettings", "AdditionalProductionSpeedFactor", 100.0); uint result = _craftManager.CalculateRecipeProductionSpeed(recipeTime, productionSpeedLevels); From 976ff45a8cd239b2f19d7c8d0d478f520f3e7fe9 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Mon, 23 Dec 2024 21:36:26 -0500 Subject: [PATCH 6/6] feat: Add mixin module Added a mixin module which is used to collect various scripts which implement common functionality shared across the server and other scripts. --- .../Handler/InstanceEnemyKillHandler.cs | 14 +++- .../Scripting/GameServerScriptManager.cs | 2 + .../Scripting/Interfaces/IExpCurveMixin.cs | 9 +++ .../Scripting/Modules/MixinModule.cs | 69 +++++++++++++++++++ .../Files/Assets/scripts/mixins/README.md | 3 + .../Files/Assets/scripts/mixins/exp_curve.csx | 17 +++++ .../scripts/settings/GameLogicSettings.csx | 9 +++ 7 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 Arrowgene.Ddon.GameServer/Scripting/Interfaces/IExpCurveMixin.cs create mode 100644 Arrowgene.Ddon.GameServer/Scripting/Modules/MixinModule.cs create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/scripts/mixins/README.md create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/scripts/mixins/exp_curve.csx diff --git a/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs b/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs index f703bcc0a..e693d1296 100644 --- a/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs @@ -1,6 +1,7 @@ using Arrowgene.Ddon.GameServer.Characters; using Arrowgene.Ddon.GameServer.Party; using Arrowgene.Ddon.GameServer.Quests; +using Arrowgene.Ddon.GameServer.Scripting.Interfaces; using Arrowgene.Ddon.Server; using Arrowgene.Ddon.Server.Network; using Arrowgene.Ddon.Shared.Entity.PacketStructure; @@ -173,7 +174,18 @@ public override S2CInstanceEnemyKillRes Handle(GameClient client, C2SInstanceEne } } - uint baseEnemyExp = _gameServer.ExpManager.GetScaledPointAmount(RewardSource.Enemy, ExpType.ExperiencePoints, enemyKilled.GetDroppedExperience()); + uint baseEnemyExp = 0; + if (Server.GameLogicSettings.Get("GameLogicSettings", "EnableExpCalculationMixin")) + { + var expCurveMixin = Server.ScriptManager.MixinModule.Get("exp_curve"); + baseEnemyExp = expCurveMixin.GetExpValue(enemyKilled); + } + else + { + baseEnemyExp = enemyKilled.GetDroppedExperience(); + } + + baseEnemyExp = _gameServer.ExpManager.GetScaledPointAmount(RewardSource.Enemy, ExpType.ExperiencePoints, baseEnemyExp); uint calcExp = _gameServer.ExpManager.GetAdjustedExp(client.GameMode, RewardSource.Enemy, client.Party, baseEnemyExp, enemyKilled.Lv); uint calcPP = _gameServer.ExpManager.GetScaledPointAmount(RewardSource.Enemy, ExpType.PlayPoints, enemyKilled.GetDroppedPlayPoints()); diff --git a/Arrowgene.Ddon.GameServer/Scripting/GameServerScriptManager.cs b/Arrowgene.Ddon.GameServer/Scripting/GameServerScriptManager.cs index 2390c9a9e..289b24c8b 100644 --- a/Arrowgene.Ddon.GameServer/Scripting/GameServerScriptManager.cs +++ b/Arrowgene.Ddon.GameServer/Scripting/GameServerScriptManager.cs @@ -22,6 +22,7 @@ public class GameServerScriptManager : ScriptManager private GlobalVariables Globals { get; } public NpcExtendedFacilityModule NpcExtendedFacilityModule { get; private set; } = new NpcExtendedFacilityModule(); public GameItemModule GameItemModule { get; private set; } = new GameItemModule(); + public MixinModule MixinModule { get; private set; } = new MixinModule(); public GameServerScriptManager(DdonGameServer server) : base(server.AssetRepository.AssetsPath) { @@ -31,6 +32,7 @@ public GameServerScriptManager(DdonGameServer server) : base(server.AssetReposit // Add modules to the list so the generic logic can iterate over all scripting modules ScriptModules[NpcExtendedFacilityModule.ModuleRoot] = NpcExtendedFacilityModule; ScriptModules[GameItemModule.ModuleRoot] = GameItemModule; + ScriptModules[MixinModule.ModuleRoot] = MixinModule; } public override void Initialize() diff --git a/Arrowgene.Ddon.GameServer/Scripting/Interfaces/IExpCurveMixin.cs b/Arrowgene.Ddon.GameServer/Scripting/Interfaces/IExpCurveMixin.cs new file mode 100644 index 000000000..9b5b0dc36 --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Scripting/Interfaces/IExpCurveMixin.cs @@ -0,0 +1,9 @@ +using Arrowgene.Ddon.Shared.Model; + +namespace Arrowgene.Ddon.GameServer.Scripting.Interfaces +{ + public abstract class IExpCurveMixin + { + public abstract uint GetExpValue(InstancedEnemy enemy); + } +} diff --git a/Arrowgene.Ddon.GameServer/Scripting/Modules/MixinModule.cs b/Arrowgene.Ddon.GameServer/Scripting/Modules/MixinModule.cs new file mode 100644 index 000000000..6b8057995 --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Scripting/Modules/MixinModule.cs @@ -0,0 +1,69 @@ +using Arrowgene.Ddon.Shared; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Scripting; +using System; +using System.Collections.Generic; +using System.Dynamic; +using System.IO; +using System.Runtime.CompilerServices; + +namespace Arrowgene.Ddon.GameServer.Scripting +{ + public class MixinModule : ScriptModule + { + public override string ModuleRoot => "mixins"; + public override string Filter => "*.csx"; + public override bool ScanSubdirectories => true; + public override bool EnableHotLoad => true; + + private Dictionary Mixins { get; set; } + + public MixinModule() + { + Mixins = new Dictionary(); + } + + public T Get(string scriptName) + { + if (!Mixins.ContainsKey(scriptName)) + { + throw new Exception($"A mixin with the name '{scriptName}' doesn't exist"); + } + + return (T) Mixins[scriptName]; + } + + public override ScriptOptions Options() + { + return ScriptOptions.Default + .AddReferences(MetadataReference.CreateFromFile(typeof(DdonGameServer).Assembly.Location)) + .AddReferences(MetadataReference.CreateFromFile(typeof(AssetRepository).Assembly.Location)) + .AddReferences(MetadataReference.CreateFromFile(typeof(DynamicAttribute).Assembly.Location)) + .AddImports("System", "System.Collections", "System.Collections.Generic") + .AddImports("System.Runtime.CompilerServices") + .AddImports("Arrowgene.Ddon.Shared") + .AddImports("Arrowgene.Ddon.Shared.Model") + .AddImports("Arrowgene.Ddon.GameServer") + .AddImports("Arrowgene.Ddon.GameServer.Characters") + .AddImports("Arrowgene.Ddon.GameServer.Scripting") + .AddImports("Arrowgene.Ddon.GameServer.Scripting.Interfaces") + .AddImports("Arrowgene.Ddon.Shared.Entity.PacketStructure") + .AddImports("Arrowgene.Ddon.Shared.Entity.Structure") + .AddImports("Arrowgene.Ddon.Shared.Model.Quest"); + } + + public override bool EvaluateResult(string path, ScriptState result) + { + if (result == null) + { + return false; + } + + // Mixins are c# Func<> with arbitary return and params based on the functionality + string mixinName = Path.GetFileNameWithoutExtension(path); + Mixins[mixinName] = result.ReturnValue; + + return true; + } + } +} diff --git a/Arrowgene.Ddon.Shared/Files/Assets/scripts/mixins/README.md b/Arrowgene.Ddon.Shared/Files/Assets/scripts/mixins/README.md new file mode 100644 index 000000000..ef8e3399d --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/scripts/mixins/README.md @@ -0,0 +1,3 @@ +# Mixins + +Mixins are a collection of configurable functionality which can be shared across various server components and scripts, but is not tied to a particular feature. \ No newline at end of file diff --git a/Arrowgene.Ddon.Shared/Files/Assets/scripts/mixins/exp_curve.csx b/Arrowgene.Ddon.Shared/Files/Assets/scripts/mixins/exp_curve.csx new file mode 100644 index 000000000..d54dde364 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/scripts/mixins/exp_curve.csx @@ -0,0 +1,17 @@ +/** + * @breif This mixin is used to calculate the exp amount for a given + * enemy based on observations from realworld exp data. It should be + * possible to call this script directly from the server code or from + * other scripts. + */ + +public class Mixin : IExpCurveMixin +{ + public override uint GetExpValue(InstancedEnemy enemy) + { + // TODO: Implement algorithm to calculate exp amount + return enemy.GetDroppedExperience(); + } +} + +return new Mixin(); diff --git a/Arrowgene.Ddon.Shared/Files/Assets/scripts/settings/GameLogicSettings.csx b/Arrowgene.Ddon.Shared/Files/Assets/scripts/settings/GameLogicSettings.csx index 212a8b5fb..5b16b014b 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/scripts/settings/GameLogicSettings.csx +++ b/Arrowgene.Ddon.Shared/Files/Assets/scripts/settings/GameLogicSettings.csx @@ -17,6 +17,15 @@ bool EnableVisualEquip = true; uint DefaultWarpFavorites = 3; uint LaternBurnTimeInSeconds = 1500; +/** + * @brief If enabled, overrides default EXP values assigned to enemies + * in the InstanceEnemyKillHandler and uses the calculations inside of + * /mixins/exp_curve.csx. + * + * @note Currently returns same exp amount as the default setting. + */ +bool EnableExpCalculationMixin = false; + // Crafting Settings double AdditionalProductionSpeedFactor = 1.0; double AdditionalCostPerformanceFactor = 1.0;