Skip to content

Commit

Permalink
feat: Add C# scripting support
Browse files Browse the repository at this point in the history
- 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.
  • Loading branch information
pacampbell committed Dec 21, 2024
1 parent 4ae8eed commit ee1a11f
Show file tree
Hide file tree
Showing 34 changed files with 931 additions and 405 deletions.
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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
17 changes: 12 additions & 5 deletions Arrowgene.Ddon.Cli/Command/ServerCommand.cs
Original file line number Diff line number Diff line change
@@ -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
{
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
8 changes: 4 additions & 4 deletions Arrowgene.Ddon.Cli/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@
* along with Arrowgene.Ddon.Cli. If not, see <https://www.gnu.org/licenses/>.
*/

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
{
Expand Down
2 changes: 1 addition & 1 deletion Arrowgene.Ddon.GameServer/Arrowgene.Ddon.GameServer.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@
</PropertyGroup>
<Import Project="./../SetSourceRevision.targets" />
<ItemGroup>
<ProjectReference Include="..\Arrowgene.Ddon.Server\Arrowgene.Ddon.Server.csproj" />
<ProjectReference Include="..\Arrowgene.Ddon.Server\Arrowgene.Ddon.Server.csproj" />
</ItemGroup>
</Project>
16 changes: 16 additions & 0 deletions Arrowgene.Ddon.GameServer/Characters/EpitaphRoadManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1322,6 +1322,22 @@ public List<InstancedGatheringItem> 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;
}

/// <summary>
/// Called by the task manager. The main task will signal all channels
/// to flush the cached information queried by the player when first
Expand Down
10 changes: 2 additions & 8 deletions Arrowgene.Ddon.GameServer/Characters/StageManager.cs
Original file line number Diff line number Diff line change
@@ -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
{
Expand Down
7 changes: 6 additions & 1 deletion Arrowgene.Ddon.GameServer/DdonGameServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -91,6 +93,7 @@ public DdonGameServer(GameServerSetting setting, IDatabase database, AssetReposi

public event EventHandler<ClientConnectionChangeArgs> ClientConnectionChangeEvent;
public GameServerSetting Setting { get; }
public ScriptManager ScriptManager { get; }
public ChatManager ChatManager { get; }
public ItemManager ItemManager { get; }
public CraftManager CraftManager { get; }
Expand Down Expand Up @@ -129,14 +132,16 @@ public DdonGameServer(GameServerSetting setting, IDatabase database, AssetReposi

public override void Start()
{
ScriptManager.Initialize();

QuestManager.LoadQuests(this);
GpCourseManager.EvaluateCourses();

if (ServerUtils.IsHeadServer(this))
{
ScheduleManager.StartServerTasks();
}

LoadChatHandler();
LoadPacketHandler();
base.Start();
Expand Down
4 changes: 2 additions & 2 deletions Arrowgene.Ddon.GameServer/GameServerSetting.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand All @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
86 changes: 5 additions & 81 deletions Arrowgene.Ddon.GameServer/Handler/NpcGetExtendedFacilityHandler.cs
Original file line number Diff line number Diff line change
@@ -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
{
Expand All @@ -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<NpcId, Action<DdonGameServer, GameClient, S2CNpcGetNpcExtendedFacilityRes>> gNpcExtendedBehavior = new Dictionary<NpcId, Action<DdonGameServer, GameClient, S2CNpcGetNpcExtendedFacilityRes>>()
{
[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<CDataNpcExtendedFacilityMenuItem>()
{
// 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};
}
}
11 changes: 5 additions & 6 deletions Arrowgene.Ddon.GameServer/Quests/GenericQuest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
});
}

Expand Down Expand Up @@ -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<QuestLayoutFlagSetInfo>();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public class Mq030260_HopesBitterEnd : Quest
{
private static readonly ServerLogger Logger = LogProvider.Logger<ServerLogger>(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)
{
}

Expand Down
Loading

0 comments on commit ee1a11f

Please sign in to comment.