first commit

This commit is contained in:
2025-07-06 00:23:46 +02:00
commit 38f50c8819
1788 changed files with 112878 additions and 0 deletions

View File

@@ -0,0 +1,64 @@
using System;
using NitroxModel.DataStructures.Util;
namespace NitroxServer.ConsoleCommands.Abstract
{
public abstract partial class Command
{
public ref struct CallArgs
{
public Command Command { get; }
public Optional<Player> Sender { get; }
public Span<string> Args { get; }
public bool IsConsole => !Sender.HasValue;
public string SenderName => Sender.HasValue ? Sender.Value.Name : "SERVER";
public CallArgs(Command command, Optional<Player> sender, Span<string> args)
{
Command = command;
Sender = sender;
Args = args;
}
public bool IsValid(int index)
{
return index < Args.Length && index >= 0 && Args.Length != 0;
}
public string GetTillEnd(int startIndex = 0)
{
// TODO: Proper argument capture/parse instead of this argument join hack
if (Args.Length > 0)
{
return string.Join(" ", Args.Slice(startIndex).ToArray());
}
return string.Empty;
}
public string Get(int index)
{
return Get<string>(index);
}
public T Get<T>(int index)
{
IParameter<object> param = Command.Parameters[index];
string arg = IsValid(index) ? Args[index] : null;
if (arg == null)
{
return default(T);
}
if (typeof(T) == typeof(string))
{
return (T)(object)arg;
}
return (T)param.Read(arg);
}
}
}
}

View File

@@ -0,0 +1,149 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NitroxModel.Core;
using NitroxModel.DataStructures.GameLogic;
using NitroxModel.DataStructures.Util;
using NitroxModel.Helper;
using NitroxModel.Packets;
using NitroxServer.GameLogic;
namespace NitroxServer.ConsoleCommands.Abstract
{
public abstract partial class Command
{
private int optional, required;
public virtual IEnumerable<string> Aliases { get; }
public string Name { get; }
public string Description { get; }
public Perms RequiredPermLevel { get; }
public PermsFlag Flags { get; }
public bool AllowedArgOverflow { get; set; }
public List<IParameter<object>> Parameters { get; }
protected Command(string name, Perms perms, PermsFlag flag, string description) : this(name, perms, description)
{
Flags = flag;
}
protected Command(string name, Perms perms, string description)
{
Validate.NotNull(name);
Name = name;
Flags = PermsFlag.NONE;
RequiredPermLevel = perms;
AllowedArgOverflow = false;
Aliases = Array.Empty<string>();
Parameters = new List<IParameter<object>>();
Description = string.IsNullOrEmpty(description) ? "No description provided" : description;
}
protected abstract void Execute(CallArgs args);
public void TryExecute(Optional<Player> sender, Span<string> args)
{
if (args.Length < required)
{
SendMessage(sender, $"Error: Invalid Parameters\nUsage: {ToHelpText(false, true)}");
return;
}
if (!AllowedArgOverflow && args.Length > optional + required)
{
SendMessage(sender, $"Error: Too many Parameters\nUsage: {ToHelpText(false, true)}");
return;
}
try
{
Execute(new CallArgs(this, sender, args));
}
catch (ArgumentException ex)
{
SendMessage(sender, $"Error: {ex.Message}");
}
catch (Exception ex)
{
Log.Error(ex, "Fatal error while trying to execute the command");
}
}
public bool CanExecute(Perms treshold)
{
return RequiredPermLevel <= treshold;
}
public string ToHelpText(bool singleCommand, bool cropText = false)
{
StringBuilder cmd = new(Name);
if (Aliases.Any())
{
cmd.AppendFormat("/{0}", string.Join("/", Aliases));
}
cmd.AppendFormat(" {0}", string.Join(" ", Parameters));
if (singleCommand)
{
string parameterPreText = Parameters.Count == 0 ? "" : Environment.NewLine;
string parameterText = $"{parameterPreText}{string.Join("\n", Parameters.Select(p => $"{p,-47} - {p.GetDescription()}"))}";
return cropText ? $"{cmd}" : $"{cmd,-32} - {Description} {parameterText}";
}
return cropText ? $"{cmd}" : $"{cmd,-32} - {Description}";
}
protected void AddParameter<T>(T param) where T : IParameter<object>
{
Validate.NotNull(param as object);
Parameters.Add(param);
if (param.IsRequired)
{
required++;
}
else
{
optional++;
}
}
/// <summary>
/// Send a message to an existing player
/// </summary>
public static void SendMessageToPlayer(Optional<Player> player, string message)
{
if (player.HasValue)
{
player.Value.SendPacket(new ChatMessage(ChatMessage.SERVER_ID, message));
}
}
/// <summary>
/// Send a message to an existing player and logs it in the console
/// </summary>
public static void SendMessage(Optional<Player> player, string message)
{
SendMessageToPlayer(player, message);
if (!player.HasValue)
{
Log.Info(message);
}
}
/// <summary>
/// Send a message to all connected players
/// </summary>
public static void SendMessageToAllPlayers(string message)
{
PlayerManager playerManager = NitroxServiceLocator.LocateService<PlayerManager>();
playerManager.SendPacketToAllPlayers(new ChatMessage(ChatMessage.SERVER_ID, message));
Log.Info($"[BROADCAST] {message}");
}
}
}

View File

@@ -0,0 +1,43 @@
using NitroxModel.Helper;
namespace NitroxServer.ConsoleCommands.Abstract
{
public abstract class Parameter<T> : IParameter<T>
{
public bool IsRequired { get; }
public string Name { get; }
private string Description { get; }
protected Parameter(string name, bool isRequired, string description)
{
Validate.IsFalse(string.IsNullOrEmpty(name));
Name = name;
IsRequired = isRequired;
Description = description;
}
public abstract bool IsValid(string arg);
public abstract T Read(string arg);
public virtual string GetDescription()
{
return Description;
}
public override string ToString()
{
return $"{(IsRequired ? '{' : '[')}{Name}{(IsRequired ? '}' : ']')}";
}
}
public interface IParameter<out T>
{
bool IsRequired { get; }
string Name { get; }
bool IsValid(string arg);
T Read(string arg);
string GetDescription();
}
}

View File

@@ -0,0 +1,41 @@
using System;
using System.Linq;
using NitroxModel.Helper;
namespace NitroxServer.ConsoleCommands.Abstract.Type
{
public class TypeBoolean : Parameter<bool>, IParameter<object>
{
private static readonly string[] noValues = new string[]
{
bool.FalseString,
"no",
"off"
};
private static readonly string[] yesValues = new string[]
{
bool.TrueString,
"yes",
"on"
};
public TypeBoolean(string name, bool isRequired, string description) : base(name, isRequired, description) { }
public override bool IsValid(string arg)
{
return yesValues.Contains(arg, StringComparer.OrdinalIgnoreCase) || noValues.Contains(arg, StringComparer.OrdinalIgnoreCase);
}
public override bool Read(string arg)
{
Validate.IsTrue(IsValid(arg), "Invalid boolean value received");
return yesValues.Contains(arg, StringComparer.OrdinalIgnoreCase);
}
object IParameter<object>.Read(string arg)
{
return Read(arg);
}
}
}

View File

@@ -0,0 +1,29 @@
using System;
using NitroxModel.Helper;
namespace NitroxServer.ConsoleCommands.Abstract.Type
{
public class TypeEnum<T> : Parameter<object> where T : struct, Enum
{
public TypeEnum(string name, bool required, string description) : base(name, required, description)
{
Validate.IsTrue(typeof(T).IsEnum, $"Type {typeof(T).FullName} isn't an enum");
}
public override bool IsValid(string arg)
{
return Enum.TryParse<T>(arg, true, out _);
}
public override object Read(string arg)
{
Validate.IsTrue(Enum.TryParse(arg, true, out T value), $"Unknown value received (pick from: {string.Join(", ", Enum.GetNames(typeof(T)))})");
return value;
}
public override string GetDescription()
{
return $"{base.GetDescription()} (values: {string.Join(", ", Enum.GetNames(typeof(T)))})";
}
}
}

View File

@@ -0,0 +1,25 @@
using NitroxModel.Helper;
namespace NitroxServer.ConsoleCommands.Abstract.Type
{
public class TypeFloat : Parameter<float>, IParameter<object>
{
public TypeFloat(string name, bool isRequired, string description) : base(name, isRequired, description) { }
public override bool IsValid(string arg)
{
return float.TryParse(arg, out _);
}
public override float Read(string arg)
{
Validate.IsTrue(float.TryParse(arg, out float value), "Invalid decimal number received");
return value;
}
object IParameter<object>.Read(string arg)
{
return Read(arg);
}
}
}

View File

@@ -0,0 +1,25 @@
using NitroxModel.Helper;
namespace NitroxServer.ConsoleCommands.Abstract.Type
{
public class TypeInt : Parameter<int>, IParameter<object>
{
public TypeInt(string name, bool isRequired, string description) : base(name, isRequired, description) { }
public override bool IsValid(string arg)
{
return int.TryParse(arg, out _);
}
public override int Read(string arg)
{
Validate.IsTrue(int.TryParse(arg, out int value), "Invalid integer received");
return value;
}
object IParameter<object>.Read(string arg)
{
return Read(arg);
}
}
}

View File

@@ -0,0 +1,24 @@
using System;
using NitroxModel.DataStructures;
using NitroxModel.Helper;
namespace NitroxServer.ConsoleCommands.Abstract.Type;
public class TypeNitroxId(string name, bool isRequired, string description) : Parameter<NitroxId>(name, isRequired, description)
{
public override bool IsValid(string arg)
{
return IsValid(arg, out _);
}
private static bool IsValid(string arg, out Guid result)
{
return Guid.TryParse(arg, out result);
}
public override NitroxId Read(string arg)
{
Validate.IsTrue(IsValid(arg, out Guid result), "Received an invalid NitroxId");
return new NitroxId(result);
}
}

View File

@@ -0,0 +1,27 @@
using NitroxModel.Core;
using NitroxModel.Helper;
using NitroxServer.GameLogic;
namespace NitroxServer.ConsoleCommands.Abstract.Type
{
public class TypePlayer : Parameter<Player>
{
private static readonly PlayerManager playerManager = NitroxServiceLocator.LocateService<PlayerManager>();
public TypePlayer(string name, bool required, string description) : base(name, required, description)
{
Validate.NotNull(playerManager, "PlayerManager can't be null to resolve the command");
}
public override bool IsValid(string arg)
{
return playerManager.TryGetPlayerByName(arg, out _);
}
public override Player Read(string arg)
{
Validate.IsTrue(playerManager.TryGetPlayerByName(arg, out Player player), "Player not found");
return player;
}
}
}

View File

@@ -0,0 +1,20 @@
using NitroxModel.Helper;
namespace NitroxServer.ConsoleCommands.Abstract.Type
{
public class TypeString : Parameter<string>
{
public TypeString(string name, bool isRequired, string description) : base(name, isRequired, description) { }
public override bool IsValid(string arg)
{
return !string.IsNullOrEmpty(arg);
}
public override string Read(string arg)
{
Validate.IsTrue(IsValid(arg), "Received null/empty instead of a valid string");
return arg;
}
}
}

View File

@@ -0,0 +1,42 @@
using NitroxModel.DataStructures.GameLogic;
using NitroxServer.ConsoleCommands.Abstract;
using NitroxServer.ConsoleCommands.Abstract.Type;
using NitroxServer.GameLogic;
namespace NitroxServer.ConsoleCommands;
// TODO: When we make the new command system, move this stuff to it
public class AuroraCommand : Command
{
private readonly StoryManager storyManager;
// We shouldn't let the server use this command because it needs some stuff to happen client-side like goals
public AuroraCommand(StoryManager storyManager) : base("aurora", Perms.ADMIN, PermsFlag.NO_CONSOLE, "Manage Aurora's state")
{
AddParameter(new TypeString("countdown/restore/explode", true, "Which action to apply to Aurora"));
this.storyManager = storyManager;
}
protected override void Execute(CallArgs args)
{
string action = args.Get<string>(0);
switch (action.ToLower())
{
case "countdown":
storyManager.BroadcastExplodeAurora(true);
break;
case "restore":
storyManager.BroadcastRestoreAurora();
break;
case "explode":
storyManager.BroadcastExplodeAurora(false);
break;
default:
// Same message as in the abstract class, in method TryExecute
SendMessage(args.Sender, $"Error: Invalid Parameters\nUsage: {ToHelpText(false, true)}");
break;
}
}
}

View File

@@ -0,0 +1,43 @@
using System.IO;
using NitroxModel.DataStructures.GameLogic;
using NitroxModel.Serialization;
using NitroxServer.ConsoleCommands.Abstract;
using NitroxServer.ConsoleCommands.Abstract.Type;
namespace NitroxServer.ConsoleCommands
{
internal class AutoSaveCommand : Command
{
private readonly Server server;
private readonly SubnauticaServerConfig serverConfig;
public AutoSaveCommand(Server server, SubnauticaServerConfig serverConfig) : base("autosave", Perms.ADMIN, "Toggles the map autosave")
{
AddParameter(new TypeBoolean("on/off", true, "Whether autosave should be on or off"));
this.server = server;
this.serverConfig = serverConfig;
}
protected override void Execute(CallArgs args)
{
bool toggle = args.Get<bool>(0);
using (serverConfig.Update(Path.Combine(KeyValueStore.Instance.GetSavesFolderDir(), server.Name)))
{
if (toggle)
{
serverConfig.DisableAutoSave = false;
Server.Instance.EnablePeriodicSaving();
SendMessage(args.Sender, "Enabled periodical saving");
}
else
{
serverConfig.DisableAutoSave = true;
Server.Instance.DisablePeriodicSaving();
SendMessage(args.Sender, "Disabled periodical saving");
}
}
}
}
}

View File

@@ -0,0 +1,26 @@
using NitroxModel.DataStructures.GameLogic;
using NitroxServer.ConsoleCommands.Abstract;
namespace NitroxServer.ConsoleCommands
{
internal class BackCommand : Command
{
public BackCommand() : base("back", Perms.MODERATOR, PermsFlag.NO_CONSOLE, "Teleports you back on your last location")
{
}
protected override void Execute(CallArgs args)
{
Player player = args.Sender.Value;
if (player.LastStoredPosition == null)
{
SendMessage(args.Sender, "No previous location...");
return;
}
player.Teleport(player.LastStoredPosition.Value, player.LastStoredSubRootID);
SendMessage(args.Sender, $"Teleported back to {player.LastStoredPosition.Value}");
}
}
}

View File

@@ -0,0 +1,18 @@
using NitroxModel.DataStructures.GameLogic;
using NitroxServer.ConsoleCommands.Abstract;
namespace NitroxServer.ConsoleCommands
{
internal class BackupCommand : Command
{
public BackupCommand() : base("backup", Perms.ADMIN, "Creates a backup of the save")
{
}
protected override void Execute(CallArgs args)
{
Server.Instance.BackUp();
SendMessageToPlayer(args.Sender, "World has been backed up");
}
}
}

View File

@@ -0,0 +1,24 @@
using System.Collections.Generic;
using NitroxModel.DataStructures.GameLogic;
using NitroxServer.ConsoleCommands.Abstract;
using NitroxServer.ConsoleCommands.Abstract.Type;
namespace NitroxServer.ConsoleCommands
{
internal class BroadcastCommand : Command
{
public override IEnumerable<string> Aliases { get; } = new[] { "say" };
public BroadcastCommand() : base("broadcast", Perms.MODERATOR, "Broadcasts a message on the server")
{
AddParameter(new TypeString("message", true, "The message to be broadcast"));
AllowedArgOverflow = true;
}
protected override void Execute(CallArgs args)
{
SendMessageToAllPlayers(args.GetTillEnd());
}
}
}

View File

@@ -0,0 +1,35 @@
using System.IO;
using NitroxModel.DataStructures.GameLogic;
using NitroxModel.Serialization;
using NitroxServer.ConsoleCommands.Abstract;
using NitroxServer.ConsoleCommands.Abstract.Type;
namespace NitroxServer.ConsoleCommands
{
internal class ChangeAdminPasswordCommand : Command
{
private readonly Server server;
private readonly SubnauticaServerConfig serverConfig;
public ChangeAdminPasswordCommand(Server server, SubnauticaServerConfig serverConfig) : base("changeadminpassword", Perms.ADMIN, "Changes admin password")
{
AddParameter(new TypeString("password", true, "The new admin password"));
this.server = server;
this.serverConfig = serverConfig;
}
protected override void Execute(CallArgs args)
{
string saveDir = Path.Combine(KeyValueStore.Instance.GetSavesFolderDir(), server.Name);
using (serverConfig.Update(saveDir))
{
string newPassword = args.Get(0);
serverConfig.AdminPassword = newPassword;
Log.InfoSensitive("Admin password changed to {password} by {playername}", newPassword, args.SenderName);
}
SendMessageToPlayer(args.Sender, "Admin password has been updated");
}
}
}

View File

@@ -0,0 +1,50 @@
using System.IO;
using NitroxModel.DataStructures.GameLogic;
using NitroxModel.Packets;
using NitroxModel.Serialization;
using NitroxModel.Server;
using NitroxServer.ConsoleCommands.Abstract;
using NitroxServer.ConsoleCommands.Abstract.Type;
using NitroxServer.GameLogic;
namespace NitroxServer.ConsoleCommands;
internal class ChangeServerGamemodeCommand : Command
{
private readonly Server server;
private readonly PlayerManager playerManager;
private readonly SubnauticaServerConfig serverConfig;
public ChangeServerGamemodeCommand(Server server, PlayerManager playerManager, SubnauticaServerConfig serverConfig) : base("changeservergamemode", Perms.ADMIN, "Changes server gamemode")
{
AddParameter(new TypeEnum<NitroxGameMode>("gamemode", true, "Gamemode to change to"));
this.server = server;
this.playerManager = playerManager;
this.serverConfig = serverConfig;
}
protected override void Execute(CallArgs args)
{
NitroxGameMode sgm = args.Get<NitroxGameMode>(0);
using (serverConfig.Update(Path.Combine(KeyValueStore.Instance.GetSavesFolderDir(), server.Name)))
{
if (serverConfig.GameMode != sgm)
{
serverConfig.GameMode = sgm;
foreach (Player player in playerManager.GetAllPlayers())
{
player.GameMode = sgm;
}
playerManager.SendPacketToAllPlayers(GameModeChanged.ForAllPlayers(sgm));
SendMessageToAllPlayers($"Server gamemode changed to \"{sgm}\" by {args.SenderName}");
}
else
{
SendMessage(args.Sender, "Server is already using this gamemode");
}
}
}
}

View File

@@ -0,0 +1,35 @@
using System.IO;
using NitroxModel.DataStructures.GameLogic;
using NitroxModel.Serialization;
using NitroxServer.ConsoleCommands.Abstract;
using NitroxServer.ConsoleCommands.Abstract.Type;
namespace NitroxServer.ConsoleCommands
{
internal class ChangeServerPasswordCommand : Command
{
private readonly Server server;
private readonly SubnauticaServerConfig serverConfig;
public ChangeServerPasswordCommand(Server server, SubnauticaServerConfig serverConfig) : base("changeserverpassword", Perms.ADMIN, "Changes server password. Clear it without argument")
{
AddParameter(new TypeString("password", false, "The new server password"));
this.server = server;
this.serverConfig = serverConfig;
}
protected override void Execute(CallArgs args)
{
string password = args.Get(0) ?? string.Empty;
using (serverConfig.Update(Path.Combine(KeyValueStore.Instance.GetSavesFolderDir(), server.Name)))
{
serverConfig.ServerPassword = password;
}
Log.InfoSensitive("Server password changed to \"{password}\" by {playername}", password, args.SenderName);
SendMessageToPlayer(args.Sender, "Server password has been updated");
}
}
}

View File

@@ -0,0 +1,82 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using NitroxModel.DataStructures.GameLogic;
using NitroxModel.Platforms.OS.Shared;
using NitroxModel.Serialization;
using NitroxServer.ConsoleCommands.Abstract;
namespace NitroxServer.ConsoleCommands
{
internal class ConfigCommand : Command
{
private readonly SemaphoreSlim configOpenLock = new(1);
private readonly Server server;
private readonly SubnauticaServerConfig serverConfig;
public ConfigCommand(Server server, SubnauticaServerConfig serverConfig) : base("config", Perms.CONSOLE, "Opens the server configuration file")
{
this.server = server;
this.serverConfig = serverConfig;
}
protected override void Execute(CallArgs args)
{
if (!configOpenLock.Wait(0))
{
Log.Warn("Waiting on previous config command to close the configuration file.");
return;
}
// Save config file if it doesn't exist yet.
string saveDir = Path.Combine(KeyValueStore.Instance.GetSavesFolderDir(), server.Name);
string configFile = Path.Combine(saveDir, serverConfig.FileName);
if (!File.Exists(configFile))
{
serverConfig.Serialize(saveDir);
}
Task.Run(async () =>
{
try
{
await StartWithDefaultProgramAsync(configFile);
}
finally
{
configOpenLock.Release();
}
serverConfig.Deserialize(saveDir); // Notifies user if deserialization failed.
Log.Info("If you made changes, restart the server for them to take effect.");
})
.ContinueWith(t =>
{
#if DEBUG
if (t.Exception != null)
{
throw t.Exception;
}
#endif
});
}
private async Task StartWithDefaultProgramAsync(string fileToOpen)
{
using Process process = FileSystem.Instance.OpenOrExecuteFile(fileToOpen);
await process.WaitForExitAsync();
try
{
while (!process.HasExited)
{
await Task.Delay(100);
}
}
catch (Exception)
{
// ignored
}
}
}
}

View File

@@ -0,0 +1,37 @@
#if DEBUG
using System.Collections.Generic;
using NitroxModel.DataStructures.GameLogic;
using NitroxModel.DataStructures.Unity;
using NitroxModel.Packets;
using NitroxServer.ConsoleCommands.Abstract;
using NitroxServer.GameLogic;
using NitroxServer.Serialization.World;
namespace NitroxServer.ConsoleCommands
{
internal class DebugStartMapCommand : Command
{
private readonly RandomStartGenerator nitroxRandomStart;
private readonly PlayerManager playerManager;
private readonly World world;
public DebugStartMapCommand(PlayerManager playerManager, RandomStartGenerator nitroxRandomStart, World world) :
base("debugstartmap", Perms.ADMIN, "Spawns blocks at spawn positions")
{
this.playerManager = playerManager;
this.nitroxRandomStart = nitroxRandomStart;
this.world = world;
}
protected override void Execute(CallArgs args)
{
List<NitroxVector3> randomStartPositions = nitroxRandomStart.GenerateRandomStartPositions(world.Seed);
playerManager.SendPacketToAllPlayers(new DebugStartMapPacket(randomStartPositions));
SendMessage(args.Sender, $"Rendered {randomStartPositions.Count} spawn positions");
}
}
}
#endif

View File

@@ -0,0 +1,26 @@
using NitroxModel.DataStructures.GameLogic;
using NitroxModel.Packets;
using NitroxServer.ConsoleCommands.Abstract;
using NitroxServer.ConsoleCommands.Abstract.Type;
namespace NitroxServer.ConsoleCommands
{
internal class DeopCommand : Command
{
public DeopCommand() : base("deop", Perms.ADMIN, "Removes admin rights from user")
{
AddParameter(new TypePlayer("name", true, "Username to remove admin rights from"));
}
protected override void Execute(CallArgs args)
{
Player targetPlayer = args.Get<Player>(0);
targetPlayer.Permissions = Perms.PLAYER;
// Need to notify him so that he no longer shows admin stuff on client (which would in any way stop working)
targetPlayer.SendPacket(new PermsChanged(targetPlayer.Permissions));
SendMessage(targetPlayer, "You were demoted to PLAYER");
SendMessage(args.Sender, $"Updated {targetPlayer.Name}\'s permissions to PLAYER");
}
}
}

View File

@@ -0,0 +1,33 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using NitroxModel.DataStructures.GameLogic;
using NitroxServer.ConsoleCommands.Abstract;
namespace NitroxServer.ConsoleCommands
{
internal class DirectoryCommand : Command
{
public override IEnumerable<string> Aliases { get; } = new[] { "dir" };
public DirectoryCommand() : base("directory", Perms.CONSOLE, "Opens the current directory of the server")
{
}
protected override void Execute(CallArgs args)
{
string path = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
if (!Directory.Exists(path))
{
Log.ErrorSensitive("Unable to open Nitrox directory {path} because it does not exist", path);
return;
}
Log.InfoSensitive("Opening directory {path}", path);
Process.Start(new ProcessStartInfo(path) { UseShellExecute = true, Verb = "open" })?.Dispose();
}
}
}

View File

@@ -0,0 +1,64 @@
using System;
using NitroxModel.DataStructures.GameLogic;
using NitroxModel.Packets;
using NitroxServer.ConsoleCommands.Abstract;
using NitroxServer.ConsoleCommands.Abstract.Type;
using NitroxServer.GameLogic;
namespace NitroxServer.ConsoleCommands;
public class FastCommand : Command
{
private readonly PlayerManager playerManager;
private readonly SessionSettings sessionSettings;
public FastCommand(PlayerManager playerManager, SessionSettings sessionSettings) : base("fast", Perms.MODERATOR, "Enables/disables a fast cheat command, whether it be \"hatch\" or \"grow\"")
{
AddParameter(new TypeEnum<FastCheatChanged.FastCheat>("cheat", true, "The name of the fast cheat to change: \"hatch\" or \"grow\""));
AddParameter(new TypeBoolean("value", false, "Whether the cheat will be enabled or disabled. Default count as a toggle"));
this.playerManager = playerManager;
this.sessionSettings = sessionSettings;
}
protected override void Execute(CallArgs args)
{
FastCheatChanged.FastCheat cheat = args.Get<FastCheatChanged.FastCheat>(0);
bool value = cheat switch
{
FastCheatChanged.FastCheat.HATCH => sessionSettings.FastHatch,
FastCheatChanged.FastCheat.GROW => sessionSettings.FastGrow,
_ => throw new ArgumentException("Must provide a valid cheat name: \"hatch\" or \"grow\""),
};
if (args.IsValid(1))
{
bool newValue = args.Get<bool>(1);
if (newValue == value)
{
SendMessage(args.Sender, $"Fast {cheat} already set to {newValue}");
return;
}
value = newValue;
}
else
{
// If the value wasn't provided then we toggle it
value = !value;
}
switch (cheat)
{
case FastCheatChanged.FastCheat.HATCH:
sessionSettings.FastHatch = value;
break;
case FastCheatChanged.FastCheat.GROW:
sessionSettings.FastGrow = value;
break;
}
playerManager.SendPacketToAllPlayers(new FastCheatChanged(cheat, value));
SendMessageToAllPlayers($"Fast {cheat} changed to {value} by {args.SenderName}");
}
}

View File

@@ -0,0 +1,50 @@
using NitroxModel.DataStructures.GameLogic;
using NitroxModel.Packets;
using NitroxModel.Server;
using NitroxServer.ConsoleCommands.Abstract;
using NitroxServer.ConsoleCommands.Abstract.Type;
using NitroxServer.GameLogic;
namespace NitroxServer.ConsoleCommands;
internal class GameModeCommand : Command
{
private readonly PlayerManager playerManager;
public GameModeCommand(PlayerManager playerManager) : base("gamemode", Perms.ADMIN, "Changes a player's gamemode")
{
AddParameter(new TypeEnum<NitroxGameMode>("gamemode", true, "Gamemode to change to"));
AddParameter(new TypePlayer("name", false, "Username to whom change the game mode (defaults to self)"));
this.playerManager = playerManager;
}
protected override void Execute(CallArgs args)
{
NitroxGameMode gameMode = args.Get<NitroxGameMode>(0);
Player targetPlayer = args.Get<Player>(1);
if (args.IsConsole && targetPlayer == null)
{
Log.Error($"Console can't use the gamemode command without providing a player name to it.");
return;
}
// The target player if not set, is the player who sent the command
targetPlayer ??= args.Sender.Value;
targetPlayer.GameMode = gameMode;
playerManager.SendPacketToAllPlayers(GameModeChanged.ForPlayer(targetPlayer.Id, gameMode));
SendMessage(targetPlayer, $"GameMode changed to {gameMode}");
if (args.IsConsole)
{
Log.Info($"Changed {targetPlayer.Name} [{targetPlayer.Id}]'s gamemode to {gameMode}");
}
else
{
if (targetPlayer != args.Sender.Value)
{
SendMessage(args.Sender.Value, $"GameMode of {targetPlayer.Name} changed to {gameMode}");
}
}
}
}

View File

@@ -0,0 +1,62 @@
using System.Collections.Generic;
using System.Linq;
using NitroxModel.Core;
using NitroxModel.DataStructures.GameLogic;
using NitroxServer.ConsoleCommands.Abstract;
using NitroxServer.ConsoleCommands.Abstract.Type;
namespace NitroxServer.ConsoleCommands
{
internal class HelpCommand : Command
{
public override IEnumerable<string> Aliases { get; } = new[] { "?" };
public HelpCommand() : base("help", Perms.PLAYER, "Displays this")
{
AddParameter(new TypeString("command", false, "Command to see help information for"));
}
protected override void Execute(CallArgs args)
{
List<string> cmdsText;
if (args.IsConsole)
{
cmdsText = GetHelpText(Perms.CONSOLE, false, args.IsValid(0) ? args.Get<string>(0) : null);
foreach (string cmdText in cmdsText)
{
Log.Info(cmdText);
}
}
else
{
cmdsText = GetHelpText(args.Sender.Value.Permissions, true, args.IsValid(0) ? args.Get<string>(0) : null);
foreach (string cmdText in cmdsText)
{
SendMessageToPlayer(args.Sender, cmdText);
}
}
}
private List<string> GetHelpText(Perms permThreshold, bool cropText, string singleCommand)
{
static bool CanExecuteAndProcess(Command cmd, Perms perms)
{
return cmd.CanExecute(perms) && !(perms == Perms.CONSOLE && cmd.Flags.HasFlag(PermsFlag.NO_CONSOLE));
}
//Runtime query to avoid circular dependencies
IEnumerable<Command> commands = NitroxServiceLocator.LocateService<IEnumerable<Command>>();
if (singleCommand != null && !commands.Any(cmd => cmd.Name.Equals(singleCommand)))
{
return new List<string> { "Command does not exist" };
}
List<string> cmdsText = new();
cmdsText.Add(singleCommand != null ? $"=== Showing help for {singleCommand} ===" : "=== Showing command list ===");
cmdsText.AddRange(commands.Where(cmd => CanExecuteAndProcess(cmd, permThreshold) && (singleCommand == null || cmd.Name.Equals(singleCommand)))
.OrderByDescending(cmd => cmd.Name)
.Select(cmd => cmd.ToHelpText(singleCommand != null, cropText)));
return cmdsText;
}
}
}

View File

@@ -0,0 +1,58 @@
using System.Collections.Generic;
using NitroxModel.DataStructures;
using NitroxModel.DataStructures.GameLogic;
using NitroxModel.Packets;
using NitroxServer.ConsoleCommands.Abstract;
using NitroxServer.ConsoleCommands.Abstract.Type;
using NitroxServer.GameLogic;
using NitroxServer.GameLogic.Entities;
namespace NitroxServer.ConsoleCommands
{
internal class KickCommand : Command
{
private readonly EntitySimulation entitySimulation;
private readonly PlayerManager playerManager;
public KickCommand(PlayerManager playerManager, EntitySimulation entitySimulation) : base("kick", Perms.MODERATOR, "Kicks a player from the server")
{
AddParameter(new TypePlayer("name", true, "Name of the player to kick"));
AddParameter(new TypeString("reason", false, "Reason for kicking the player"));
AllowedArgOverflow = true;
this.playerManager = playerManager;
this.entitySimulation = entitySimulation;
}
protected override void Execute(CallArgs args)
{
Player playerToKick = args.Get<Player>(0);
if (args.SenderName == playerToKick.Name)
{
SendMessage(args.Sender, "You can't kick yourself");
return;
}
if (!args.IsConsole && playerToKick.Permissions >= args.Sender.Value.Permissions)
{
SendMessage(args.Sender, $"You're not allowed to kick {playerToKick.Name}");
return;
}
playerToKick.SendPacket(new PlayerKicked(args.GetTillEnd(1)));
playerManager.PlayerDisconnected(playerToKick.Connection);
List<SimulatedEntity> revokedEntities = entitySimulation.CalculateSimulationChangesFromPlayerDisconnect(playerToKick);
if (revokedEntities.Count > 0)
{
SimulationOwnershipChange ownershipChange = new(revokedEntities);
playerManager.SendPacketToAllPlayers(ownershipChange);
}
playerManager.SendPacketToOtherPlayers(new Disconnect(playerToKick.Id), playerToKick);
SendMessage(args.Sender, $"The player {playerToKick.Name} has been disconnected");
}
}
}

View File

@@ -0,0 +1,32 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NitroxModel.DataStructures.GameLogic;
using NitroxModel.Serialization;
using NitroxServer.ConsoleCommands.Abstract;
using NitroxServer.GameLogic;
namespace NitroxServer.ConsoleCommands
{
internal class ListCommand : Command
{
private readonly PlayerManager playerManager;
private readonly SubnauticaServerConfig serverConfig;
public ListCommand(SubnauticaServerConfig serverConfig, PlayerManager playerManager) : base("list", Perms.PLAYER, "Shows who's online")
{
this.playerManager = playerManager;
this.serverConfig = serverConfig;
}
protected override void Execute(CallArgs args)
{
IList<string> players = playerManager.GetConnectedPlayers().Select(player => player.Name).ToList();
StringBuilder builder = new($"List of players ({players.Count}/{serverConfig.MaxConnections}):\n");
builder.Append(string.Join(", ", players));
SendMessage(args.Sender, builder.ToString());
}
}
}

View File

@@ -0,0 +1,33 @@
#if DEBUG
using System.Collections.Generic;
using NitroxModel.DataStructures;
using NitroxModel.DataStructures.GameLogic;
using NitroxServer.ConsoleCommands.Abstract;
using NitroxServer.ConsoleCommands.Abstract.Type;
using NitroxServer.GameLogic.Entities.Spawning;
namespace NitroxServer.ConsoleCommands
{
internal class LoadBatchCommand : Command
{
private readonly BatchEntitySpawner batchEntitySpawner;
public LoadBatchCommand(BatchEntitySpawner batchEntitySpawner) : base("loadbatch", Perms.ADMIN, "Loads entities at x y z")
{
AddParameter(new TypeInt("x", true, "x coordinate"));
AddParameter(new TypeInt("y", true, "y coordinate"));
AddParameter(new TypeInt("z", true, "z coordinate"));
this.batchEntitySpawner = batchEntitySpawner;
}
protected override void Execute(CallArgs args)
{
NitroxInt3 batchId = new(args.Get<int>(0), args.Get<int>(1), args.Get<int>(2));
List<Entity> entities = batchEntitySpawner.LoadUnspawnedEntities(batchId);
SendMessage(args.Sender, $"Loaded {entities.Count} entities from batch {batchId}");
}
}
}
#endif

View File

@@ -0,0 +1,32 @@
using NitroxModel.DataStructures.GameLogic;
using NitroxModel.Serialization;
using NitroxServer.ConsoleCommands.Abstract;
using NitroxServer.ConsoleCommands.Abstract.Type;
namespace NitroxServer.ConsoleCommands
{
internal class LoginCommand : Command
{
private readonly SubnauticaServerConfig serverConfig;
public LoginCommand(SubnauticaServerConfig serverConfig) : base("login", Perms.PLAYER, PermsFlag.NO_CONSOLE, "Log in to server as admin (requires password)")
{
AddParameter(new TypeString("password", true, "The admin password for the server"));
this.serverConfig = serverConfig;
}
protected override void Execute(CallArgs args)
{
if (args.Get<string>(0) == serverConfig.AdminPassword)
{
args.Sender.Value.Permissions = Perms.ADMIN;
SendMessage(args.Sender, $"Updated permissions to ADMIN for {args.SenderName}");
}
else
{
SendMessage(args.Sender, "Incorrect Password");
}
}
}
}

View File

@@ -0,0 +1,48 @@
using NitroxModel.DataStructures.GameLogic;
using NitroxModel.Packets;
using NitroxServer.ConsoleCommands.Abstract;
using NitroxServer.ConsoleCommands.Abstract.Type;
using NitroxServer.GameLogic;
namespace NitroxServer.ConsoleCommands
{
internal class MuteCommand : Command
{
private readonly PlayerManager playerManager;
public MuteCommand(PlayerManager playerManager) : base("mute", Perms.MODERATOR, "Prevents a user from chatting")
{
this.playerManager = playerManager;
AddParameter(new TypePlayer("name", true, "Player to mute"));
}
protected override void Execute(CallArgs args)
{
Player targetPlayer = args.Get<Player>(0);
if (args.SenderName == targetPlayer.Name)
{
SendMessage(args.Sender, "You can't mute yourself");
return;
}
if (!args.IsConsole && targetPlayer.Permissions >= args.Sender.Value.Permissions)
{
SendMessage(args.Sender, $"You're not allowed to mute {targetPlayer.Name}");
return;
}
if (targetPlayer.PlayerContext.IsMuted)
{
SendMessage(args.Sender, $"{targetPlayer.Name} is already muted");
args.Sender.Value.SendPacket(new MutePlayer(targetPlayer.Id, targetPlayer.PlayerContext.IsMuted));
return;
}
targetPlayer.PlayerContext.IsMuted = true;
playerManager.SendPacketToAllPlayers(new MutePlayer(targetPlayer.Id, targetPlayer.PlayerContext.IsMuted));
SendMessage(targetPlayer, "You're now muted");
SendMessage(args.Sender, $"Muted {targetPlayer.Name}");
}
}
}

View File

@@ -0,0 +1,26 @@
using NitroxModel.DataStructures.GameLogic;
using NitroxModel.Packets;
using NitroxServer.ConsoleCommands.Abstract;
using NitroxServer.ConsoleCommands.Abstract.Type;
namespace NitroxServer.ConsoleCommands
{
internal class OpCommand : Command
{
public OpCommand() : base("op", Perms.ADMIN, "Sets a user as admin")
{
AddParameter(new TypePlayer("name", true, "The players name to make an admin"));
}
protected override void Execute(CallArgs args)
{
Player targetPlayer = args.Get<Player>(0);
targetPlayer.Permissions = Perms.ADMIN;
// We need to notify this player that he can show all the admin-related stuff
targetPlayer.SendPacket(new PermsChanged(targetPlayer.Permissions));
SendMessage(targetPlayer, "You were promoted to ADMIN");
SendMessage(args.Sender, $"Updated {targetPlayer.Name}\'s permissions to ADMIN");
}
}
}

View File

@@ -0,0 +1,62 @@
#if DEBUG
using System;
using System.Collections.Generic;
using NitroxModel.Core;
using NitroxModel.DataStructures.GameLogic;
using NitroxModel.DataStructures.GameLogic.Entities;
using NitroxModel.DataStructures.Unity;
using NitroxServer.ConsoleCommands.Abstract;
using NitroxServer.ConsoleCommands.Abstract.Type;
using NitroxServer.GameLogic;
using NitroxServer.GameLogic.Entities;
namespace NitroxServer.ConsoleCommands;
internal class PlayerCommand : Command
{
private readonly Lazy<PlayerManager> playerManager = new(NitroxServiceLocator.LocateService<PlayerManager>);
private readonly Lazy<WorldEntityManager> worldEntityManager = new(NitroxServiceLocator.LocateService<WorldEntityManager>);
private readonly Lazy<SimulationOwnershipData> simulationOwnershipData = new(NitroxServiceLocator.LocateService<SimulationOwnershipData>);
public PlayerCommand() : base("player", Perms.CONSOLE, "Lists all visible cells of a player, their simulated entities per cell and the player's visible out of cell entities")
{
AddParameter(new TypeString("player name", true, "name of the target player"));
}
protected override void Execute(CallArgs args)
{
string playerName = args.Get<string>(0);
if (playerManager.Value.TryGetPlayerByName(playerName, out Player player))
{
List<AbsoluteEntityCell> visibleCells = player.GetVisibleCells();
Log.Info(player);
Log.Info($"Visible cells [{visibleCells.Count}]:");
foreach (AbsoluteEntityCell visibleCell in visibleCells)
{
string simulatedEntities = "";
foreach (WorldEntity worldEntity in worldEntityManager.Value.GetEntities(visibleCell))
{
if (simulationOwnershipData.Value.TryGetLock(worldEntity.Id, out SimulationOwnershipData.PlayerLock playerLock) &&
playerLock.Player.Id == player.Id)
{
simulatedEntities += $"[{worldEntity.Id}; {worldEntity.TechType?.ToString() ?? worldEntity.ClassId}], ";
}
}
Log.Info($"{visibleCell}; {NitroxVector3.Distance(visibleCell.Position, player.Position)}");
if (simulatedEntities.Length > 0)
{
// Get everything but the last ", " of the string
Log.Info(simulatedEntities[..^2]);
}
}
Log.Info($"\nOut of cell entities:\n{string.Join(", ", player.OutOfCellVisibleEntities)}");
}
else
{
Log.Error($"Player with name {playerName} not found");
}
}
}
#endif

View File

@@ -0,0 +1,66 @@
using System;
using System.Collections.Generic;
using NitroxModel.DataStructures.GameLogic;
using NitroxModel.DataStructures.Util;
using NitroxServer.ConsoleCommands.Abstract;
using NitroxServer.Exceptions;
namespace NitroxServer.ConsoleCommands.Processor
{
public class ConsoleCommandProcessor
{
private readonly Dictionary<string, Command> commands = new();
private readonly char[] splitChar = { ' ' };
public ConsoleCommandProcessor(IEnumerable<Command> cmds)
{
foreach (Command cmd in cmds)
{
if (commands.ContainsKey(cmd.Name))
{
throw new DuplicateRegistrationException($"Command {cmd.Name} is registered multiple times.");
}
commands[cmd.Name] = cmd;
foreach (string alias in cmd.Aliases)
{
if (commands.ContainsKey(alias))
{
throw new DuplicateRegistrationException($"Command {alias} is registered multiple times.");
}
commands[alias] = cmd;
}
}
}
public void ProcessCommand(string msg, Optional<Player> sender, Perms permissions)
{
if (string.IsNullOrWhiteSpace(msg))
{
return;
}
Span<string> parts = msg.Split(splitChar, StringSplitOptions.RemoveEmptyEntries);
if (!commands.TryGetValue(parts[0], out Command command))
{
Command.SendMessage(sender, $"Command not found: {parts[0]}");
return;
}
if (!sender.HasValue && command.Flags.HasFlag(PermsFlag.NO_CONSOLE))
{
Log.Error("This command cannot be used by CONSOLE");
return;
}
if (command.CanExecute(permissions))
{
command.TryExecute(sender, parts[1..]);
}
else
{
Command.SendMessage(sender, "You do not have the required permissions for this command !");
}
}
}
}

View File

@@ -0,0 +1,42 @@
using NitroxModel.DataStructures.GameLogic;
using NitroxModel.Packets;
using NitroxServer.ConsoleCommands.Abstract;
using NitroxServer.ConsoleCommands.Abstract.Type;
namespace NitroxServer.ConsoleCommands
{
internal class PromoteCommand : Command
{
public PromoteCommand() : base("promote", Perms.MODERATOR, "Sets specific permissions to a user")
{
AddParameter(new TypePlayer("name", true, "The username to change the permissions of"));
AddParameter(new TypeEnum<Perms>("perms", true, "Permission level"));
}
protected override void Execute(CallArgs args)
{
Player targetPlayer = args.Get<Player>(0);
Perms permissions = args.Get<Perms>(1);
if (args.SenderName == targetPlayer.Name)
{
SendMessage(args.Sender, "You can't promote yourself");
return;
}
//Allows a bounded permission hierarchy
if (args.IsConsole || permissions < args.Sender.Value.Permissions)
{
targetPlayer.Permissions = permissions;
targetPlayer.SendPacket(new PermsChanged(targetPlayer.Permissions));
SendMessage(args.Sender, $"Updated {targetPlayer.Name}\'s permissions to {permissions}");
SendMessageToPlayer(targetPlayer, $"You've been promoted to {permissions}");
}
else
{
SendMessage(args.Sender, $"You're not allowed to update {targetPlayer.Name}\'s permissions");
}
}
}
}

View File

@@ -0,0 +1,55 @@
using System.IO;
using NitroxModel.DataStructures.GameLogic;
using NitroxModel.Serialization;
using NitroxServer.ConsoleCommands.Abstract;
using NitroxServer.ConsoleCommands.Abstract.Type;
using NitroxServer.Serialization;
using NitroxServer.Serialization.World;
namespace NitroxServer.ConsoleCommands;
public class PvpCommand : Command
{
private readonly SubnauticaServerConfig serverConfig;
private readonly Server server;
public PvpCommand(SubnauticaServerConfig serverConfig, Server server) : base("pvp", Perms.ADMIN, "Enables/Disables PvP")
{
AddParameter(new TypeString("state", true, "on/off"));
this.serverConfig = serverConfig;
this.server = server;
}
protected override void Execute(CallArgs args)
{
string state = args.Get<string>(0).ToLower();
bool pvpEnabled = false;
switch (state)
{
case "on":
pvpEnabled = true;
break;
case "off":
break;
default:
SendMessage(args.Sender, "Parameter must be \"on\" or \"off\"");
return;
}
using (serverConfig.Update(Path.Combine(KeyValueStore.Instance.GetSavesFolderDir(), server.Name)))
{
if (serverConfig.PvPEnabled == pvpEnabled)
{
SendMessage(args.Sender, $"PvP is already {state}");
return;
}
serverConfig.PvPEnabled = pvpEnabled;
SendMessageToAllPlayers($"PvP is now {state}");
}
}
}

View File

@@ -0,0 +1,50 @@
#if DEBUG
using System;
using NitroxModel.Core;
using NitroxModel.DataStructures;
using NitroxModel.DataStructures.GameLogic;
using NitroxModel.DataStructures.GameLogic.Entities;
using NitroxServer.ConsoleCommands.Abstract;
using NitroxServer.ConsoleCommands.Abstract.Type;
using NitroxServer.GameLogic;
using NitroxServer.GameLogic.Entities;
namespace NitroxServer.ConsoleCommands;
internal class QueryCommand : Command
{
private readonly Lazy<EntityRegistry> entityRegistry = new(NitroxServiceLocator.LocateService<EntityRegistry>);
private readonly Lazy<SimulationOwnershipData> simulationOwnershipData = new(NitroxServiceLocator.LocateService<SimulationOwnershipData>);
public QueryCommand() : base("query", Perms.CONSOLE, "Query the entity associated with the given NitroxId")
{
AddParameter(new TypeNitroxId("entityId", true, "NitroxId of the queried entity"));
}
protected override void Execute(CallArgs args)
{
NitroxId nitroxId = args.Get<NitroxId>(0);
if (entityRegistry.Value.TryGetEntityById(nitroxId, out Entity entity))
{
Log.Info(entity);
if (entity is WorldEntity worldEntity && worldEntity.Transform != null)
{
Log.Info(worldEntity.AbsoluteEntityCell);
}
if (simulationOwnershipData.Value.TryGetLock(nitroxId, out SimulationOwnershipData.PlayerLock playerLock))
{
Log.Info($"Lock owner: {playerLock.Player.Name}");
}
else
{
Log.Info("Not locked");
}
}
else
{
Log.Error($"Entity with id {nitroxId} not found");
}
}
}
#endif

View File

@@ -0,0 +1,37 @@
using System.Diagnostics;
using NitroxModel.DataStructures.GameLogic;
using NitroxServer.ConsoleCommands.Abstract;
namespace NitroxServer.ConsoleCommands
{
internal class RestartCommand : Command
{
private readonly Server server;
public RestartCommand(Server server) : base("restart", Perms.CONSOLE, "Restarts the server")
{
this.server = server;
}
protected override void Execute(CallArgs args)
{
if (Debugger.IsAttached)
{
Log.Error("Cannot restart server while debugger is attached.");
return;
}
string program = Process.GetCurrentProcess().MainModule?.FileName;
if (program == null)
{
Log.Error("Failed to get location of server.");
return;
}
SendMessageToAllPlayers("Server is restarting...");
server.Stop();
using Process proc = Process.Start(program);
}
}
}

View File

@@ -0,0 +1,18 @@
using NitroxModel.DataStructures.GameLogic;
using NitroxServer.ConsoleCommands.Abstract;
namespace NitroxServer.ConsoleCommands
{
internal class SaveCommand : Command
{
public SaveCommand() : base("save", Perms.MODERATOR, "Saves the map")
{
}
protected override void Execute(CallArgs args)
{
Server.Instance.Save();
SendMessageToPlayer(args.Sender, "World saved");
}
}
}

View File

@@ -0,0 +1,43 @@
using System.IO;
using NitroxModel.Packets;
using NitroxModel.Serialization;
using NitroxModel.Server;
using NitroxServer.ConsoleCommands.Abstract;
using NitroxServer.ConsoleCommands.Abstract.Type;
using NitroxServer.GameLogic;
using NitroxServer.Serialization;
using NitroxServer.Serialization.World;
namespace NitroxServer.ConsoleCommands;
internal class SetKeepInventoryCommand : Command
{
private readonly PlayerManager playerManager;
private readonly SubnauticaServerConfig serverConfig;
private readonly Server server;
public SetKeepInventoryCommand(PlayerManager playerManager, SubnauticaServerConfig serverConfig, Server server) : base("keepinventory", NitroxModel.DataStructures.GameLogic.Perms.ADMIN, "Sets \"keep inventory\" setting to on/off. If \"on\", players won't lose items when they die.")
{
AddParameter(new TypeBoolean("state", true, "The true/false state to set keep inventory on death to"));
this.playerManager = playerManager;
this.serverConfig = serverConfig;
this.server = server;
}
protected override void Execute(CallArgs args)
{
bool newKeepInventoryState = args.Get<bool>(0);
using (serverConfig.Update(Path.Combine(KeyValueStore.Instance.GetSavesFolderDir(), server.Name)))
{
if (serverConfig.KeepInventoryOnDeath != newKeepInventoryState)
{
serverConfig.KeepInventoryOnDeath = newKeepInventoryState;
playerManager.SendPacketToAllPlayers(new KeepInventoryChanged(newKeepInventoryState));
SendMessageToAllPlayers($"KeepInventoryOnDeath changed to \"{newKeepInventoryState}\" by {args.SenderName}");
}
else
{
SendMessage(args.Sender, $"KeepInventoryOnDeath already set to {newKeepInventoryState}");
}
}
}
}

View File

@@ -0,0 +1,26 @@
using System.Collections.Generic;
using NitroxModel.DataStructures.GameLogic;
using NitroxServer.ConsoleCommands.Abstract;
namespace NitroxServer.ConsoleCommands
{
internal class StopCommand : Command
{
public override IEnumerable<string> Aliases { get; } = new[] { "exit", "halt", "quit", "close" };
public StopCommand() : base("stop", Perms.ADMIN, "Stops the server")
{
}
protected override void Execute(CallArgs args)
{
Server server = Server.Instance;
if (!server.IsRunning)
{
return;
}
SendMessageToAllPlayers("Server is shutting down...");
server.Stop(shouldSave: true);
}
}
}

View File

@@ -0,0 +1,23 @@
using NitroxModel.DataStructures.GameLogic;
using NitroxServer.ConsoleCommands.Abstract;
namespace NitroxServer.ConsoleCommands
{
internal class SummaryCommand : Command
{
private readonly Server server;
public SummaryCommand(Server server) : base("summary", Perms.MODERATOR, "Shows persisted data")
{
this.server = server;
AllowedArgOverflow = true;
}
protected override void Execute(CallArgs args)
{
Perms viewerPerms = args.Sender.OrNull()?.Permissions ?? Perms.PLAYER;
SendMessage(args.Sender, server.GetSaveSummary(viewerPerms));
}
}
}

View File

@@ -0,0 +1,48 @@
using NitroxModel.DataStructures.GameLogic;
using NitroxModel.Packets;
using NitroxServer.ConsoleCommands.Abstract;
using NitroxServer.ConsoleCommands.Abstract.Type;
using NitroxServer.GameLogic;
namespace NitroxServer.ConsoleCommands;
// TODO: When we make the new command system, move this stuff to it
public class SunbeamCommand : Command
{
private readonly StoryManager storyManager;
// We shouldn't let the server use this command because it needs some stuff to happen client-side like goals
public SunbeamCommand(StoryManager storyManager) : base("sunbeam", Perms.ADMIN, PermsFlag.NO_CONSOLE, "Start sunbeam events")
{
AddParameter(new TypeString("storystart/countdown/gunaim", true, "Which Sunbeam event to start"));
this.storyManager = storyManager;
}
protected override void Execute(CallArgs args)
{
if (!args.Sender.HasValue)
{
SendMessage(args.Sender, "This command can't be used by CONSOLE");
return;
}
string action = args.Get<string>(0);
switch (action.ToLower())
{
case "storystart":
storyManager.StartSunbeamEvent(PlaySunbeamEvent.SunbeamEvent.STORYSTART);
break;
case "countdown":
storyManager.StartSunbeamEvent(PlaySunbeamEvent.SunbeamEvent.COUNTDOWN);
break;
case "gunaim":
storyManager.StartSunbeamEvent(PlaySunbeamEvent.SunbeamEvent.GUNAIM);
break;
default:
// Same message as in the abstract class, in method TryExecute
SendMessage(args.Sender, $"Error: Invalid Parameters\nUsage: {ToHelpText(false, true)}");
break;
}
}
}

View File

@@ -0,0 +1,45 @@
using System.IO;
using NitroxModel.DataStructures.GameLogic;
using NitroxModel.Serialization;
using NitroxModel.Server;
using NitroxServer.ConsoleCommands.Abstract;
using NitroxServer.ConsoleCommands.Abstract.Type;
using NitroxServer.Serialization.World;
namespace NitroxServer.ConsoleCommands
{
internal class SwapSerializerCommand : Command
{
private readonly Server server;
private readonly WorldPersistence worldPersistence;
private readonly SubnauticaServerConfig serverConfig;
public SwapSerializerCommand(Server server, SubnauticaServerConfig serverConfig, WorldPersistence worldPersistence) : base("swapserializer", Perms.CONSOLE, "Allows to change the save format")
{
AddParameter(new TypeEnum<ServerSerializerMode>("serializer", true, "Save format to change to"));
this.server = server;
this.worldPersistence = worldPersistence;
this.serverConfig = serverConfig;
}
protected override void Execute(CallArgs args)
{
ServerSerializerMode serializerMode = args.Get<ServerSerializerMode>(0);
using (serverConfig.Update(Path.Combine(KeyValueStore.Instance.GetSavesFolderDir(), server.Name)))
{
if (serializerMode != serverConfig.SerializerMode)
{
serverConfig.SerializerMode = serializerMode;
worldPersistence.UpdateSerializer(serializerMode);
SendMessage(args.Sender, $"Server save format swapped to {serverConfig.SerializerMode}");
}
else
{
SendMessage(args.Sender, "Server is already using this save format");
}
}
}
}
}

View File

@@ -0,0 +1,29 @@
using System.Collections.Generic;
using NitroxModel.DataStructures.GameLogic;
using NitroxModel.DataStructures.Unity;
using NitroxModel.DataStructures.Util;
using NitroxServer.ConsoleCommands.Abstract;
using NitroxServer.ConsoleCommands.Abstract.Type;
namespace NitroxServer.ConsoleCommands
{
internal class TeleportCommand : Command
{
public override IEnumerable<string> Aliases { get; } = new[] { "tp" };
public TeleportCommand() : base("teleport", Perms.MODERATOR, PermsFlag.NO_CONSOLE, "Teleports you on a specific location")
{
AddParameter(new TypeInt("x", true, "x coordinate"));
AddParameter(new TypeInt("y", true, "y coordinate"));
AddParameter(new TypeInt("z", true, "z coordinate"));
}
protected override void Execute(CallArgs args)
{
NitroxVector3 position = new(args.Get<int>(0), args.Get<int>(1), args.Get<int>(2));
args.Sender.Value.Teleport(position, Optional.Empty);
SendMessage(args.Sender, $"Teleported to {position}");
}
}
}

View File

@@ -0,0 +1,41 @@
using NitroxModel.DataStructures.GameLogic;
using NitroxServer.ConsoleCommands.Abstract;
using NitroxServer.ConsoleCommands.Abstract.Type;
using NitroxServer.GameLogic;
namespace NitroxServer.ConsoleCommands;
public class TimeCommand : Command
{
private readonly TimeKeeper timeKeeper;
public TimeCommand(TimeKeeper timeKeeper) : base("time", Perms.MODERATOR, "Changes the map time")
{
AddParameter(new TypeString("day/night", false, "Time to change to"));
this.timeKeeper = timeKeeper;
}
protected override void Execute(CallArgs args)
{
string time = args.Get(0);
switch (time?.ToLower())
{
case "day":
timeKeeper.ChangeTime(StoryManager.TimeModification.DAY);
SendMessageToAllPlayers("Time set to day");
break;
case "night":
timeKeeper.ChangeTime(StoryManager.TimeModification.NIGHT);
SendMessageToAllPlayers("Time set to night");
break;
default:
timeKeeper.ChangeTime(StoryManager.TimeModification.SKIP);
SendMessageToAllPlayers("Skipped time");
break;
}
}
}

View File

@@ -0,0 +1,48 @@
using NitroxModel.DataStructures.GameLogic;
using NitroxModel.Packets;
using NitroxServer.ConsoleCommands.Abstract;
using NitroxServer.ConsoleCommands.Abstract.Type;
using NitroxServer.GameLogic;
namespace NitroxServer.ConsoleCommands
{
internal class UnmuteCommand : Command
{
private readonly PlayerManager playerManager;
public UnmuteCommand(PlayerManager playerManager) : base("unmute", Perms.MODERATOR, "Removes a mute from a player")
{
this.playerManager = playerManager;
AddParameter(new TypePlayer("name", true, "Player to unmute"));
}
protected override void Execute(CallArgs args)
{
Player targetPlayer = args.Get<Player>(0);
if (args.SenderName == targetPlayer.Name)
{
SendMessage(args.Sender, "You can't unmute yourself");
return;
}
if (!args.IsConsole && targetPlayer.Permissions >= args.Sender.Value.Permissions)
{
SendMessage(args.Sender, $"You're not allowed to unmute {targetPlayer.Name}");
return;
}
if (!targetPlayer.PlayerContext.IsMuted)
{
SendMessage(args.Sender, $"{targetPlayer.Name} is already unmuted");
args.Sender.Value.SendPacket(new MutePlayer(targetPlayer.Id, targetPlayer.PlayerContext.IsMuted));
return;
}
targetPlayer.PlayerContext.IsMuted = false;
playerManager.SendPacketToAllPlayers(new MutePlayer(targetPlayer.Id, targetPlayer.PlayerContext.IsMuted));
SendMessage(targetPlayer, "You're no longer muted");
SendMessage(args.Sender, $"Unmuted {targetPlayer.Name}");
}
}
}

View File

@@ -0,0 +1,38 @@
using NitroxModel.DataStructures.GameLogic;
using NitroxModel.Helper;
using NitroxServer.ConsoleCommands.Abstract;
using NitroxServer.ConsoleCommands.Abstract.Type;
namespace NitroxServer.ConsoleCommands
{
internal class WarpCommand : Command
{
public WarpCommand() : base("warp", Perms.MODERATOR, "Allows to teleport players")
{
AddParameter(new TypePlayer("name", true, "Player to teleport to (or a player specified to teleport)"));
AddParameter(new TypePlayer("name", false, "The players name to teleport to"));
}
protected override void Execute(CallArgs args)
{
Player destination;
Player sender;
//Allows the console to teleport two players
if (args.IsValid(1))
{
destination = args.Get<Player>(1);
sender = args.Get<Player>(0);
}
else
{
Validate.IsFalse(args.IsConsole, "This command can't be used by CONSOLE");
destination = args.Get<Player>(0);
sender = args.Sender.Value;
}
sender.Teleport(destination.Position, destination.SubRootId);
SendMessage(sender, $"Teleported to {destination.Name}");
}
}
}

View File

@@ -0,0 +1,28 @@
using System.Collections.Generic;
using NitroxModel.DataStructures.GameLogic;
using NitroxServer.ConsoleCommands.Abstract;
using NitroxServer.ConsoleCommands.Abstract.Type;
namespace NitroxServer.ConsoleCommands
{
internal class WhisperCommand : Command
{
public override IEnumerable<string> Aliases { get; } = new[] { "w", "msg", "m" };
public WhisperCommand() : base("whisper", Perms.PLAYER, "Sends a private message to a player")
{
AddParameter(new TypePlayer("name", true, "The players name to message"));
AddParameter(new TypeString("msg", true, "The message to send"));
AllowedArgOverflow = true;
}
protected override void Execute(CallArgs args)
{
Player foundPlayer = args.Get<Player>(0);
string message = $"[{args.SenderName} -> YOU]: {args.GetTillEnd(1)}";
SendMessageToPlayer(foundPlayer, message);
}
}
}

View File

@@ -0,0 +1,31 @@
using System.Text;
using NitroxModel.DataStructures.GameLogic;
using NitroxServer.ConsoleCommands.Abstract;
using NitroxServer.ConsoleCommands.Abstract.Type;
namespace NitroxServer.ConsoleCommands
{
internal class WhoisCommand : Command
{
public WhoisCommand() : base("whois", Perms.PLAYER, "Shows informations over a player")
{
AddParameter(new TypePlayer("name", true, "The players name"));
}
protected override void Execute(CallArgs args)
{
Player player = args.Get<Player>(0);
StringBuilder builder = new($"==== {player.Name} ====\n");
builder.AppendLine($"ID: {player.Id}");
builder.AppendLine($"Role: {player.Permissions}");
builder.AppendLine($"Position: {player.Position.X}, {player.Position.Y}, {player.Position.Z}");
builder.AppendLine($"Oxygen: {player.Stats.Oxygen}/{player.Stats.MaxOxygen}");
builder.AppendLine($"Food: {player.Stats.Food}");
builder.AppendLine($"Water: {player.Stats.Water}");
builder.AppendLine($"Infection: {player.Stats.InfectionAmount}");
SendMessage(args.Sender, builder.ToString());
}
}
}