Skip to content
This repository was archived by the owner on Jun 17, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions src/Prima.Core.Server/Data/Internal/TimerDataObject.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
namespace Prima.Core.Server.Data.Internal;

public class TimerDataObject : IDisposable
{
private readonly object _lock = new object();
public string Name { get; set; }

public string Id { get; set; }

public double IntervalInMs { get; set; }

public Action Callback { get; set; }

public bool Repeat { get; set; }

public double RemainingTimeInMs = 0;

public double DelayInMs { get; set; }


public void DecrementRemainingTime(double deltaTime)
{
if (Monitor.TryEnter(_lock))
{
try
{
if (DelayInMs > 0)
{
DelayInMs -= deltaTime;
if (DelayInMs > 0)
{
return;
}
}

RemainingTimeInMs -= deltaTime;
}
finally
{
Monitor.Exit(_lock);
}
}
}

public void ResetRemainingTime()
{
if (Monitor.TryEnter(_lock))
{
try
{
RemainingTimeInMs = IntervalInMs;
}
finally
{
Monitor.Exit(_lock);
}
}
}


public override string ToString()
{
return $"Timer: {Name}, Id: {Id}, Interval: {IntervalInMs}, RemainingTime: {RemainingTimeInMs}, Repeat: {Repeat}";
}

public void Dispose()
{
Callback = null;
Name = null;
Id = null;
IntervalInMs = 0;
RemainingTimeInMs = 0;
Repeat = false;
DelayInMs = 0;

GC.SuppressFinalize(this);
}
}
12 changes: 12 additions & 0 deletions src/Prima.Core.Server/Interfaces/Services/ITimerService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Orion.Core.Server.Interfaces.Services.Base;

namespace Prima.Core.Server.Interfaces.Services;

public interface ITimerService : IOrionService, IOrionStartService, IDisposable
{
string RegisterTimer(string name, double intervalInMs, Action callback, double delayInMs = 0, bool repeat = false);

void UnregisterTimer(string timerId);

void UnregisterAllTimers();
}
61 changes: 61 additions & 0 deletions src/Prima.Core.Server/Modules/Scripts/FileScriptModule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using Orion.Core.Server.Attributes.Scripts;
using Orion.Core.Server.Data.Directories;
using Orion.Core.Server.Interfaces.Services.System;

namespace Prima.Core.Server.Modules.Scripts;

[ScriptModule("files")]
public class FileScriptModule
{
private readonly DirectoriesConfig _directoriesConfig;

private readonly IScriptEngineService _scriptEngineService;

public FileScriptModule(DirectoriesConfig directoriesConfig, IScriptEngineService scriptEngineService)
{
_directoriesConfig = directoriesConfig;
_scriptEngineService = scriptEngineService;
}


[ScriptFunction("Include a script file")]
public void IncludeScript(string fileName)
{
if (string.IsNullOrEmpty(fileName))
{
throw new ArgumentNullException(nameof(fileName), "File name cannot be null or empty");
}

var filePath = Path.Combine(_directoriesConfig.Root, fileName);

if (!File.Exists(filePath))
{
throw new FileNotFoundException($"Script file '{fileName}' not found in the scripts directory.");
}

_scriptEngineService.ExecuteScriptFile(filePath);
}

[ScriptFunction("Include all script files in a directory")]
public void IncludeScripts(string directory)
{
if (string.IsNullOrEmpty(directory))
{
throw new ArgumentNullException(nameof(directory), "Directory name cannot be null or empty");
}

var directoryPath = Path.Combine(_directoriesConfig.Root, directory);

if (!Directory.Exists(directoryPath))
{
throw new DirectoryNotFoundException($"Directory '{directory}' not found in the scripts directory.");
}

var scriptFiles = Directory.GetFiles(directoryPath, "*.js");

foreach (var scriptFile in scriptFiles)
{
_scriptEngineService.ExecuteScriptFile(scriptFile);
}
}
}
2 changes: 1 addition & 1 deletion src/Prima.Core.Server/Prima.Core.Server.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

<ItemGroup>
<PackageReference Include="LiteDB" Version="5.0.21" />
<PackageReference Include="Orion.Core.Server" Version="0.28.2" />
<PackageReference Include="Orion.Core.Server" Version="0.29.0" />
</ItemGroup>

<ItemGroup>
Expand Down
15 changes: 15 additions & 0 deletions src/Prima.Network/Packets/ClientVersionReq.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Prima.Network.Packets.Base;

namespace Prima.Network.Packets;

public class ClientVersionReq : BaseUoNetworkPacket
{
public ClientVersionReq() : base(0xBD, 3)
{
}

public override Span<byte> Write()
{
return new byte[] { 0x00, 0x03 };
}
}
6 changes: 3 additions & 3 deletions src/Prima.Network/Prima.Network.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.4" />
<PackageReference Include="Orion.Core.Server" Version="0.28.2" />
<PackageReference Include="Orion.Network.Core" Version="0.28.2" />
<PackageReference Include="Orion.Network.Tcp" Version="0.28.2" />
<PackageReference Include="Orion.Core.Server" Version="0.29.0" />
<PackageReference Include="Orion.Network.Core" Version="0.29.0" />
<PackageReference Include="Orion.Network.Tcp" Version="0.29.0" />
</ItemGroup>


Expand Down
44 changes: 43 additions & 1 deletion src/Prima.Server/Handlers/CharacterCreationHandler.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,29 @@
using Orion.Core.Server.Interfaces.Services.System;
using Prima.Core.Server.Data.Session;
using Prima.Core.Server.Handlers.Base;
using Prima.Core.Server.Interfaces.Listeners;
using Prima.Core.Server.Interfaces.Services;
using Prima.Network.Packets;
using Prima.Server.Modules.Scripts;
using Prima.UOData.Data.EventData;
using Prima.UOData.Interfaces.Services;
using Prima.UOData.Packets;

namespace Prima.Server.Handlers;

public class CharacterCreationHandler : BasePacketListenerHandler, INetworkPacketListener<CharacterCreation>
{
public CharacterCreationHandler(ILogger<CharacterCreationHandler> logger, INetworkService networkService, IServiceProvider serviceProvider) : base(logger, networkService, serviceProvider)
private readonly IScriptEngineService _scriptEngineService;

private readonly IMapService _mapService;

public CharacterCreationHandler(
ILogger<CharacterCreationHandler> logger, INetworkService networkService, IServiceProvider serviceProvider,
IScriptEngineService scriptEngineService, IMapService mapService
) : base(logger, networkService, serviceProvider)
{
_scriptEngineService = scriptEngineService;
_mapService = mapService;
}

protected override void RegisterHandlers()
Expand All @@ -19,6 +33,34 @@ protected override void RegisterHandlers()

public async Task OnPacketReceived(NetworkSession session, CharacterCreation packet)
{
// TODO: persist character creation data

TriggerCharacterCreatedEvent(packet);

await session.SendPacketAsync(new ClientVersionReq());
}

private void TriggerCharacterCreatedEvent(CharacterCreation packet)
{
var eventArgs = new CharacterCreatedEventArgs(
packet.Name,
packet.IsFemale,
packet.Hue,
packet.Int,
packet.Str,
packet.Dex,
_mapService.GetAvailableStartingCities()[packet.StartingLocation],
packet.Skills,
packet.ShirtColor,
packet.PantsColor,
packet.HairStyle,
packet.HairColor,
packet.FacialHair,
packet.FacialHairColor,
packet.Profession,
packet.Race
);

_scriptEngineService.ExecuteCallback(nameof(EventScriptModule.OnCharacterCreated), eventArgs);
}
}
22 changes: 22 additions & 0 deletions src/Prima.Server/Modules/Scripts/EventScriptModule.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Orion.Core.Server.Attributes.Scripts;
using Orion.Core.Server.Interfaces.Services.System;
using Prima.Core.Server.Contexts;
using Prima.UOData.Data.EventData;

namespace Prima.Server.Modules.Scripts;

Expand Down Expand Up @@ -50,4 +51,25 @@ public void OnUserLogin(Action<UserLoginContext> action)
}
);
}

[ScriptFunction("Register a callback when user creates a character")]
public void OnCharacterCreated(Action<CharacterCreatedEventArgs> action)
{
_scriptEngineService.AddCallback(
nameof(OnCharacterCreated),
context =>
{
if (context == null)
{
throw new ArgumentNullException(nameof(context), "Context cannot be null");
return;
}

if (context[0] is CharacterCreatedEventArgs characterCreatedEventArgs)
{
action(characterCreatedEventArgs);
}
}
);
}
}
69 changes: 69 additions & 0 deletions src/Prima.Server/Modules/Scripts/TimerScriptModule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using Orion.Core.Server.Attributes.Scripts;
using Prima.Core.Server.Interfaces.Services;

namespace Prima.Server.Modules.Scripts;

[ScriptModule("timers")]
public class TimerScriptModule
{
private readonly ITimerService _timerService;

public TimerScriptModule(ITimerService timerService)
{
_timerService = timerService;
}


[ScriptFunction("Register a timer")]
public string Register(
string name, int intervalInSeconds, Action callback, int delayInSeconds = 0, bool isRepeat = false
)
{
if (string.IsNullOrEmpty(name))
{
throw new ArgumentNullException(nameof(name), "Timer name cannot be null or empty");
}

if (intervalInSeconds <= 0)
{
throw new ArgumentOutOfRangeException(nameof(intervalInSeconds), "Interval must be greater than zero");
}

if (callback == null)
{
throw new ArgumentNullException(nameof(callback), "Callback cannot be null");
}

return _timerService.RegisterTimer(
name,
TimeSpan.FromSeconds(intervalInSeconds).TotalMilliseconds,
callback,
TimeSpan.FromSeconds(delayInSeconds).TotalMilliseconds,
isRepeat
);
}

[ScriptFunction("Register a timer that repeats")]
public string Repeated(string name, int intervalInSeconds, Action callback, int delayInSeconds = 0)
{
return Register(name, intervalInSeconds, callback, delayInSeconds, true);
}


[ScriptFunction("Register a timer that runs once")]
public string OneShot(string name, int intervalInSeconds, Action callback, int delayInSeconds = 0)
{
return Register(name, intervalInSeconds, callback, delayInSeconds, false);
}

[ScriptFunction("Unregister a timer")]
public void Unregister(string timerId)
{
if (string.IsNullOrEmpty(timerId))
{
throw new ArgumentNullException(nameof(timerId), "Timer ID cannot be null or empty");
}

_timerService.UnregisterTimer(timerId);
}
}
4 changes: 4 additions & 0 deletions src/Prima.Server/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
using Prima.Core.Server.Data.Options;
using Prima.Core.Server.Interfaces.Services;
using Prima.Core.Server.Modules.Container;
using Prima.Core.Server.Modules.Scripts;
using Prima.Core.Server.Types;
using Prima.Network.Modules;
using Prima.Server.Handlers;
Expand Down Expand Up @@ -103,13 +104,16 @@ static async Task Main(string[] args)
.AddService<IAssetService, AssetService>()
.AddService<INetworkService, NetworkService>()
.AddService<ISerialGeneratorService, SerialGeneratorService>()
.AddService<ITimerService, TimerService>()
.AddService<IClientConfigurationService, ClientConfigurationService>()
;

builder.Services
.AddScriptModule<EventScriptModule>()
.AddScriptModule<SchedulerModule>()
.AddScriptModule<VariableModule>()
.AddScriptModule<FileScriptModule>()
.AddScriptModule<TimerScriptModule>()
.AddScriptModule<CommandsScriptModule>();

builder.Services
Expand Down
Loading