Clients can execute permitted console commands server-side now. The console commands have three different actions: the default action, one that's executed client-side when a client uses it, and one that's executed server-side when a clients requests it. If the client-side action is omitted, the client relays the command to the server as-is. If the third action is omitted, the server executes the default action.

This commit is contained in:
Joonas Rikkonen
2017-12-06 19:52:57 +02:00
parent 91a8e6d0c6
commit 9bc0931be5
8 changed files with 551 additions and 175 deletions

View File

@@ -105,7 +105,7 @@ namespace Barotrauma
if (PlayerInput.KeyHit(Keys.Enter))
{
ExecuteCommand(textBox.Text, game);
ExecuteCommand(textBox.Text);
textBox.Text = "";
}
}

View File

@@ -608,15 +608,17 @@ namespace Barotrauma.Networking
}
}
if (newPermissions != permissions)
{
SetPermissions(newPermissions, permittedConsoleCommands);
}
SetPermissions(newPermissions, permittedConsoleCommands);
}
private void SetPermissions(ClientPermissions newPermissions, List<string> permittedConsoleCommands)
{
if (newPermissions == permissions) return;
if (!(this.permittedConsoleCommands.Any(c => !permittedConsoleCommands.Contains(c)) ||
permittedConsoleCommands.Any(c => !this.permittedConsoleCommands.Contains(c))))
{
if (newPermissions == permissions) return;
}
GUIMessageBox.MessageBoxes.RemoveAll(mb => mb.UserData as string == "permissions");
string msg = "";
@@ -637,7 +639,9 @@ namespace Barotrauma.Networking
//TODO: display permitted console commands
}
permissions = newPermissions;
this.permittedConsoleCommands = new List<string>(permittedConsoleCommands);
new GUIMessageBox("Permissions changed", msg).UserData = "permissions";
GameMain.NetLobbyScreen.SubList.Enabled = Voting.AllowSubVoting || HasPermission(ClientPermissions.SelectSub);
@@ -1384,6 +1388,9 @@ namespace Barotrauma.Networking
msg.Write((byte)ClientPacketHeader.SERVER_COMMAND);
msg.Write((byte)ClientPermissions.ConsoleCommands);
msg.Write(command);
Vector2 cursorWorldPos = GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition);
msg.Write(cursorWorldPos.X);
msg.Write(cursorWorldPos.Y);
client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered);
}

View File

@@ -16,7 +16,7 @@ namespace Barotrauma
{
while (QueuedCommands.Count>0)
{
ExecuteCommand(QueuedCommands[0], GameMain.Instance);
ExecuteCommand(QueuedCommands[0]);
QueuedCommands.RemoveAt(0);
}
}

View File

@@ -31,22 +31,73 @@ namespace Barotrauma
{
public readonly string[] names;
public readonly string help;
private Action<string[]> onExecute;
private bool relayToServer;
/// <summary>
/// Executed when a client uses the command. If not set, the command is relayed to the server as-is.
/// </summary>
private Action<string[]> onClientExecute;
/// <summary>
/// Executed server-side when a client attempts to use the command.
/// </summary>
private Action<Client, Vector2, string[]> onClientRequestExecute;
public bool RelayToServer
{
get { return onClientExecute == null; }
}
/// <param name="name">The name of the command. Use | to give multiple names/aliases to the command.</param>
/// <param name="help">The text displayed when using the help command.</param>
/// <param name="onExecute">The default action when executing the command.</param>
/// <param name="onClientExecute">The action when a client attempts to execute the command. If null, the command is relayed to the server as-is.</param>
/// <param name="onClientRequestExecute">The server-side action when a client requests executing the command. If null, the default action is executed.</param>
public Command(string name, string help, Action<string[]> onExecute, Action<string[]> onClientExecute, Action<Client, Vector2, string[]> onClientRequestExecute)
{
names = name.Split('|');
this.help = help;
this.onExecute = onExecute;
this.onClientExecute = onClientExecute;
this.onClientRequestExecute = onClientRequestExecute;
}
/// <summary>
/// Use this constructor to create a command that executes the same action regardless of whether it's executed by a client or the server.
/// </summary>
public Command(string name, string help, Action<string[]> onExecute)
{
names = name.Split('|');
this.help = help;
this.onExecute = onExecute;
this.onClientExecute = onExecute;
}
public void Execute(string[] args)
{
onExecute(args);
}
public void ClientExecute(string[] args)
{
onClientExecute(args);
}
public void ServerExecuteOnClientRequest(Client client, Vector2 cursorWorldPos, string[] args)
{
if (onClientRequestExecute == null)
{
onExecute(args);
}
else
{
onClientRequestExecute(client, cursorWorldPos, args);
}
}
}
const int MaxMessages = 200;
@@ -102,6 +153,15 @@ namespace Barotrauma
NewMessage("- " + c.ID.ToString() + ": " + c.Name + ", " + c.Connection.RemoteEndPoint.Address.ToString(), Color.Cyan);
}
NewMessage("***************", Color.Cyan);
}, null,
(Client client, Vector2 cursorWorldPos, string[] args) =>
{
GameMain.Server.SendChatMessage("***************", client);
foreach (Client c in GameMain.Server.ConnectedClients)
{
GameMain.Server.SendChatMessage("- " + c.ID.ToString() + ": " + c.Name + ", " + c.Connection.RemoteEndPoint.Address.ToString(), client);
}
GameMain.Server.SendChatMessage("***************", client);
}));
@@ -112,145 +172,42 @@ namespace Barotrauma
commands.Add(new Command("spawn|spawncharacter", "spawn [creaturename] [near/inside/outside]: Spawn a creature at a random spawnpoint (use the second parameter to only select spawnpoints near/inside/outside the submarine).", (string[] args) =>
{
if (args.Length == 0) return;
Character spawnedCharacter = null;
Vector2 spawnPosition = Vector2.Zero;
WayPoint spawnPoint = null;
if (args.Length > 1)
string errorMsg;
SpawnCharacter(args, GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition), out errorMsg);
if (!string.IsNullOrWhiteSpace(errorMsg))
{
switch (args[1].ToLowerInvariant())
{
case "inside":
spawnPoint = WayPoint.GetRandom(SpawnType.Human, null, Submarine.MainSub);
break;
case "outside":
spawnPoint = WayPoint.GetRandom(SpawnType.Enemy);
break;
case "near":
case "close":
float closestDist = -1.0f;
foreach (WayPoint wp in WayPoint.WayPointList)
{
if (wp.Submarine != null) continue;
//don't spawn inside hulls
if (Hull.FindHull(wp.WorldPosition, null) != null) continue;
float dist = Vector2.Distance(wp.WorldPosition, GameMain.GameScreen.Cam.WorldViewCenter);
if (closestDist < 0.0f || dist < closestDist)
{
spawnPoint = wp;
closestDist = dist;
}
}
break;
case "cursor":
spawnPosition = GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition);
break;
default:
spawnPoint = WayPoint.GetRandom(args[0].ToLowerInvariant() == "human" ? SpawnType.Human : SpawnType.Enemy);
break;
}
ThrowError(errorMsg);
}
else
},
null,
(Client client, Vector2 cursorPos, string[] args) =>
{
string errorMsg;
SpawnCharacter(args, cursorPos, out errorMsg);
if (!string.IsNullOrWhiteSpace(errorMsg))
{
spawnPoint = WayPoint.GetRandom(args[0].ToLowerInvariant() == "human" ? SpawnType.Human : SpawnType.Enemy);
}
if (string.IsNullOrWhiteSpace(args[0])) return;
if (spawnPoint != null) spawnPosition = spawnPoint.WorldPosition;
if (args[0].ToLowerInvariant() == "human")
{
spawnedCharacter = Character.Create(Character.HumanConfigFile, spawnPosition);
#if CLIENT
if (GameMain.GameSession != null)
{
SinglePlayerCampaign mode = GameMain.GameSession.GameMode as SinglePlayerCampaign;
if (mode != null)
{
Character.Controlled = spawnedCharacter;
GameMain.GameSession.CrewManager.AddCharacter(Character.Controlled);
GameMain.GameSession.CrewManager.SelectCharacter(null, Character.Controlled);
}
}
#endif
}
else
{
List<string> characterFiles = GameMain.Config.SelectedContentPackage.GetFilesOfType(ContentType.Character);
foreach (string characterFile in characterFiles)
{
if (Path.GetFileNameWithoutExtension(characterFile).ToLowerInvariant() == args[0].ToLowerInvariant())
{
Character.Create(characterFile, spawnPosition);
return;
}
}
ThrowError("No character matching the name \"" + args[0] + "\" found in the selected content package.");
//attempt to open the config from the default path (the file may still be present even if it isn't included in the content package)
string configPath = "Content/Characters/"
+ args[0].First().ToString().ToUpper() + args[0].Substring(1)
+ "/" + args[0].ToLower() + ".xml";
Character.Create(configPath, spawnPosition);
ThrowError(errorMsg);
}
}));
commands.Add(new Command("spawnitem", "spawnitem [itemname] [cursor/inventory]: Spawn an item at the position of the cursor, in the inventory of the controlled character or at a random spawnpoint if the last parameter is omitted.", (string[] args) =>
commands.Add(new Command("spawnitem", "spawnitem [itemname] [cursor/inventory]: Spawn an item at the position of the cursor, in the inventory of the controlled character or at a random spawnpoint if the last parameter is omitted.",
(string[] args) =>
{
if (args.Length < 1) return;
Vector2? spawnPos = null;
Inventory spawnInventory = null;
int extraParams = 0;
switch (args.Last())
string errorMsg;
SpawnItem(args, GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition), out errorMsg);
if (!string.IsNullOrWhiteSpace(errorMsg))
{
case "cursor":
extraParams = 1;
spawnPos = GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition);
break;
case "inventory":
extraParams = 1;
spawnInventory = Character.Controlled == null ? null : Character.Controlled.Inventory;
break;
default:
extraParams = 0;
break;
ThrowError(errorMsg);
}
string itemName = string.Join(" ", args.Take(args.Length - extraParams)).ToLowerInvariant();
var itemPrefab = MapEntityPrefab.Find(itemName) as ItemPrefab;
if (itemPrefab == null)
},
null,
(Client client, Vector2 cursorWorldPos, string[] args) =>
{
string errorMsg;
SpawnItem(args, cursorWorldPos, out errorMsg);
if (!string.IsNullOrWhiteSpace(errorMsg))
{
ThrowError("Item \"" + itemName + "\" not found!");
return;
}
if (spawnPos == null && spawnInventory == null)
{
var wp = WayPoint.GetRandom(SpawnType.Human, null, Submarine.MainSub);
spawnPos = wp == null ? Vector2.Zero : wp.WorldPosition;
}
if (spawnPos != null)
{
Entity.Spawner.AddToSpawnQueue(itemPrefab, (Vector2)spawnPos);
}
else if (spawnInventory != null)
{
Entity.Spawner.AddToSpawnQueue(itemPrefab, spawnInventory);
ThrowError(errorMsg);
}
}));
@@ -258,12 +215,26 @@ namespace Barotrauma
{
HumanAIController.DisableCrewAI = true;
NewMessage("Crew AI disabled", Color.White);
},
null,
(Client client, Vector2 cursorWorldPos, string[] args) =>
{
HumanAIController.DisableCrewAI = true;
NewMessage("Crew AI disabled by \"" + client.Name + "\"", Color.White);
GameMain.Server.SendChatMessage("Crew AI disabled", client);
}));
commands.Add(new Command("enablecrewai", "enablecrewai: Enable the AI of the NPCs in the crew.", (string[] args) =>
{
HumanAIController.DisableCrewAI = false;
NewMessage("Crew AI enabled", Color.White);
},
null,
(Client client, Vector2 cursorWorldPos, string[] args) =>
{
HumanAIController.DisableCrewAI = false;
NewMessage("Crew AI enabled by \"" + client.Name + "\"", Color.White);
GameMain.Server.SendChatMessage("Crew AI enabled", client);
}));
commands.Add(new Command("autorestart", "autorestart [true/false]: Enable or disable round auto-restart.", (string[] args) =>
@@ -289,7 +260,7 @@ namespace Barotrauma
GameMain.NetLobbyScreen.LastUpdateID++;
}
NewMessage(GameMain.Server.AutoRestart ? "Automatic restart enabled." : "Automatic restart disabled.", Color.White);
}));
}, null, null));
commands.Add(new Command("autorestartinterval", "autorestartinterval [seconds]: Set how long the server waits between rounds before automatically starting a new one. If set to 0, autorestart is disabled.", (string[] args) =>
{
@@ -317,7 +288,7 @@ namespace Barotrauma
GameMain.NetLobbyScreen.LastUpdateID++;
}
}
}));
}, null, null));
commands.Add(new Command("autorestarttimer", "autorestarttimer [seconds]: Set the current autorestart countdown to the specified value.", (string[] args) =>
{
@@ -346,10 +317,12 @@ namespace Barotrauma
GameMain.NetLobbyScreen.LastUpdateID++;
}
}
}));
}, null, null));
commands.Add(new Command("giveperm", "giveperm [id]: Grants administrative permissions to the player with the specified client ID.", (string[] args) =>
{
//todo: allow client usage
if (GameMain.Server == null) return;
if (args.Length < 1) return;
@@ -367,20 +340,23 @@ namespace Barotrauma
ClientPermissions permission = ClientPermissions.None;
if (perm.ToLower() == "all")
{
permission = ClientPermissions.EndRound | ClientPermissions.Kick | ClientPermissions.Ban | ClientPermissions.SelectSub | ClientPermissions.SelectMode | ClientPermissions.ManageCampaign;
permission = ClientPermissions.EndRound | ClientPermissions.Kick | ClientPermissions.Ban |
ClientPermissions.SelectSub | ClientPermissions.SelectMode | ClientPermissions.ManageCampaign | ClientPermissions.ConsoleCommands;
}
else
{
Enum.TryParse<ClientPermissions>(perm, out permission);
Enum.TryParse(perm, out permission);
}
client.SetPermissions(client.Permissions | permission);
client.GivePermission(permission);
GameMain.Server.UpdateClientPermissions(client);
DebugConsole.NewMessage("Granted "+perm+" permissions to "+client.Name+".",Color.White);
NewMessage("Granted " + perm + " permissions to " + client.Name + ".", Color.White);
});
}));
commands.Add(new Command("revokeperm", "revokeperm [id]: Revokes administrative permissions to the player with the specified client ID.", (string[] args) =>
{
//todo: allow client usage
if (GameMain.Server == null) return;
if (args.Length < 1) return;
@@ -402,11 +378,11 @@ namespace Barotrauma
}
else
{
Enum.TryParse<ClientPermissions>(perm, out permission);
Enum.TryParse(perm, out permission);
}
client.SetPermissions(client.Permissions & ~permission);
client.RemovePermission(permission);
GameMain.Server.UpdateClientPermissions(client);
DebugConsole.NewMessage("Revoked " + perm + " permissions from " + client.Name + ".", Color.White);
NewMessage("Revoked " + perm + " permissions from " + client.Name + ".", Color.White);
});
}));
@@ -521,7 +497,7 @@ namespace Barotrauma
}
banDuration = parsedBanDuration;
}
var client = GameMain.Server.ConnectedClients.Find(c => c.Connection.RemoteEndPoint.Address.ToString() == args[0]);
if (client == null)
{
@@ -556,6 +532,28 @@ namespace Barotrauma
tpCharacter.Submarine = null;
tpCharacter.AnimController.SetPosition(ConvertUnits.ToSimUnits(cam.ScreenToWorld(PlayerInput.MousePosition)));
tpCharacter.AnimController.FindHull(cam.ScreenToWorld(PlayerInput.MousePosition), true);
},
null,
(Client client, Vector2 cursorWorldPos, string[] args) =>
{
Character tpCharacter = null;
if (args.Length == 0)
{
tpCharacter = client.Character;
}
else
{
tpCharacter = FindMatchingCharacter(args, false);
}
if (tpCharacter == null) return;
var cam = GameMain.GameScreen.Cam;
tpCharacter.AnimController.CurrentHull = null;
tpCharacter.Submarine = null;
tpCharacter.AnimController.SetPosition(ConvertUnits.ToSimUnits(cursorWorldPos));
tpCharacter.AnimController.FindHull(cursorWorldPos, true);
}));
commands.Add(new Command("godmode", "godmode: Toggle submarine godmode. Makes the main submarine invulnerable to damage.", (string[] args) =>
@@ -564,17 +562,26 @@ namespace Barotrauma
Submarine.MainSub.GodMode = !Submarine.MainSub.GodMode;
NewMessage(Submarine.MainSub.GodMode ? "Godmode on" : "Godmode off", Color.White);
},
null,
(Client client, Vector2 cursorWorldPos, string[] args) =>
{
if (Submarine.MainSub == null) return;
Submarine.MainSub.GodMode = !Submarine.MainSub.GodMode;
NewMessage((Submarine.MainSub.GodMode ? "Godmode turned on by \"" : "Godmode off by \"") + client.Name+"\"", Color.White);
GameMain.Server.SendChatMessage(Submarine.MainSub.GodMode ? "Godmode on" : "Godmode off", client);
}));
commands.Add(new Command("lockx", "lockx: Lock horizontal movement of the main submarine.", (string[] args) =>
{
Submarine.LockX = !Submarine.LockX;
}));
}, null, null));
commands.Add(new Command("locky", "loxky: Lock vertical movement of the main submarine.", (string[] args) =>
{
Submarine.LockY = !Submarine.LockY;
}));
}, null, null));
commands.Add(new Command("dumpids", "", (string[] args) =>
{
@@ -601,6 +608,27 @@ namespace Barotrauma
healedCharacter = FindMatchingCharacter(args);
}
if (healedCharacter != null)
{
healedCharacter.AddDamage(CauseOfDeath.Damage, -healedCharacter.MaxHealth, null);
healedCharacter.Oxygen = 100.0f;
healedCharacter.Bleeding = 0.0f;
healedCharacter.SetStun(0.0f, true);
}
},
null,
(Client client, Vector2 cursorWorldPos, string[] args) =>
{
Character healedCharacter = null;
if (args.Length == 0)
{
healedCharacter = client.Character;
}
else
{
healedCharacter = FindMatchingCharacter(args);
}
if (healedCharacter != null)
{
healedCharacter.AddDamage(CauseOfDeath.Damage, -healedCharacter.MaxHealth, null);
@@ -636,11 +664,44 @@ namespace Barotrauma
break;
}
}
},
null,
(Client client, Vector2 cursorWorldPos, string[] args) =>
{
Character revivedCharacter = null;
if (args.Length == 0)
{
revivedCharacter = client.Character;
}
else
{
revivedCharacter = FindMatchingCharacter(args);
}
if (revivedCharacter == null) return;
revivedCharacter.Revive(false);
if (GameMain.Server != null)
{
foreach (Client c in GameMain.Server.ConnectedClients)
{
if (c.Character != revivedCharacter) continue;
//clients stop controlling the character when it dies, force control back
GameMain.Server.SetClientCharacter(c, revivedCharacter);
break;
}
}
}));
commands.Add(new Command("freeze", "", (string[] args) =>
{
if (Character.Controlled != null) Character.Controlled.AnimController.Frozen = !Character.Controlled.AnimController.Frozen;
},
null,
(Client client, Vector2 cursorWorldPos, string[] args) =>
{
if (client.Character != null) client.Character.AnimController.Frozen = !client.Character.AnimController.Frozen;
}));
commands.Add(new Command("freecamera|freecam", "freecam: Detach the camera from the controlled character.", (string[] args) =>
@@ -676,6 +737,17 @@ namespace Barotrauma
if (args.Length > 2) float.TryParse(args[2], out damage);
if (args.Length > 3) float.TryParse(args[3], out structureDamage);
new Explosion(range, force, damage, structureDamage).Explode(explosionPos);
},
null,
(Client client, Vector2 cursorWorldPos, string[] args) =>
{
Vector2 explosionPos = cursorWorldPos;
float range = 500, force = 10, damage = 50, structureDamage = 10;
if (args.Length > 0) float.TryParse(args[0], out range);
if (args.Length > 1) float.TryParse(args[1], out force);
if (args.Length > 2) float.TryParse(args[2], out damage);
if (args.Length > 3) float.TryParse(args[3], out structureDamage);
new Explosion(range, force, damage, structureDamage).Explode(explosionPos);
}));
commands.Add(new Command("fixitems", "fixitems: Repairs all items and restores them to full condition.", (string[] args) =>
@@ -684,7 +756,7 @@ namespace Barotrauma
{
it.Condition = it.Prefab.Health;
}
}));
}, null, null));
commands.Add(new Command("fixhulls|fixwalls", "fixwalls/fixhulls: Fixes all walls.", (string[] args) =>
{
@@ -695,7 +767,7 @@ namespace Barotrauma
w.AddDamage(i, -100000.0f);
}
}
}));
}, null, null));
commands.Add(new Command("power", "power [temperature]: Immediately sets the temperature of the nuclear reactor to the specified value.", (string[] args) =>
{
@@ -714,7 +786,7 @@ namespace Barotrauma
{
reactorItem.CreateServerEvent(reactor);
}
}));
}, null, null));
commands.Add(new Command("oxygen|air", "oxygen/air: Replenishes the oxygen levels in every room to 100%.", (string[] args) =>
{
@@ -722,7 +794,7 @@ namespace Barotrauma
{
hull.OxygenPercentage = 100.0f;
}
}));
}, null, null));
commands.Add(new Command("killmonsters", "killmonsters: Immediately kills all AI-controlled enemies in the level.", (string[] args) =>
{
@@ -731,7 +803,7 @@ namespace Barotrauma
if (!(c.AIController is EnemyAIController)) continue;
c.AddDamage(CauseOfDeath.Damage, 10000.0f, null);
}
}));
}, null, null));
commands.Add(new Command("netstats", "netstats: Toggles the visibility of the network statistics UI.", (string[] args) =>
{
@@ -744,7 +816,6 @@ namespace Barotrauma
if (GameMain.Server == null) return;
int separatorIndex = Array.IndexOf(args, ";");
if (separatorIndex == -1 || args.Length < 3)
{
ThrowError("Invalid parameters. The command should be formatted as \"setclientcharacter [client] ; [character]\"");
@@ -753,8 +824,7 @@ namespace Barotrauma
string[] argsLeft = args.Take(separatorIndex).ToArray();
string[] argsRight = args.Skip(separatorIndex + 1).ToArray();
string clientName = String.Join(" ", argsLeft);
string clientName = string.Join(" ", argsLeft);
var client = GameMain.Server.ConnectedClients.Find(c => c.Name == clientName);
if (client == null)
@@ -762,6 +832,29 @@ namespace Barotrauma
ThrowError("Client \"" + clientName + "\" not found.");
}
var character = FindMatchingCharacter(argsRight, false);
GameMain.Server.SetClientCharacter(client, character);
},
null,
(Client senderClient, Vector2 cursorWorldPos, string[] args) =>
{
int separatorIndex = Array.IndexOf(args, ";");
if (separatorIndex == -1 || args.Length < 3)
{
GameMain.Server.SendChatMessage("Invalid parameters. The command should be formatted as \"setclientcharacter [client] ; [character]\"", senderClient);
return;
}
string[] argsLeft = args.Take(separatorIndex).ToArray();
string[] argsRight = args.Skip(separatorIndex + 1).ToArray();
string clientName = string.Join(" ", argsLeft);
var client = GameMain.Server.ConnectedClients.Find(c => c.Name == clientName);
if (client == null)
{
GameMain.Server.SendChatMessage("Client \"" + clientName + "\" not found.", senderClient);
}
var character = FindMatchingCharacter(argsRight, false);
GameMain.Server.SetClientCharacter(client, character);
}));
@@ -822,6 +915,69 @@ namespace Barotrauma
campaign.Map.SelectLocation(location);
NewMessage(location.Name + " selected.", Color.White);
}
},
(string[] args) =>
{
#if CLIENT
var campaign = GameMain.GameSession?.GameMode as CampaignMode;
if (campaign == null)
{
ThrowError("No campaign active!");
return;
}
if (args.Length == 0)
{
int i = 0;
foreach (LocationConnection connection in campaign.Map.CurrentLocation.Connections)
{
NewMessage(" " + i + ". " + connection.OtherLocation(campaign.Map.CurrentLocation).Name, Color.White);
i++;
}
ShowQuestionPrompt("Select a destination (0 - " + (campaign.Map.CurrentLocation.Connections.Count - 1) + "):", (string selectedDestination) =>
{
int destinationIndex = -1;
if (!int.TryParse(selectedDestination, out destinationIndex)) return;
if (destinationIndex < 0 || destinationIndex >= campaign.Map.CurrentLocation.Connections.Count)
{
NewMessage("Index out of bounds!", Color.Red);
return;
}
GameMain.Client.SendConsoleCommand("campaigndestination " + destinationIndex);
});
}
else
{
int destinationIndex = -1;
if (!int.TryParse(args[0], out destinationIndex)) return;
if (destinationIndex < 0 || destinationIndex >= campaign.Map.CurrentLocation.Connections.Count)
{
NewMessage("Index out of bounds!", Color.Red);
return;
}
GameMain.Client.SendConsoleCommand("campaigndestination " + destinationIndex);
}
#endif
},
(Client senderClient, Vector2 cursorWorldPos, string[] args) =>
{
var campaign = GameMain.GameSession?.GameMode as CampaignMode;
if (campaign == null)
{
GameMain.Server.SendChatMessage("No campaign active!", senderClient);
return;
}
int destinationIndex = -1;
if (args.Length < 1 || !int.TryParse(args[0], out destinationIndex)) return;
if (destinationIndex < 0 || destinationIndex >= campaign.Map.CurrentLocation.Connections.Count)
{
GameMain.Server.SendChatMessage("Index out of bounds!", senderClient);
return;
}
Location location = campaign.Map.CurrentLocation.Connections[destinationIndex].OtherLocation(campaign.Map.CurrentLocation);
campaign.Map.SelectLocation(location);
GameMain.Server.SendChatMessage(location.Name + " selected.", senderClient);
}));
#if DEBUG
@@ -856,7 +1012,7 @@ namespace Barotrauma
{
GameMain.Server.CreateEntityEvent(wall);
}
}));
}, null, null));
#endif
InitProjectSpecific();
@@ -944,7 +1100,7 @@ namespace Barotrauma
return Messages[selectedIndex].Text;
}
public static void ExecuteCommand(string command, GameMain game)
public static void ExecuteCommand(string command)
{
if (activeQuestionCallback != null)
{
@@ -967,14 +1123,25 @@ namespace Barotrauma
{
NewMessage(command, Color.White);
}
#if !DEBUG && CLIENT
if (GameMain.Client != null)
{
if (GameMain.Client.HasConsoleCommandPermission(splitCommand[0].ToLowerInvariant()))
{
GameMain.Client.SendConsoleCommand(command);
NewMessage("Server command: "+command, Color.White);
Command matchingCommand = commands.Find(c => c.names.Contains(splitCommand[0].ToLowerInvariant()));
//if the command is not defined client-side, we'll relay it anyway because it may be a custom command at the server's side
if (matchingCommand == null || matchingCommand.RelayToServer)
{
GameMain.Client.SendConsoleCommand(command);
}
else
{
matchingCommand.ClientExecute(splitCommand.Skip(1).ToArray());
}
NewMessage("Server command: " + command, Color.White);
return;
}
if (!IsCommandPermitted(splitCommand[0].ToLowerInvariant(), GameMain.Client))
@@ -1001,7 +1168,41 @@ namespace Barotrauma
ThrowError("Command \"" + splitCommand[0] + "\" not found.");
}
}
public static void ExecuteClientCommand(Client client, Vector2 cursorWorldPos, string command)
{
if (GameMain.Server == null) return;
if (string.IsNullOrWhiteSpace(command)) return;
if (!client.HasPermission(ClientPermissions.ConsoleCommands))
{
GameMain.Server.SendChatMessage("You are not permitted to use console commands!", client);
return;
}
string[] splitCommand = SplitCommand(command);
Command matchingCommand = commands.Find(c => c.names.Contains(splitCommand[0].ToLowerInvariant()));
if (matchingCommand != null && !client.PermittedConsoleCommands.Contains(matchingCommand))
{
GameMain.Server.SendChatMessage("You are not permitted to use the command\"" + matchingCommand.names[0] + "\"!", client);
return;
}
else if (matchingCommand == null)
{
GameMain.Server.SendChatMessage("Command \"" + splitCommand[0] + "\" not found.", client);
return;
}
try
{
matchingCommand.ServerExecuteOnClientRequest(client, cursorWorldPos, splitCommand.Skip(1).ToArray());
}
catch (Exception e)
{
ThrowError("Executing the command \"" + matchingCommand.names[0]+"\" by request from \""+client.Name+"\" failed.", e);
}
}
private static Character FindMatchingCharacter(string[] args, bool ignoreRemotePlayers = false)
{
if (args.Length == 0) return null;
@@ -1049,6 +1250,152 @@ namespace Barotrauma
return null;
}
private static void SpawnCharacter(string[] args, Vector2 cursorWorldPos, out string errorMsg)
{
errorMsg = "";
if (args.Length == 0) return;
Character spawnedCharacter = null;
Vector2 spawnPosition = Vector2.Zero;
WayPoint spawnPoint = null;
if (args.Length > 1)
{
switch (args[1].ToLowerInvariant())
{
case "inside":
spawnPoint = WayPoint.GetRandom(SpawnType.Human, null, Submarine.MainSub);
break;
case "outside":
spawnPoint = WayPoint.GetRandom(SpawnType.Enemy);
break;
case "near":
case "close":
float closestDist = -1.0f;
foreach (WayPoint wp in WayPoint.WayPointList)
{
if (wp.Submarine != null) continue;
//don't spawn inside hulls
if (Hull.FindHull(wp.WorldPosition, null) != null) continue;
float dist = Vector2.Distance(wp.WorldPosition, GameMain.GameScreen.Cam.WorldViewCenter);
if (closestDist < 0.0f || dist < closestDist)
{
spawnPoint = wp;
closestDist = dist;
}
}
break;
case "cursor":
spawnPosition = cursorWorldPos;
break;
default:
spawnPoint = WayPoint.GetRandom(args[0].ToLowerInvariant() == "human" ? SpawnType.Human : SpawnType.Enemy);
break;
}
}
else
{
spawnPoint = WayPoint.GetRandom(args[0].ToLowerInvariant() == "human" ? SpawnType.Human : SpawnType.Enemy);
}
if (string.IsNullOrWhiteSpace(args[0])) return;
if (spawnPoint != null) spawnPosition = spawnPoint.WorldPosition;
if (args[0].ToLowerInvariant() == "human")
{
spawnedCharacter = Character.Create(Character.HumanConfigFile, spawnPosition);
#if CLIENT
if (GameMain.GameSession != null)
{
SinglePlayerCampaign mode = GameMain.GameSession.GameMode as SinglePlayerCampaign;
if (mode != null)
{
Character.Controlled = spawnedCharacter;
GameMain.GameSession.CrewManager.AddCharacter(Character.Controlled);
GameMain.GameSession.CrewManager.SelectCharacter(null, Character.Controlled);
}
}
#endif
}
else
{
List<string> characterFiles = GameMain.Config.SelectedContentPackage.GetFilesOfType(ContentType.Character);
foreach (string characterFile in characterFiles)
{
if (Path.GetFileNameWithoutExtension(characterFile).ToLowerInvariant() == args[0].ToLowerInvariant())
{
Character.Create(characterFile, spawnPosition);
return;
}
}
errorMsg = "No character matching the name \"" + args[0] + "\" found in the selected content package.";
//attempt to open the config from the default path (the file may still be present even if it isn't included in the content package)
string configPath = "Content/Characters/"
+ args[0].First().ToString().ToUpper() + args[0].Substring(1)
+ "/" + args[0].ToLower() + ".xml";
Character.Create(configPath, spawnPosition);
}
}
private static void SpawnItem(string[] args, Vector2 cursorPos, out string errorMsg)
{
errorMsg = "";
if (args.Length < 1) return;
Vector2? spawnPos = null;
Inventory spawnInventory = null;
int extraParams = 0;
switch (args.Last())
{
case "cursor":
extraParams = 1;
spawnPos = cursorPos;
break;
case "inventory":
extraParams = 1;
spawnInventory = Character.Controlled == null ? null : Character.Controlled.Inventory;
break;
default:
extraParams = 0;
break;
}
string itemName = string.Join(" ", args.Take(args.Length - extraParams)).ToLowerInvariant();
var itemPrefab = MapEntityPrefab.Find(itemName) as ItemPrefab;
if (itemPrefab == null)
{
errorMsg = "Item \"" + itemName + "\" not found!";
return;
}
if (spawnPos == null && spawnInventory == null)
{
var wp = WayPoint.GetRandom(SpawnType.Human, null, Submarine.MainSub);
spawnPos = wp == null ? Vector2.Zero : wp.WorldPosition;
}
if (spawnPos != null)
{
Entity.Spawner.AddToSpawnQueue(itemPrefab, (Vector2)spawnPos);
}
else if (spawnInventory != null)
{
Entity.Spawner.AddToSpawnQueue(itemPrefab, spawnInventory);
}
}
public static void NewMessage(string msg, Color color)
{
if (string.IsNullOrEmpty((msg))) return;

View File

@@ -157,9 +157,10 @@ namespace Barotrauma.Networking
return rName;
}
public void SetPermissions(ClientPermissions permissions)
public void SetPermissions(ClientPermissions permissions, List<DebugConsole.Command> permittedConsoleCommands)
{
this.Permissions = permissions;
this.PermittedConsoleCommands = permittedConsoleCommands;
}
public void GivePermission(ClientPermissions permission)

View File

@@ -795,6 +795,11 @@ namespace Barotrauma.Networking
campaign.ServerRead(inc, sender);
}
break;
case ClientPermissions.ConsoleCommands:
string consoleCommand = inc.ReadString();
Vector2 clientCursorPos = new Vector2(inc.ReadSingle(), inc.ReadSingle());
DebugConsole.ExecuteClientCommand(sender, clientCursorPos, consoleCommand);
break;
}
inc.ReadPadBits();
@@ -853,7 +858,7 @@ namespace Barotrauma.Networking
outmsg.Write(GameStarted);
outmsg.Write(AllowSpectating);
outmsg.Write((byte)c.Permissions);
WritePermissions(outmsg, c);
}
private void ClientWriteIngame(Client c)
@@ -1622,6 +1627,12 @@ namespace Barotrauma.Networking
}
}
public void SendChatMessage(string txt, Client recipient)
{
ChatMessage msg = ChatMessage.Create("", txt, ChatMessageType.Server, null);
SendChatMessage(msg, recipient);
}
public void SendChatMessage(ChatMessage msg, Client recipient)
{
msg.NetStateID = recipient.ChatMsgQueue.Count > 0 ?
@@ -1928,6 +1939,15 @@ namespace Barotrauma.Networking
var msg = server.CreateMessage();
msg.Write((byte)ServerPacketHeader.PERMISSIONS);
WritePermissions(msg, client);
server.SendMessage(msg, client.Connection, NetDeliveryMethod.ReliableUnordered);
SaveClientPermissions();
}
private void WritePermissions(NetBuffer msg, Client client)
{
msg.Write((byte)client.Permissions);
if (client.Permissions.HasFlag(ClientPermissions.ConsoleCommands))
{
@@ -1940,10 +1960,6 @@ namespace Barotrauma.Networking
}
}
}
server.SendMessage(msg, client.Connection, NetDeliveryMethod.ReliableUnordered);
SaveClientPermissions();
}
public void SetClientCharacter(Client client, Character newCharacter)

View File

@@ -218,11 +218,11 @@ namespace Barotrauma.Networking
var savedPermissions = clientPermissions.Find(cp => cp.IP == newClient.Connection.RemoteEndPoint.Address.ToString());
if (savedPermissions != null)
{
newClient.SetPermissions(savedPermissions.Permissions);
newClient.SetPermissions(savedPermissions.Permissions, savedPermissions.PermittedCommands);
}
else
{
newClient.SetPermissions(ClientPermissions.None);
newClient.SetPermissions(ClientPermissions.None, new List<DebugConsole.Command>());
}
}

View File

@@ -364,14 +364,13 @@ namespace Barotrauma.Networking
}
}
new SavedClientPermission(clientName, clientIP, permissions, permittedCommands);
clientPermissions.Add(new SavedClientPermission(clientName, clientIP, permissions, permittedCommands));
}
}
/// <summary>
/// Method for loading old .txt client permission files to provide backwards compatibility
/// </summary>
/// <param name="file"></param>
private void LoadClientPermissionsOld(string file)
{
if (!File.Exists(file)) return;
@@ -407,6 +406,12 @@ namespace Barotrauma.Networking
public void SaveClientPermissions()
{
//delete old client permission file
if (File.Exists("Data/clientpermissions.txt"))
{
File.Delete("Data/clientpermissions.txt");
}
Log("Saving client permissions", ServerLog.MessageType.ServerMessage);
XDocument doc = new XDocument(new XElement("ClientPermissions"));