Skip to content

Commit 47d0015

Browse files
committed
Add support for Counter-Strike 2
Closes #1
1 parent 8095a56 commit 47d0015

File tree

7 files changed

+385
-13
lines changed

7 files changed

+385
-13
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,7 @@
1111
.DS_Store
1212

1313
# .NET
14+
bin/
1415
libs/
1516
obj/
17+
*.user

AzLink-CSharp.sln

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AzLink-Oxide", "AzLink-Oxide\AzLink-Oxide.csproj", "{91893BB6-6417-4A83-8274-CF15CA0E28EA}"
4+
EndProject
5+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AzLink-CounterStrikeSharp", "AzLink-CounterStrikeSharp\AzLink-CounterStrikeSharp.csproj", "{F4EA059C-CAA2-4D99-A2F8-E5C773E2E21A}"
6+
EndProject
7+
Global
8+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
9+
Debug|Any CPU = Debug|Any CPU
10+
Release|Any CPU = Release|Any CPU
11+
EndGlobalSection
12+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
13+
{91893BB6-6417-4A83-8274-CF15CA0E28EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
14+
{91893BB6-6417-4A83-8274-CF15CA0E28EA}.Debug|Any CPU.Build.0 = Debug|Any CPU
15+
{91893BB6-6417-4A83-8274-CF15CA0E28EA}.Release|Any CPU.ActiveCfg = Release|Any CPU
16+
{91893BB6-6417-4A83-8274-CF15CA0E28EA}.Release|Any CPU.Build.0 = Release|Any CPU
17+
{F4EA059C-CAA2-4D99-A2F8-E5C773E2E21A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
18+
{F4EA059C-CAA2-4D99-A2F8-E5C773E2E21A}.Debug|Any CPU.Build.0 = Debug|Any CPU
19+
{F4EA059C-CAA2-4D99-A2F8-E5C773E2E21A}.Release|Any CPU.ActiveCfg = Release|Any CPU
20+
{F4EA059C-CAA2-4D99-A2F8-E5C773E2E21A}.Release|Any CPU.Build.0 = Release|Any CPU
21+
EndGlobalSection
22+
EndGlobal
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
<RootNamespace>AzLink.CounterStrikeSharp</RootNamespace>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.239" />
12+
</ItemGroup>
13+
14+
</Project>
Lines changed: 306 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,306 @@
1+
using System.Net.Http.Json;
2+
using System.Text.Json;
3+
using System.Text.Json.Serialization;
4+
using CounterStrikeSharp.API;
5+
using CounterStrikeSharp.API.Core;
6+
using CounterStrikeSharp.API.Core.Attributes.Registration;
7+
using CounterStrikeSharp.API.Modules.Commands;
8+
using CounterStrikeSharp.API.Modules.Timers;
9+
using Microsoft.Extensions.Logging;
10+
11+
namespace AzLink.CounterStrikeSharp;
12+
13+
public class AzLink : BasePlugin, IPluginConfig<AzLinkConfig>
14+
{
15+
private const string AzLinkVersion = "1.0.0";
16+
17+
private HttpClient client = new();
18+
19+
private DateTime lastFullSent = DateTime.Now;
20+
private DateTime lastSent = DateTime.Now;
21+
22+
public override string ModuleName => "AzLink";
23+
public override string ModuleAuthor => "Azuriom";
24+
public override string ModuleVersion => AzLinkVersion;
25+
public override string ModuleDescription => "Link your Azuriom website with an Counter-Strike 2 server.";
26+
27+
public AzLinkConfig Config { get; set; } = new();
28+
29+
public void OnConfigParsed(AzLinkConfig config)
30+
{
31+
Config = config;
32+
33+
InitHttpClient();
34+
35+
if (config.SiteKey == null || config.Url == null)
36+
{
37+
Logger.LogWarning("AzLink is not configured yet.");
38+
}
39+
}
40+
41+
public override void Load(bool hotReload)
42+
{
43+
AddTimer(60, TryFetch, TimerFlags.REPEAT);
44+
}
45+
46+
[ConsoleCommand("azlink_setup", "Setup AzLink")]
47+
[CommandHelper(whoCanExecute: CommandUsage.SERVER_ONLY)]
48+
public void OnSetupCommand(CCSPlayerController? player, CommandInfo commandInfo)
49+
{
50+
if (commandInfo.ArgCount < 3)
51+
{
52+
commandInfo.ReplyToCommand(
53+
"You must first add this server in your Azuriom admin dashboard, in the 'Servers' section.");
54+
return;
55+
}
56+
57+
Config.Url = commandInfo.GetArg(1);
58+
Config.SiteKey = commandInfo.GetArg(2);
59+
60+
InitHttpClient();
61+
62+
PingWebsite(() =>
63+
{
64+
commandInfo.ReplyToCommand("Linked to the website successfully.");
65+
SaveConfig();
66+
}, code =>
67+
{
68+
commandInfo.ReplyToCommand($"An error occurred, code {code}");
69+
Config.Url = null;
70+
});
71+
}
72+
73+
[ConsoleCommand("azlink_status", "Check the status of AzLink")]
74+
[CommandHelper(whoCanExecute: CommandUsage.SERVER_ONLY)]
75+
public void OnStatusCommand(CCSPlayerController? player, CommandInfo commandInfo)
76+
{
77+
if (Config.Url == null)
78+
{
79+
commandInfo.ReplyToCommand("AzLink is not configured yet, use the 'setup' subcommand first.");
80+
return;
81+
}
82+
83+
PingWebsite(() => commandInfo.ReplyToCommand("Connected to the website successfully."),
84+
code => commandInfo.ReplyToCommand($"An error occurred, code {code}"));
85+
}
86+
87+
[ConsoleCommand("azlink_fetch", "Fetch data from the website")]
88+
[CommandHelper(whoCanExecute: CommandUsage.SERVER_ONLY)]
89+
public void OnFetchCommand(CCSPlayerController? player, CommandInfo commandInfo)
90+
{
91+
if (Config.Url == null)
92+
{
93+
commandInfo.ReplyToCommand("AzLink is not configured yet, use the 'setup' subcommand first.");
94+
return;
95+
}
96+
97+
RunFetch(res =>
98+
{
99+
DispatchCommands(res.Commands);
100+
101+
commandInfo.ReplyToCommand("Data has been fetched successfully.");
102+
}, code => commandInfo.ReplyToCommand($"An error occurred, code {code}"), true);
103+
}
104+
105+
private void TryFetch()
106+
{
107+
var now = DateTime.Now;
108+
109+
if (Config.Url == null || Config.SiteKey == null)
110+
{
111+
return;
112+
}
113+
114+
if ((now - lastSent).TotalSeconds < 15)
115+
{
116+
return;
117+
}
118+
119+
lastSent = now;
120+
121+
var full = now.Minute % 15 == 0 && (now - lastFullSent).TotalSeconds >= 60;
122+
123+
if (full)
124+
{
125+
lastFullSent = now;
126+
}
127+
128+
RunFetch(res => DispatchCommands(res.Commands),
129+
code => Logger.LogError("Unable to send data to the website (code {code})", code), full);
130+
}
131+
132+
private void RunFetch(Action<FetchResponse> callback, Action<int> errorHandler, bool sendFullData)
133+
{
134+
//Server.NextFrameAsync(() =>
135+
//{
136+
var data = GetServerData(sendFullData);
137+
138+
FetchAsync(callback, errorHandler, data);
139+
//});
140+
}
141+
142+
private async void FetchAsync<T>(Action<FetchResponse> callback, Action<int> errorHandler, T data)
143+
{
144+
try
145+
{
146+
var res = await client.PostAsJsonAsync("/api/azlink", data);
147+
148+
if (!res.IsSuccessStatusCode)
149+
{
150+
await Server.NextFrameAsync(() => errorHandler((int)res.StatusCode));
151+
return;
152+
}
153+
154+
var fetchRes = await res.Content.ReadFromJsonAsync<FetchResponse>();
155+
156+
if (fetchRes == null)
157+
{
158+
throw new ApplicationException("Unable to parse the response from the website.");
159+
}
160+
161+
await Server.NextFrameAsync(() => callback(fetchRes));
162+
}
163+
catch (Exception e)
164+
{
165+
Logger.LogError("An error occurred while fetching data from the website: {error}", e.Message);
166+
167+
Console.WriteLine(e);
168+
}
169+
}
170+
171+
private async void PingWebsite(Action onSuccess, Action<int> errorHandler)
172+
{
173+
if (Config.Url == null || Config.SiteKey == null)
174+
{
175+
throw new ApplicationException("AzLink is not configured yet.");
176+
}
177+
178+
try
179+
{
180+
var res = await client.GetAsync("/api/azlink");
181+
182+
if (!res.IsSuccessStatusCode)
183+
{
184+
await Server.NextFrameAsync(() => errorHandler((int)res.StatusCode));
185+
186+
return;
187+
}
188+
189+
await Server.NextFrameAsync(onSuccess);
190+
}
191+
catch (Exception e)
192+
{
193+
Logger.LogError("An error occurred while pinging the website: {error}", e.Message);
194+
195+
Console.WriteLine(e);
196+
}
197+
}
198+
199+
private void DispatchCommands(ICollection<PendingCommand> commands)
200+
{
201+
if (commands.Count == 0)
202+
{
203+
return;
204+
}
205+
206+
foreach (var info in commands)
207+
{
208+
var player = Utilities.GetPlayerFromSteamId(ulong.Parse(info.UserId));
209+
var name = player?.PlayerName ?? info.UserName;
210+
var id = player?.UserId?.ToString() ?? info.UserId;
211+
212+
foreach (var command in info.Values)
213+
{
214+
var cmd = command.Replace("{player}", name)
215+
.Replace("{id}", id)
216+
.Replace("{steam_id}", info.UserId);
217+
218+
Logger.LogInformation("Dispatching command to {Name} ({User}): {Command}", name, info.UserId, cmd);
219+
220+
Server.ExecuteCommand(cmd);
221+
}
222+
}
223+
224+
Logger.LogInformation("Dispatched commands to {Count} players.", commands.Count);
225+
}
226+
227+
private Dictionary<string, object> GetServerData(bool includeFullData)
228+
{
229+
var online = Utilities.GetPlayers().Select(player => new Dictionary<string, string>
230+
{
231+
{ "name", player.PlayerName }, { "uid", player.SteamID.ToString() }
232+
});
233+
var data = new Dictionary<string, object>
234+
{
235+
{
236+
"platform", new Dictionary<string, string>
237+
{
238+
{ "type", "COUNTER_STRIKE_SHARP" },
239+
{ "name", "CounterStrikeSharp" },
240+
{ "version", Api.GetVersionString() },
241+
{ "key", "uid" }
242+
}
243+
},
244+
{ "version", ModuleVersion },
245+
{ "players", online.ToArray() },
246+
{ "maxPlayers", Server.MaxPlayers },
247+
{ "full", includeFullData }
248+
};
249+
250+
if (includeFullData)
251+
{
252+
data.Add("ram", GC.GetTotalMemory(false) / 1024 / 1024);
253+
}
254+
255+
return data;
256+
}
257+
258+
private void InitHttpClient()
259+
{
260+
if (Config.Url == null || Config.SiteKey == null)
261+
{
262+
return;
263+
}
264+
265+
client = new();
266+
client.BaseAddress = new Uri(Config.Url);
267+
client.DefaultRequestHeaders.Clear();
268+
client.DefaultRequestHeaders.Add("Azuriom-Link-Token", Config.SiteKey);
269+
client.DefaultRequestHeaders.Add("Accept", "application/json");
270+
client.DefaultRequestHeaders.Add("User-Agent", $"AzLink CounterStrikeSource v{AzLinkVersion}");
271+
}
272+
273+
private void SaveConfig()
274+
{
275+
var baseName = Path.GetFileName(ModuleDirectory);
276+
var configsPath = Path.Combine(ModuleDirectory, "..", "..", "configs", "plugins");
277+
var jsonConfigPath = Path.Combine(configsPath, baseName, $"{baseName}.json");
278+
var json = JsonSerializer.Serialize(Config, new JsonSerializerOptions
279+
{
280+
WriteIndented = true
281+
});
282+
283+
File.WriteAllText(jsonConfigPath, json);
284+
}
285+
}
286+
287+
public class AzLinkConfig : BasePluginConfig
288+
{
289+
public string? Url { get; set; }
290+
291+
public string? SiteKey { get; set; }
292+
}
293+
294+
internal class FetchResponse
295+
{
296+
[JsonPropertyName("commands")] public List<PendingCommand> Commands { get; set; } = [];
297+
}
298+
299+
internal class PendingCommand
300+
{
301+
[JsonPropertyName("uid")] public string UserId { get; set; } = "";
302+
303+
[JsonPropertyName("name")] public string UserName { get; set; } = "";
304+
305+
[JsonPropertyName("values")] public List<string> Values { get; set; } = [];
306+
}

AzLink-Oxide/AzLink-Oxide.csproj

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net7.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
<RootNamespace>Oxide.Plugins</RootNamespace>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<Reference Include="Oxide.Common">
12+
<HintPath>libs\Oxide.Common.dll</HintPath>
13+
</Reference>
14+
<Reference Include="Oxide.Core">
15+
<HintPath>libs\Oxide.Core.dll</HintPath>
16+
</Reference>
17+
<Reference Include="Oxide.CSharp">
18+
<HintPath>libs\Oxide.CSharp.dll</HintPath>
19+
</Reference>
20+
<Reference Include="Oxide.References">
21+
<HintPath>libs\Oxide.References.dll</HintPath>
22+
</Reference>
23+
</ItemGroup>
24+
25+
</Project>

0 commit comments

Comments
 (0)