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.
- Converted extended NPC facilities into scripting .csx files for each
  NPC.
  • Loading branch information
pacampbell committed Dec 21, 2024
1 parent 4ae8eed commit 5740041
Show file tree
Hide file tree
Showing 25 changed files with 581 additions and 309 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
16 changes: 11 additions & 5 deletions Arrowgene.Ddon.Cli/Command/ServerCommand.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
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.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 +19,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 +110,12 @@ public CommandResultType Run(CommandParameter parameter)
_assetRepository.Initialize();
}

if (_scriptServerSettings == null)
{
_scriptServerSettings = new ScriptedServerSettings(_setting.AssetPath);
_scriptServerSettings.LoadSettings(_setting.GameServerSetting.GameLogicSetting);
}

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.CompileScripts();

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

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

LoadChatHandler();
LoadPacketHandler();
base.Start();
Expand Down
2 changes: 1 addition & 1 deletion 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 Down
78 changes: 4 additions & 74 deletions Arrowgene.Ddon.GameServer/Handler/NpcGetExtendedFacilityHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,86 +22,16 @@ public NpcGetExtendedFacilityHandler(DdonGameServer server) : base(server)
public override S2CNpcGetNpcExtendedFacilityRes Handle(GameClient client, C2SNpcGetNpcExtendedFacilityReq request)
{
var result = new S2CNpcGetNpcExtendedFacilityRes();
if (gNpcExtendedBehavior.ContainsKey(request.NpcId))

if (Server.ScriptManager.NpcExtendedFacilities.ContainsKey(request.NpcId))
{
result.NpcId = request.NpcId;
gNpcExtendedBehavior[request.NpcId](Server, client, result);
Server.ScriptManager.NpcExtendedFacilities[request.NpcId].GetExtendedOptions(Server, client, result);
}
return result;
}

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};
}
}
14 changes: 14 additions & 0 deletions Arrowgene.Ddon.GameServer/Scripting/DdonLibrary.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace Arrowgene.Ddon.GameServer.Scripting
{
public class DdonLibrary
{
public DdonLibrary()
{
}

public static uint GetValue()
{
return 1;
}
}
}
22 changes: 22 additions & 0 deletions Arrowgene.Ddon.GameServer/Scripting/INpcExtendedFacility.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using Arrowgene.Ddon.GameServer;
using Arrowgene.Ddon.Shared.Entity.PacketStructure;
using Arrowgene.Ddon.Shared.Model;

namespace Arrowgene.Ddon.GameServer.Scripting
{
public abstract class INpcExtendedFacility
{
/// <summary>
/// NPC ID associated with the extended options.
/// </summary>
public NpcId NpcId { get; protected set; }

/// <summary>
/// Gets extended menu options for the NPC.
/// </summary>
/// <param name="server"></param>
/// <param name="client"></param>
/// <param name="result">The result object for the extended NPC options</param>
public abstract void GetExtendedOptions(DdonGameServer server, GameClient client, S2CNpcGetNpcExtendedFacilityRes result);
}
}
81 changes: 81 additions & 0 deletions Arrowgene.Ddon.GameServer/Scripting/ScriptManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using Arrowgene.Ddon.Server;
using Arrowgene.Ddon.Shared;
using Arrowgene.Ddon.Shared.Model;
using Arrowgene.Logging;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;
using System.Collections.Generic;
using System.IO;

namespace Arrowgene.Ddon.GameServer.Scripting
{
public class ScriptManager
{
private static readonly ServerLogger Logger = LogProvider.Logger<ServerLogger>(typeof(ScriptManager));
public class Globals
{
public Globals(DdonGameServer server)
{
Server = server;
}

public DdonGameServer Server { get; }
};


public ScriptManager(DdonGameServer server)
{
Server = server;
ScriptsRoot = $"{server.AssetRepository.AssetsPath}\\scripts";
NpcExtendedFacilities = new Dictionary<NpcId, INpcExtendedFacility>();
}

private DdonGameServer Server { get; }
private string ScriptsRoot { get; set; }
public Dictionary<NpcId, INpcExtendedFacility> NpcExtendedFacilities { get; private set; }

public void CompileScripts()
{
var ExtendedFacilitiesPath = $"{ScriptsRoot}\\extended_facilities";

var options = 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.Shared.Entity.PacketStructure")
.AddImports("Arrowgene.Ddon.Shared.Entity.Structure")
.AddImports("Arrowgene.Ddon.Shared.Model.Quest");


var globals = new Globals(Server);

Logger.Info($"Compiling NPC extended facility scripts from {ExtendedFacilitiesPath}");
foreach (var file in Directory.EnumerateFiles(ExtendedFacilitiesPath))
{
Logger.Info($"{file}");

var script = CSharpScript.Create(
code: File.ReadAllText(file),
options: options,
globalsType: typeof(Globals)
);

var result = script.RunAsync(globals).Result;
if (result == null)
{
Logger.Error($"Failed to execute '{file}' successfully. Skipping.");
continue;
}

INpcExtendedFacility extendedFacility = (INpcExtendedFacility)result.ReturnValue;
NpcExtendedFacilities[extendedFacility.NpcId] = extendedFacility;
}
}
}
}
Loading

0 comments on commit 5740041

Please sign in to comment.