Files
Torch/Torch/Commands/TorchCommands.cs
2022-10-09 17:52:30 +07:00

396 lines
14 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Timers;
using NLog;
using Sandbox.Game.Multiplayer;
using Sandbox.ModAPI;
using Torch;
using Torch.API;
using Torch.API.Managers;
using Torch.API.Session;
using Torch.Commands.Permissions;
using Torch.Managers;
using Torch.Mod;
using Torch.Mod.Messages;
using VRage.Game;
using VRage.Game.ModAPI;
namespace Torch.Commands
{
public class TorchCommands : CommandModule
{
private static bool _restartPending = false;
private static bool _cancelRestart = false;
private bool _stopPending = false;
private bool _cancelStop = false;
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
[Command("whatsmyip")]
[Permission(MyPromoteLevel.None)]
public void GetIP(ulong steamId = 0)
{
if (steamId == 0)
{
steamId = Context.Player.SteamUserId;
}
else if (Context.Player.PromoteLevel <= MyPromoteLevel.Admin)
{
steamId = Context.Player.SteamUserId;
}
var state = new VRage.GameServices.MyP2PSessionState();
Sandbox.Engine.Networking.MyGameService.Peer2Peer.GetSessionState(steamId, ref state);
var ip = new IPAddress(BitConverter.GetBytes(state.RemoteIP).Reverse().ToArray());
Context.Respond($"Your IP is {ip}");
}
[Command("help", "Displays help for a command")]
[Permission(MyPromoteLevel.None)]
public void Help()
{
var commandManager = Context.Torch.CurrentSession?.Managers.GetManager<CommandManager>();
if (commandManager == null)
{
Context.Respond("Must have an attached session to list commands");
return;
}
commandManager.Commands.GetNode(Context.Args, out CommandTree.CommandNode node);
if (node != null)
{
var command = node.Command;
var children = node.Subcommands.Where(e => Context.Player == null || e.Value.Command?.MinimumPromoteLevel <= Context.Player.PromoteLevel).Select(x => x.Key);
var sb = new StringBuilder();
if (Context.Player != null && command?.MinimumPromoteLevel > Context.Player.PromoteLevel)
{
Context.Respond("You are not authorized to use this command.");
return;
}
if (command != null)
{
sb.AppendLine($"Syntax: {command.SyntaxHelp}");
sb.Append(command.HelpText);
}
if (node.Subcommands.Count() != 0)
sb.Append($"\nSubcommands: {string.Join(", ", children)}");
Context.Respond(sb.ToString());
}
else
{
Context.Respond(
$"Command not found. Use the {commandManager.Prefix}longhelp command and check your Comms menu for a full list of commands.");
}
}
[Command("longhelp", "Get verbose help. Will send a long message in a dialog window.")]
[Permission(MyPromoteLevel.None)]
public void LongHelp()
{
var commandManager = Context.Torch.CurrentSession?.Managers.GetManager<CommandManager>();
if (commandManager == null)
{
Context.Respond("Must have an attached session to list commands");
return;
}
commandManager.Commands.GetNode(Context.Args, out CommandTree.CommandNode node);
if (node != null)
{
var command = node.Command;
var children = node.Subcommands.Where(e => e.Value.Command?.MinimumPromoteLevel <= Context.Player.PromoteLevel).Select(x => x.Key);
var sb = new StringBuilder();
if (command != null && (Context.Player == null || command.MinimumPromoteLevel <= Context.Player.PromoteLevel))
{
sb.AppendLine($"Syntax: {command.SyntaxHelp}");
sb.Append(command.HelpText);
}
if (node.Subcommands.Count() != 0)
sb.Append($"\nSubcommands: {string.Join(", ", children)}");
Context.Respond(sb.ToString());
}
else
{
var sb = new StringBuilder();
foreach (var command in commandManager.Commands.WalkTree())
{
if (command.IsCommand && (Context.Player == null || command.Command.MinimumPromoteLevel <= Context.Player.PromoteLevel))
sb.AppendLine($"{command.Command.SyntaxHelp}\n {command.Command.HelpText}");
}
if (!Context.SentBySelf)
{
var m = new DialogMessage("Torch Help", subtitle: "Available commands:", content: sb.ToString());
ModCommunication.SendMessageTo(m, Context.Player.SteamUserId);
}
else
Context.Respond($"Available commands: {sb}");
}
}
[Command("ver", "Shows the running Torch version.")]
[Permission(MyPromoteLevel.None)]
public void Version()
{
var ver = Context.Torch.TorchVersion;
Context.Respond($"Torch version: {ver} SE version: {MyFinalBuildConstants.APP_VERSION}");
}
[Command("plugins", "Lists the currently loaded plugins.")]
[Permission(MyPromoteLevel.None)]
public void Plugins()
{
var plugins = Context.Torch.Managers.GetManager<PluginManager>()?.Plugins.Select(p => p.Value.Name) ??
Enumerable.Empty<string>();
Context.Respond($"Loaded plugins: {string.Join(", ", plugins)}");
}
[Command("stop", "Stops the server.")]
public void Stop(bool save = true, int countdownSeconds = 0)
{
if (_stopPending)
{
Context.Respond("A stop is already pending.");
return;
}
_stopPending = true;
if (!save)
Log.Warn("Stop without save is not possible. Feature is deprecated");
Task.Run(() =>
{
var countdown = StopCountdown(countdownSeconds, save).GetEnumerator();
while (countdown.MoveNext())
{
Thread.Sleep(1000);
}
});
/*Context.Respond("Stopping server.");
if (save)
DoSave()?.ContinueWith((a, mod) =>
{
ITorchBase torch = (mod as CommandModule)?.Context?.Torch;
Debug.Assert(torch != null);
torch.Stop();
}, this, TaskContinuationOptions.RunContinuationsAsynchronously);
else
Context.Torch.Stop();*/
}
[Command("restart", "Restarts the server.")]
public void Restart(int countdownSeconds = 10, bool save = true)
{
if (_restartPending)
{
Context.Respond("A restart is already pending.");
return;
}
_restartPending = true;
if (!save)
Log.Warn("Restart without save is not possible. Feature is deprecated");
Task.Run(() =>
{
var countdown = RestartCountdown(countdownSeconds, save).GetEnumerator();
while (countdown.MoveNext())
{
Thread.Sleep(1000);
}
});
}
[Command("notify", "Shows a message as a notification in the middle of all players' screens.")]
[Permission(MyPromoteLevel.Admin)]
public void Notify(string message, int disappearTimeMs = 2000, string font = "White")
{
ModCommunication.SendMessageToClients(new NotificationMessage(message, disappearTimeMs, font));
}
[Command("restart cancel", "Cancel a pending restart.")]
public void CancelRestart()
{
if (_restartPending)
_cancelRestart = true;
else
Context.Respond("A restart is not pending.");
}
[Command("stop cancel", "Cancel a pending stop.")]
public void CancelStop()
{
if (_stopPending)
_cancelStop = true;
else
Context.Respond("Server Stop is not pending.");
}
private IEnumerable StopCountdown(int countdown, bool save)
{
for (var i = countdown; i >= 0; i--)
{
if (_cancelStop)
{
Context.Torch.CurrentSession.Managers.GetManager<IChatManagerClient>()
.SendMessageAsSelf($"Stop cancelled.");
_stopPending = false;
_cancelStop = false;
yield break;
}
if (i >= 60 && i % 60 == 0)
{
Context.Torch.CurrentSession.Managers.GetManager<IChatManagerClient>()
.SendMessageAsSelf($"Stopping server in {i / 60} minute{Pluralize(i / 60)}.");
yield return null;
}
else if (i > 0)
{
if (i < 11)
Context.Torch.CurrentSession.Managers.GetManager<IChatManagerClient>()
.SendMessageAsSelf($"Stopping server in {i} second{Pluralize(i)}.");
yield return null;
}
else
{
if (save)
{
Log.Info("Saving game before stop.");
Context.Torch.CurrentSession.Managers.GetManager<IChatManagerClient>()
.SendMessageAsSelf($"Saving game before stop.");
}
Log.Info("Stopping server.");
Context.Torch.Invoke(() => Context.Torch.Stop());
yield break;
}
}
}
private IEnumerable RestartCountdown(int countdown, bool save)
{
for (var i = countdown; i >= 0; i--)
{
if (_cancelRestart)
{
Context.Torch.CurrentSession.Managers.GetManager<IChatManagerClient>()
.SendMessageAsSelf($"Restart cancelled.");
_restartPending = false;
_cancelRestart = false;
yield break;
}
if (i >= 60 && i % 60 == 0)
{
Context.Torch.CurrentSession.Managers.GetManager<IChatManagerClient>()
.SendMessageAsSelf($"Restarting server in {i / 60} minute{Pluralize(i / 60)}.");
yield return null;
}
else if (i > 0)
{
if (i < 11)
Context.Torch.CurrentSession.Managers.GetManager<IChatManagerClient>()
.SendMessageAsSelf($"Restarting server in {i} second{Pluralize(i)}.");
yield return null;
}
else
{
if (save)
{
Log.Info("Saving game before restart.");
Context.Torch.CurrentSession.Managers.GetManager<IChatManagerClient>()
.SendMessageAsSelf($"Saving game before restart.");
}
Log.Info("Restarting server.");
Context.Torch.Invoke(() => Context.Torch.Restart(save));
yield break;
}
}
}
private string Pluralize(int num)
{
return num == 1 ? "" : "s";
}
/// <summary>
/// Initializes a save of the game.
/// Caller id defaults to 0 in the case of triggering the chat command from server.
/// </summary>
[Command("save", "Saves the game.")]
public void Save()
{
Context.Respond("Saving game.");
DoSave();
}
private Task DoSave()
{
Task<GameSaveResult> task = Context.Torch.Save(300 * 1000, true);
if (task == null)
{
Context.Respond("Save failed, a save is already in progress");
return null;
}
return task.ContinueWith((taskCapture, state) =>
{
CommandContext context = (state as CommandModule)?.Context;
Debug.Assert(context != null);
switch (taskCapture.Result)
{
case GameSaveResult.Success:
context.Respond("Saved game.");
break;
case GameSaveResult.GameNotReady:
context.Respond("Save failed: Game was not ready.");
break;
case GameSaveResult.TimedOut:
context.Respond("Save failed: Save timed out.");
break;
case GameSaveResult.FailedToTakeSnapshot:
context.Respond("Save failed: unable to take snapshot");
break;
case GameSaveResult.FailedToSaveToDisk:
context.Respond("Save failed: unable to save to disk");
break;
case GameSaveResult.UnknownError:
context.Respond("Save failed: unknown reason");
break;
default:
throw new ArgumentOutOfRangeException();
}
}, this, TaskContinuationOptions.RunContinuationsAsynchronously);
}
[Command("uptime", "Check how long the server has been online.")]
public void Uptime()
{
Context.Respond(((ITorchServer)Context.Torch).ElapsedPlayTime.ToString());
}
}
}