diff --git a/.env.example b/.env.example
index bd0f1e07..7c6bdaa0 100644
--- a/.env.example
+++ b/.env.example
@@ -7,6 +7,10 @@ ServerInfo__IntroAudioUrl=https://od.lk/s/Nl8yMDg4MTc0NDBf/intro-cs.mp3
# Expressed in seconds.
ServerInfo__FlagAutoReturnTime=120
+ServerOwner__Name=MrDave
+# Specify the secret key to give me admin.
+ServerOwner__SecretKey=
+
# Expressed in minutes.
CommandCooldowns__Health=3
CommandCooldowns__Armour=3
diff --git a/src/Application/Common/Resources/Messages.Designer.cs b/src/Application/Common/Resources/Messages.Designer.cs
index 8910792f..e2aa4b17 100644
--- a/src/Application/Common/Resources/Messages.Designer.cs
+++ b/src/Application/Common/Resources/Messages.Designer.cs
@@ -627,6 +627,15 @@ internal static string OnFlagTaken {
}
}
+ ///
+ /// Looks up a localized string similar to Owner's name or secret key are not set.
+ ///
+ internal static string OwnerNameOrSecretKeyAreNotSet {
+ get {
+ return ResourceManager.GetString("OwnerNameOrSecretKeyAreNotSet", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Password cannot be empty.
///
@@ -717,6 +726,15 @@ internal static string PlayerIsInClassSelection {
}
}
+ ///
+ /// Looks up a localized string similar to You are not the owner of this server.
+ ///
+ internal static string PlayerIsNotServerOwner {
+ get {
+ return ResourceManager.GetString("PlayerIsNotServerOwner", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to That player name already exists.
///
@@ -1085,5 +1103,14 @@ internal static string WrongPassword {
return ResourceManager.GetString("WrongPassword", resourceCulture);
}
}
+
+ ///
+ /// Looks up a localized string similar to The secret key you entered is incorrect. Please try again.
+ ///
+ internal static string WrongSecretKey {
+ get {
+ return ResourceManager.GetString("WrongSecretKey", resourceCulture);
+ }
+ }
}
}
diff --git a/src/Application/Common/Resources/Messages.resx b/src/Application/Common/Resources/Messages.resx
index 724f161a..61043f25 100644
--- a/src/Application/Common/Resources/Messages.resx
+++ b/src/Application/Common/Resources/Messages.resx
@@ -306,6 +306,9 @@
{PlayerName} has taken the {TeamName} team's {ColorName} flag! Keep an eye on the score!
+
+ Owner's name or secret key are not set
+
Password cannot be empty
@@ -336,6 +339,9 @@
The player is in the class selection
+
+ You are not the owner of this server
+
That player name already exists
@@ -459,4 +465,7 @@
The password you entered is incorrect. Please try again
+
+ The secret key you entered is incorrect. Please try again
+
\ No newline at end of file
diff --git a/src/Application/Players/Accounts/ServerOwnerSettings.cs b/src/Application/Players/Accounts/ServerOwnerSettings.cs
new file mode 100644
index 00000000..b4c22154
--- /dev/null
+++ b/src/Application/Players/Accounts/ServerOwnerSettings.cs
@@ -0,0 +1,7 @@
+namespace CTF.Application.Players.Accounts;
+
+public class ServerOwnerSettings
+{
+ public string Name { get; init; } = string.Empty;
+ public string SecretKey { get; init; } = string.Empty;
+}
diff --git a/src/Application/Players/Accounts/Systems/ChangeRoleSystem.cs b/src/Application/Players/Accounts/Systems/ChangeRoleSystem.cs
index 4ec76a66..41553b0b 100644
--- a/src/Application/Players/Accounts/Systems/ChangeRoleSystem.cs
+++ b/src/Application/Players/Accounts/Systems/ChangeRoleSystem.cs
@@ -1,6 +1,9 @@
namespace CTF.Application.Players.Accounts.Systems;
-public class ChangeRoleSystem(IPlayerRepository playerRepository) : ISystem
+public class ChangeRoleSystem(
+ IPlayerRepository playerRepository,
+ IDialogService dialogService,
+ ServerOwnerSettings serverOwnerSettings) : ISystem
{
[PlayerCommand("setrole")]
public void SetRole(
@@ -53,4 +56,65 @@ public void SetRole(
targetPlayer.GameText(gameText, 4000, 3);
currentPlayer.SendClientMessage(Color.Yellow, message);
}
+
+ [PlayerCommand("givemeadmin")]
+ public async void GiveMeAdmin(Player currentPlayer)
+ {
+ if (string.IsNullOrWhiteSpace(serverOwnerSettings.Name) ||
+ string.IsNullOrWhiteSpace(serverOwnerSettings.SecretKey))
+ {
+ currentPlayer.SendClientMessage(Color.Red, Messages.OwnerNameOrSecretKeyAreNotSet);
+ return;
+ }
+
+ var ownerName = serverOwnerSettings.Name.Trim();
+ bool isNotEquals = !currentPlayer.Name.Equals(ownerName, StringComparison.OrdinalIgnoreCase);
+ if (isNotEquals)
+ {
+ currentPlayer.SendClientMessage(Color.Red, Messages.PlayerIsNotServerOwner);
+ return;
+ }
+
+ var dialog = new InputDialog()
+ {
+ Caption = "Secret key",
+ Content = "Enter secret key",
+ Button1 = "Accept",
+ Button2 = "Close"
+ };
+
+ InputDialogResponse response = await dialogService.ShowAsync(currentPlayer, dialog);
+ if (response.IsRightButtonOrDisconnected())
+ return;
+
+ var enteredSecretKey = response.InputText;
+ bool isWrongSecretKey = enteredSecretKey != serverOwnerSettings.SecretKey;
+ if (isWrongSecretKey)
+ {
+ const int MaxFailedAttempts = 3;
+ var failedAttemptCount = currentPlayer.GetComponent();
+ failedAttemptCount ??= currentPlayer.AddComponent();
+ failedAttemptCount.Value++;
+ if (failedAttemptCount.Value == MaxFailedAttempts)
+ {
+ currentPlayer.Kick();
+ return;
+ }
+ currentPlayer.SendClientMessage(Color.Red, Messages.WrongSecretKey);
+ GiveMeAdmin(currentPlayer);
+ return;
+ }
+
+ var gameText = Smart.Format(Messages.PromotedToRole, new { RoleName = RoleId.Admin });
+ PlayerInfo playerInfo = currentPlayer.GetInfo();
+ playerInfo.SetRole(RoleId.Admin);
+ playerRepository.UpdateRole(playerInfo);
+ currentPlayer.GameText(gameText, 4000, 3);
+ currentPlayer.GetComponent()?.Destroy();
+ }
+
+ private class FailedAttemptCountComponent : Component
+ {
+ public int Value { get; set; } = 0;
+ }
}
diff --git a/src/Host/Extensions/AppSettingsExtensions.cs b/src/Host/Extensions/AppSettingsExtensions.cs
index cf6ad614..c17a395f 100644
--- a/src/Host/Extensions/AppSettingsExtensions.cs
+++ b/src/Host/Extensions/AppSettingsExtensions.cs
@@ -18,10 +18,15 @@ public static IServiceCollection AddSettings(
.GetRequiredSection("TopPlayers")
.Get();
+ var serverOwnerSettings = configuration
+ .GetRequiredSection("ServerOwner")
+ .Get();
+
services
.AddSingleton(serverSettings)
.AddSingleton(commandCooldowns)
- .AddSingleton(topPlayersSettings);
+ .AddSingleton(topPlayersSettings)
+ .AddSingleton(serverOwnerSettings);
return services;
}
diff --git a/src/Host/Usings.cs b/src/Host/Usings.cs
index 390c0152..3aad8be2 100644
--- a/src/Host/Usings.cs
+++ b/src/Host/Usings.cs
@@ -9,6 +9,7 @@
global using CTF.Application.Common.Settings;
global using CTF.Application.Common.Services;
global using CTF.Application.Players;
+global using CTF.Application.Players.Accounts;
global using CTF.Application.Players.TopPlayers.Models;
global using CTF.Host.Extensions;
global using CTF.Host.Services;