Skip to content
Open
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
2 changes: 2 additions & 0 deletions SlipeServer.Console/Logic/ServerTestLogic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using SlipeServer.Packets.Lua.Camera;
using SlipeServer.Packets.Structs;
using SlipeServer.Server;
using SlipeServer.Server.Concepts;
using SlipeServer.Server.Constants;
using SlipeServer.Server.ElementCollections;
using SlipeServer.Server.Elements;
Expand Down Expand Up @@ -185,6 +186,7 @@
private void SetupTestElements()
{
this.testResource = this.resourceProvider.GetResource("TestResource");
this.testResource.AddClientTaskHelper();
this.secondTestResource = this.resourceProvider.GetResource("SecondTestResource");
this.secondTestResource.NoClientScripts[$"{this.secondTestResource!.Name}/testfile.lua"] =
Encoding.UTF8.GetBytes("outputChatBox(\"I AM A NOT CACHED MESSAGE\")");
Expand Down Expand Up @@ -266,7 +268,7 @@
this.Vehicle = new Vehicle(530, new Vector3(20, 5, 3)).AssociateWith(this.server);
this.Taxi = new Vehicle((ushort)VehicleModel.Taxi, new Vector3(20, -5, 3)).AssociateWith(this.server);
this.Rhino = new Vehicle((ushort)VehicleModel.Rhino, new Vector3(20, -25, 3)).AssociateWith(this.server);
this.Rhino.Jacked += (e, args) => this.logger.LogInformation($"Rhino has been jacked by {args.NewDriver.Name}, kicking out {args.PreviousDriver.Name}");

Check warning on line 271 in SlipeServer.Console/Logic/ServerTestLogic.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Don't use string interpolation in logging message templates.

See more on https://sonarcloud.io/project/issues?id=mta-slipe_Slipe-Server&issues=AZ1ddKu-S1usNdOdrg_1&open=AZ1ddKu-S1usNdOdrg_1&pullRequest=565

this.Elegy = new Vehicle(562, new Vector3(30, -20, 3)).AssociateWith(this.server);
this.Elegy.HealthChanged += (e, value) =>
Expand Down Expand Up @@ -561,7 +563,7 @@
if (ushort.TryParse(args.Arguments[0], out ushort slots))
{
this.server.SetMaxPlayers(slots);
this.logger.LogInformation("Slots has been changed to: {slots}", slots);

Check warning on line 566 in SlipeServer.Console/Logic/ServerTestLogic.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use PascalCase for named placeholders.

See more on https://sonarcloud.io/project/issues?id=mta-slipe_Slipe-Server&issues=AZ1ddKu-S1usNdOdrg_2&open=AZ1ddKu-S1usNdOdrg_2&pullRequest=565
}
}
};
Expand Down Expand Up @@ -688,7 +690,7 @@
if (byte.TryParse(args.Arguments[0], out byte interior))
{
args.Player.Camera.Interior = interior;
this.logger.LogInformation("Camera interior changed to: {interior}", interior);

Check warning on line 693 in SlipeServer.Console/Logic/ServerTestLogic.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use PascalCase for named placeholders.

See more on https://sonarcloud.io/project/issues?id=mta-slipe_Slipe-Server&issues=AZ1ddKu-S1usNdOdrg_3&open=AZ1ddKu-S1usNdOdrg_3&pullRequest=565
}
}
};
Expand Down Expand Up @@ -947,7 +949,7 @@
await Task.Delay(1000);
stopwatch.Stop();
var distance = Vector3.Distance(element.Position, new Vector3(0, 0, 3));
this.logger.LogInformation("Element travelled {distance} units in {ms} ms", distance, stopwatch.ElapsedMilliseconds);

Check warning on line 952 in SlipeServer.Console/Logic/ServerTestLogic.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use PascalCase for named placeholders.

See more on https://sonarcloud.io/project/issues?id=mta-slipe_Slipe-Server&issues=AZ1ddKu-S1usNdOdrg_4&open=AZ1ddKu-S1usNdOdrg_4&pullRequest=565
element.Destroy();
};

Expand Down Expand Up @@ -1033,10 +1035,10 @@
}
catch (Exception ex)
{
this.logger.LogError(ex, "Failed to start Slipe Lua test resource for {playerName}, {ex}", args.Player.Name, ex.ToString());

Check warning on line 1038 in SlipeServer.Console/Logic/ServerTestLogic.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use PascalCase for named placeholders.

See more on https://sonarcloud.io/project/issues?id=mta-slipe_Slipe-Server&issues=AZ1ddKu-S1usNdOdrg_5&open=AZ1ddKu-S1usNdOdrg_5&pullRequest=565
}
stopwatch.Stop();
this.logger.LogInformation("Starting Slipe Lua test resource for {playerName} took {milliseconds}ms", args.Player.Name, stopwatch.ElapsedMilliseconds);

Check warning on line 1041 in SlipeServer.Console/Logic/ServerTestLogic.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use PascalCase for named placeholders.

See more on https://sonarcloud.io/project/issues?id=mta-slipe_Slipe-Server&issues=AZ1ddKu-S1usNdOdrg_6&open=AZ1ddKu-S1usNdOdrg_6&pullRequest=565
};

this.commandService.AddCommand("blowup").Triggered += (source, args) =>
Expand Down Expand Up @@ -1536,7 +1538,7 @@

player.Kicked += (player, args) =>
{
this.logger.LogWarning("{playerName} has been kicked, reason: {reason}", player.Name, args.Reason);

Check warning on line 1541 in SlipeServer.Console/Logic/ServerTestLogic.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use PascalCase for named placeholders.

See more on https://sonarcloud.io/project/issues?id=mta-slipe_Slipe-Server&issues=AZ1ddKu-S1usNdOdrg_q&open=AZ1ddKu-S1usNdOdrg_q&pullRequest=565
};

player.ScreenshotTaken += HandlePlayerScreenshot;
Expand All @@ -1561,20 +1563,20 @@

player.AcInfoReceived += (o, args) =>
{
this.logger.LogInformation("ACInfo for {playerName} detectedACList:{acList} d3d9Size: {D3D9Size} d3d9SHA256: {D3D9SHA256}", player.Name, string.Join(",", args.DetectedACList), args.D3D9Size, args.D3D9SHA256);

Check warning on line 1566 in SlipeServer.Console/Logic/ServerTestLogic.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use PascalCase for named placeholders.

See more on https://sonarcloud.io/project/issues?id=mta-slipe_Slipe-Server&issues=AZ1ddKu-S1usNdOdrg_r&open=AZ1ddKu-S1usNdOdrg_r&pullRequest=565
};

player.DiagnosticInfoReceived += (o, args) =>
{
this.logger.LogInformation("DIAGNOSTIC: {playerName} #{level} {message}", player.Name, args.Level, args.Message);

Check warning on line 1571 in SlipeServer.Console/Logic/ServerTestLogic.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use PascalCase for named placeholders.

See more on https://sonarcloud.io/project/issues?id=mta-slipe_Slipe-Server&issues=AZ1ddKu-S1usNdOdrg_s&open=AZ1ddKu-S1usNdOdrg_s&pullRequest=565
};

player.ModInfoReceived += (o, args) =>
{
this.logger.LogInformation("Player: {playerName} ModInfo:", player.Name);

Check warning on line 1576 in SlipeServer.Console/Logic/ServerTestLogic.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use PascalCase for named placeholders.

See more on https://sonarcloud.io/project/issues?id=mta-slipe_Slipe-Server&issues=AZ1ddKu-S1usNdOdrg_t&open=AZ1ddKu-S1usNdOdrg_t&pullRequest=565
foreach (var item in args.ModInfoItems)
{
this.logger.LogInformation("\t{name} - md5: {md5}", item.Name, item.LongMd5);

Check warning on line 1579 in SlipeServer.Console/Logic/ServerTestLogic.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use PascalCase for named placeholders.

See more on https://sonarcloud.io/project/issues?id=mta-slipe_Slipe-Server&issues=AZ1ddKu-S1usNdOdrg_u&open=AZ1ddKu-S1usNdOdrg_u&pullRequest=565
}
};

Expand All @@ -1583,17 +1585,17 @@
switch (args.PlayerNetworkStatus)
{
case Packets.Enums.PlayerNetworkStatusType.InterruptionBegan:
this.logger.LogInformation("(packets from {name}) interruption began {ticks} ticks ago", o.Name, args.Ticks);

Check warning on line 1588 in SlipeServer.Console/Logic/ServerTestLogic.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use PascalCase for named placeholders.

See more on https://sonarcloud.io/project/issues?id=mta-slipe_Slipe-Server&issues=AZ1ddKu-S1usNdOdrg_v&open=AZ1ddKu-S1usNdOdrg_v&pullRequest=565
break;
case Packets.Enums.PlayerNetworkStatusType.InterruptionEnd:
this.logger.LogInformation("(packets from {name}) interruption began {ticks} ticks ago and has just ended", o.Name, args.Ticks);

Check warning on line 1591 in SlipeServer.Console/Logic/ServerTestLogic.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use PascalCase for named placeholders.

See more on https://sonarcloud.io/project/issues?id=mta-slipe_Slipe-Server&issues=AZ1ddKu-S1usNdOdrg_w&open=AZ1ddKu-S1usNdOdrg_w&pullRequest=565
break;
}
};

player.TeamChanged += (thePlayer, args) =>
{
this.logger.LogDebug("{playerName} Joined {teamName} team!", thePlayer.Name, thePlayer.Team?.TeamName);

Check warning on line 1598 in SlipeServer.Console/Logic/ServerTestLogic.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use PascalCase for named placeholders.

See more on https://sonarcloud.io/project/issues?id=mta-slipe_Slipe-Server&issues=AZ1ddKu-S1usNdOdrg_x&open=AZ1ddKu-S1usNdOdrg_x&pullRequest=565
};

player.TargetChanged += (thePlayer, args) =>
Expand All @@ -1601,7 +1603,7 @@
if (args.NewValue != null && args.NewValue is Vehicle vehicle)
{
if (vehicle.Model == (ushort)VehicleModel.Rhino)
this.logger.LogDebug("{playerName} Changed target rhino", thePlayer.Name);

Check warning on line 1606 in SlipeServer.Console/Logic/ServerTestLogic.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use PascalCase for named placeholders.

See more on https://sonarcloud.io/project/issues?id=mta-slipe_Slipe-Server&issues=AZ1ddKu-S1usNdOdrg_y&open=AZ1ddKu-S1usNdOdrg_y&pullRequest=565
}
};

Expand All @@ -1616,9 +1618,9 @@
{
player.HasJetpack = !player.HasJetpack;
if (player.HasJetpack)
this.logger.LogInformation("{name} put on a jetpack!", sender.Name);

Check warning on line 1621 in SlipeServer.Console/Logic/ServerTestLogic.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use PascalCase for named placeholders.

See more on https://sonarcloud.io/project/issues?id=mta-slipe_Slipe-Server&issues=AZ1ddKu-S1usNdOdrg_z&open=AZ1ddKu-S1usNdOdrg_z&pullRequest=565
else
this.logger.LogInformation("{name} pulled off his jetpack!", sender.Name);

Check warning on line 1623 in SlipeServer.Console/Logic/ServerTestLogic.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use PascalCase for named placeholders.

See more on https://sonarcloud.io/project/issues?id=mta-slipe_Slipe-Server&issues=AZ1ddKu-S1usNdOdrg_0&open=AZ1ddKu-S1usNdOdrg_0&pullRequest=565
} else if (e.Key == "h")
{
jetpackBindEnabled = !jetpackBindEnabled;
Expand Down Expand Up @@ -1691,7 +1693,7 @@
} else
{
Player? player = (Player?)o;
this.logger.LogWarning("Failed to take a screenshot ({tag}) of player: {playerName}, reason: {errorMessage}", e.Tag, player?.Name, e.ErrorMessage);

Check warning on line 1696 in SlipeServer.Console/Logic/ServerTestLogic.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use PascalCase for named placeholders.

See more on https://sonarcloud.io/project/issues?id=mta-slipe_Slipe-Server&issues=AZ1ddKu-S1usNdOdrg_o&open=AZ1ddKu-S1usNdOdrg_o&pullRequest=565
}
}

Expand All @@ -1700,7 +1702,7 @@
var sampleValue = new SampleLuaValue();
sampleValue.Parse(luaEvent.Parameters.First());

this.logger.LogInformation("{event}", JsonConvert.SerializeObject(sampleValue));

Check warning on line 1705 in SlipeServer.Console/Logic/ServerTestLogic.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use PascalCase for named placeholders.

See more on https://sonarcloud.io/project/issues?id=mta-slipe_Slipe-Server&issues=AZ1ddKu-S1usNdOdrg_p&open=AZ1ddKu-S1usNdOdrg_p&pullRequest=565
}

private void TriggerTestEvent(Player player)
Expand Down
25 changes: 24 additions & 1 deletion SlipeServer.Console/Resources/TestResource/test.lua
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,27 @@ addEventHandler("onClientRender", root, function()
k = k + 1
printDebugVehicle(k, v);
end
end)
end)

local clientTasks = {}
addEvent("testClientTask", true)
addEventHandler("testClientTask", root, function(clientTask)
outputChatBox("ClientTask created.");
clientTasks[#clientTasks + 1] = clientTask;
end)

addCommandHandler("resolveTasks", function()
for i,task in ipairs(clientTasks)do
ClientTask.Resolve(task, "Ok");
end
outputChatBox("Resolved: "..#clientTasks.." tasks.")
clientTasks = {}
end)

addCommandHandler("rejectTasks", function()
for i,task in ipairs(clientTasks)do
ClientTask.Reject(task, "Ok");
end
outputChatBox("Failed: "..#clientTasks.." tasks.")
clientTasks = {}
end)
54 changes: 53 additions & 1 deletion SlipeServer.Example/ServerExampleLogic.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using SlipeServer.Server;
using SlipeServer.Server.Concepts;
using SlipeServer.Server.Elements;
using SlipeServer.Server.Exceptions;
using SlipeServer.Server.Services;

namespace SlipeServer.Example;
Expand All @@ -8,12 +10,13 @@
{
private readonly CommandService commandService;
private readonly ChatBox chatBox;
private readonly MtaServer mtaServer;

public ServerExampleLogic(CommandService commandService, ChatBox chatBox, MtaServer mtaServer)
{
this.commandService = commandService;
this.chatBox = chatBox;

this.mtaServer = mtaServer;
AddCommand("hello", player =>
{
this.chatBox.OutputTo(player, "Hello world");
Expand All @@ -34,7 +37,7 @@
AddVehiclesCommands();
}

private void AddVehiclesCommands()

Check failure on line 40 in SlipeServer.Example/ServerExampleLogic.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this method to reduce its Cognitive Complexity from 23 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=mta-slipe_Slipe-Server&issues=AZ1ddKyVS1usNdOdrg_8&open=AZ1ddKyVS1usNdOdrg_8&pullRequest=565
{
AddCommand("myvehprintdamage", player =>
{
Expand Down Expand Up @@ -83,6 +86,40 @@
vehicle.Fix();
this.chatBox.OutputTo(player, "Vehicle fixed");
});

AddCommand("clienttask", async player =>
{
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));

Check failure on line 92 in SlipeServer.Example/ServerExampleLogic.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Dispose 'cts' when it is no longer needed.

See more on https://sonarcloud.io/project/issues?id=mta-slipe_Slipe-Server&issues=AZ1ddKyVS1usNdOdrg_7&open=AZ1ddKyVS1usNdOdrg_7&pullRequest=565
var task = this.mtaServer.CreateClientTask(player, cts.Token);

player.TriggerLuaEvent("testClientTask", player, task);

try
{
await task;
}
catch (PlayerDisconnectedException e) // When player left the server
{
Console.WriteLine("Result: PlayerDisconnectedException");
}
catch (InvalidOperationException e) // When client sent invalid response
{
Console.WriteLine("Result: InvalidOperationException");
}
catch (ClientErrorException e) // When client on purpose rejected task
{
Console.WriteLine("Result: ClientErrorException");
}
catch (OperationCanceledException e) // Exceptin from cts from above
{
Console.WriteLine("Result: OperationCanceledException");
}
finally
{
this.chatBox.OutputTo(player, "Task completed");
}

});
}

private void AddCommand(string command, Action<Player> callback)
Expand All @@ -92,4 +129,19 @@
callback(e.Player);
};
}

private void AddCommand(string command, Func<Player, Task> callback)
{
this.commandService.AddCommand(command).Triggered += async (object? sender, Server.Events.CommandTriggeredEventArgs e) =>
{
try
{
await callback(e.Player);
}
catch(Exception ex)
{
Console.WriteLine(ex.ToString());
}
};
}
}
131 changes: 131 additions & 0 deletions SlipeServer.Server/Concepts/ClientTask.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
using SlipeServer.Packets.Definitions.Lua;
using SlipeServer.Server.Elements;
using SlipeServer.Server.Exceptions;
using SlipeServer.Server.Resources;
using System;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;

namespace SlipeServer.Server.Concepts;

public static class ResourceExtensions
{
public static Resource AddClientTaskHelper(this Resource resource)
{
resource.NoClientScripts["clientTaskHelper.lua"] = System.Text.UTF8Encoding.UTF8.GetBytes(ClientTask.luaHelperCode);

return resource;
}
}

public sealed class ClientErrorException : Exception
{
public ClientErrorException(string? message) : base(message)
{

}
}

public sealed class ClientTask : LuaValue, IDisposable
{
public const string luaHelperCode = """
ClientTask = {
Resolve = function(clientTask, ...)
if(clientTask._completed)then
error("ClientTask already completed");
end
clientTask._completed = true;
triggerServerEvent("clientTask_"..clientTask._id, localPlayer, "success", ...)
end,
Reject = function(clientTask, ...)
if(clientTask._completed)then
error("ClientTask already completed");
end
clientTask._completed = true;
triggerServerEvent("clientTask_"..clientTask._id, localPlayer, "error", ...)
end,
}
""";

private readonly TaskCompletionSource taskCompletionSource;
private readonly string eventName;
public MtaServer MtaServer { get; }
public Player Player { get; }
public string Id { get; }

internal ClientTask(MtaServer mtaServer, Player player, string id, CancellationToken cancellationToken) : base(new LuaTable
{
["_id"] = id
})
{
this.taskCompletionSource = new TaskCompletionSource();
this.MtaServer = mtaServer;
this.Player = player;
this.Id = id;
this.eventName = $"clientTask_{this.Id}";
this.MtaServer.LuaEventTriggered += HandleLuaEventTriggered;
this.Player.Disconnected += HandleDisconnected;
cancellationToken.Register(() =>
{
this.taskCompletionSource.TrySetCanceled();
});
}

private void HandleDisconnected(Player sender, Elements.Events.PlayerQuitEventArgs e)
{
this.taskCompletionSource.TrySetException(new PlayerDisconnectedException(sender));
Dispose();
}

private void HandleLuaEventTriggered(Events.LuaEvent luaEvent)
{
if (luaEvent.Name != this.eventName || luaEvent.Player != this.Player)
return;

try
{
var result = luaEvent.Parameters[0].StringValue;

if (result == "success")
{
this.taskCompletionSource.TrySetResult();
}
else if (result == "error")
{
if(luaEvent.Parameters.Length >= 2)
{
this.taskCompletionSource.TrySetException(new ClientErrorException(luaEvent.Parameters[1].StringValue));
} else
{
this.taskCompletionSource.TrySetException(new ClientErrorException(null));
}
}
else
{
this.taskCompletionSource.TrySetException(new InvalidOperationException());
}
}
catch (Exception ex)
{
this.taskCompletionSource.TrySetException(ex);
}
finally
{
Dispose();
}
}

public void Dispose()
{
this.MtaServer.LuaEventTriggered -= HandleLuaEventTriggered;
this.Player.Disconnected -= HandleDisconnected;
this.taskCompletionSource.TrySetException(new ObjectDisposedException(nameof(ClientTask)));
}

public TaskAwaiter GetAwaiter()
{
return this.taskCompletionSource.Task.GetAwaiter();
}
}
14 changes: 14 additions & 0 deletions SlipeServer.Server/Exceptions/PlayerDisconnectedException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using SlipeServer.Server.Elements;
using System;

namespace SlipeServer.Server.Exceptions;

public sealed class PlayerDisconnectedException : Exception
{
public Player Player { get; }

public PlayerDisconnectedException(Player player)
{
this.Player = player;
}
}
13 changes: 13 additions & 0 deletions SlipeServer.Server/MtaServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using SlipeServer.Server.AllSeeingEye;
using SlipeServer.Server.Bans;
using SlipeServer.Server.Clients;
using SlipeServer.Server.Concepts;
using SlipeServer.Server.ElementCollections;
using SlipeServer.Server.Elements;
using SlipeServer.Server.Elements.IdGeneration;
Expand All @@ -32,6 +33,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;

namespace SlipeServer.Server;

Expand Down Expand Up @@ -678,6 +680,17 @@ public static MtaServer Create(IServiceProvider serviceProvider, Action<ServerBu
public static MtaServer<TPlayer> CreateWithDiSupport<TPlayer>(Action<ServerBuilder> builderAction) where TPlayer : Player
=> new MtaDiPlayerServer<TPlayer>(builderAction);

/// <summary>
/// Creates
/// </summary>
/// <param name="player"></param>
/// <returns></returns>
public ClientTask CreateClientTask(Player player, CancellationToken cancellationToken = default)
{
var id = Guid.NewGuid().ToString();
return new ClientTask(this, player, id, cancellationToken);
}

/// <summary>
/// Triggered when any element is created on the server through the .AssociateElement method
/// </summary>
Expand Down