Console Rework (#483)

* Extracted the logic from DebugConsole into a new ClientConsole class.

* ClientConsole moved to IoC system.
Verb system replaced with concmds.

* Shared Cleanup

* ClientChatConsole skeleton.

* DebugConsole and LobbyChat are now both subscribed to ClientChatConsole.

* Removed server chat commands.

* cleaned up server command sysyem.

* More chat handling, and aliasing.

* Nightly work on Say command.

* Fixes a bug in Maths.Angle.

* Chat channel colors moved to ClientChatConsole.

* Now Server commands are sent without opening DebugConsole.

* Emotes work.
Clientside chat formatting works.

* Fixed angle unit test.
This commit is contained in:
Acruid
2018-01-03 15:53:41 -08:00
committed by Pieter-Jan Briers
parent 1570ed1242
commit 6247ff4eff
58 changed files with 1112 additions and 917 deletions

View File

@@ -1,6 +1,7 @@
using System;
using System.Diagnostics;
using Lidgren.Network;
using SS14.Client.Console;
using SS14.Client.Interfaces;
using SS14.Client.Interfaces.Player;
using SS14.Client.Interfaces.State;
@@ -24,7 +25,7 @@ namespace SS14.Client
[Dependency]
private readonly IPlayerManager _playMan;
/// <inheritdoc />
public ushort DefaultPort { get; } = 1212;

View File

@@ -0,0 +1,150 @@
using System.Collections.Generic;
using OpenTK.Graphics;
using SS14.Client.Interfaces.GameObjects;
using SS14.Client.Interfaces.Player;
using SS14.Client.UserInterface.CustomControls;
using SS14.Shared.Console;
using SS14.Shared.GameObjects;
using SS14.Shared.IoC;
using SS14.Shared.Maths;
using SS14.Shared.Network.Messages;
namespace SS14.Client.Console
{
/// <summary>
/// Expands the console to support chat, channels, and emotes.
/// </summary>
public class ClientChatConsole : ClientConsole, IClientChatConsole
{
private const char ConCmdSlash = '/';
private const char OocAlias = '[';
private const char MeAlias = '@';
private readonly Dictionary<ChatChannel, Color4> _chatColors;
[Dependency]
private readonly IClientEntityManager _entityManager;
[Dependency]
private readonly IPlayerManager _players;
/// <summary>
/// Default Constructor.
/// </summary>
public ClientChatConsole()
{
_chatColors = new Dictionary<ChatChannel, Color4>
{
[ChatChannel.Default] = Color4.Gray,
[ChatChannel.Damage] = Color4.Red,
[ChatChannel.Radio] = new Color4(0, 100, 0, 255),
[ChatChannel.Server] = Color4.Blue,
[ChatChannel.Player] = new Color4(0, 128, 0, 255),
[ChatChannel.Local] = new Color4(0, 200, 0, 255),
[ChatChannel.OOC] = Color4.White,
[ChatChannel.Emote] = Color4.Cyan,
[ChatChannel.Visual] = Color4.Yellow,
};
}
/// <summary>
/// Initializes the console into a useable state.
/// </summary>
public override void Initialize()
{
base.Initialize();
_network.RegisterNetMessage<MsgChat>(MsgChat.NAME, (int) MsgChat.ID, msg => HandleChatMsg((MsgChat) msg));
}
/// <inheritdoc />
public void ParseChatMessage(Chatbox chatBox, string text)
{
ParseChatMessage(text, chatBox.DefaultChatFormat);
}
/// <inheritdoc />
public void ParseChatMessage(string text, string defaultFormat = null)
{
if (string.IsNullOrWhiteSpace(text))
return;
switch (text[0])
{
case ConCmdSlash:
{
// run locally
var conInput = text.Substring(1);
ProcessCommand(conInput);
break;
}
case OocAlias:
{
var conInput = text.Substring(2);
ProcessCommand($"ooc \"{conInput}\"");
break;
}
case MeAlias:
{
var conInput = text.Substring(2);
ProcessCommand($"me \"{conInput}\"");
break;
}
default:
{
var conInput = defaultFormat != null ? string.Format(defaultFormat, text) : text;
ProcessCommand(conInput);
break;
}
}
}
private void HandleChatMsg(MsgChat msg)
{
var channel = msg.Channel;
var text = msg.Text;
var index = msg.Index;
var entityId = msg.EntityId;
switch (channel)
{
case ChatChannel.Local:
case ChatChannel.Server:
case ChatChannel.OOC:
case ChatChannel.Radio:
{
string name;
if (index.HasValue && _players.SessionsDict.TryGetValue(index.Value, out var session))
{
name = session.Name;
}
else if (entityId.HasValue)
{
var ent = _entityManager.GetEntity(entityId.Value);
name = ent.Name ?? ent.ToString();
}
else
{
name = "<TERU-SAMA>";
}
text = $"[{channel}] {name}: {text}";
break;
}
}
AddLine(text, channel, GetChannelColor(channel));
if (entityId.HasValue && _entityManager.TryGetEntity(entityId.Value, out var a))
a.SendMessage(this, ComponentMessageType.EntitySaidSomething, channel, text);
}
private Color GetChannelColor(ChatChannel channel)
{
if (_chatColors.TryGetValue(channel, out var color))
return color;
return Color.White;
}
}
}

View File

@@ -0,0 +1,225 @@
using System;
using System.Collections.Generic;
using Lidgren.Network;
using OpenTK.Graphics;
using SS14.Client.Interfaces.Console;
using SS14.Shared.Console;
using SS14.Shared.Interfaces.Network;
using SS14.Shared.Interfaces.Reflection;
using SS14.Shared.IoC;
using SS14.Shared.Log;
using SS14.Shared.Network;
using SS14.Shared.Network.Messages;
using SS14.Shared.Reflection;
using SS14.Shared.Utility;
namespace SS14.Client.Console
{
public class AddStringArgs : EventArgs
{
public string Text { get; }
public Color4 Color { get; }
public ChatChannel Channel { get; }
public AddStringArgs(string text, Color4 color, ChatChannel channel)
{
Text = text;
Color = color;
Channel = channel;
}
}
public class ClientConsole : IClientConsole, IDebugConsole
{
private static readonly Color4 MsgColor = new Color4(65, 105, 225, 255);
[Dependency]
protected readonly IClientNetManager _network;
private readonly Dictionary<string, IConsoleCommand> _commands = new Dictionary<string, IConsoleCommand>();
private bool _requestedCommands;
/// <inheritdoc />
public virtual void Initialize()
{
_network.RegisterNetMessage<MsgConCmdReg>(MsgConCmdReg.NAME, (int)MsgConCmdReg.ID, HandleConCmdReg);
_network.RegisterNetMessage<MsgConCmdAck>(MsgConCmdAck.NAME, (int)MsgConCmdAck.ID, HandleConCmdAck);
_network.RegisterNetMessage<MsgConCmd>(MsgConCmd.NAME, (int)MsgConCmd.ID);
Reset();
}
/// <inheritdoc />
public virtual void Reset()
{
_commands.Clear();
_requestedCommands = false;
_network.Connected += OnNetworkConnected;
InitializeCommands();
SendServerCommandRequest();
}
private void OnNetworkConnected(object sender, NetChannelArgs netChannelArgs)
{
SendServerCommandRequest();
}
/// <inheritdoc />
public void Dispose() { }
public IReadOnlyDictionary<string, IConsoleCommand> Commands => _commands;
public void AddLine(string text, ChatChannel channel, Color4 color)
{
AddString?.Invoke(this, new AddStringArgs(text, color, channel));
}
public void Clear()
{
ClearText?.Invoke(this, EventArgs.Empty);
}
public event EventHandler<AddStringArgs> AddString;
public event EventHandler ClearText;
private void HandleConCmdAck(NetMessage message)
{
var msg = (MsgConCmdAck) message;
AddLine("< " + msg.Text, ChatChannel.Default, MsgColor);
}
private void HandleConCmdReg(NetMessage message)
{
var msg = (MsgConCmdReg) message;
foreach (var cmd in msg.Commands)
{
var commandName = cmd.Name;
// Do not do duplicate commands.
if (_commands.ContainsKey(commandName))
{
Logger.Warning($"Server sent console command {commandName}, but we already have one with the same name. Ignoring.");
continue;
}
var command = new ServerDummyCommand(commandName, cmd.Help, cmd.Description);
_commands[commandName] = command;
}
}
/// <summary>
/// Processes commands (chat messages starting with /)
/// </summary>
/// <param name="text">input text</param>
public void ProcessCommand(string text)
{
if(string.IsNullOrWhiteSpace(text))
return;
// echo the command locally
AddLine("> " + text, ChatChannel.Default, new Color4(255, 250, 240, 255));
//Commands are processed locally and then sent to the server to be processed there again.
var args = new List<string>();
CommandParsing.ParseArguments(text, args);
var commandname = args[0];
var forward = true;
if (_commands.ContainsKey(commandname))
{
var command = _commands[commandname];
args.RemoveAt(0);
forward = command.Execute(this, args.ToArray());
}
else if (!IoCManager.Resolve<IClientNetManager>().IsConnected)
{
AddLine("Unknown command: " + commandname, ChatChannel.Default, Color4.Red);
return;
}
if (forward)
SendServerConsoleCommand(text);
}
/// <summary>
/// Locates and registeres all local commands.
/// </summary>
private void InitializeCommands()
{
var manager = IoCManager.Resolve<IReflectionManager>();
foreach (var t in manager.GetAllChildren<IConsoleCommand>())
{
var instance = (IConsoleCommand) Activator.CreateInstance(t, null);
if (_commands.ContainsKey(instance.Command))
throw new Exception($"Command already registered: {instance.Command}");
_commands[instance.Command] = instance;
}
}
/// <summary>
/// Requests remote commands from server.
/// </summary>
public void SendServerCommandRequest()
{
if (_requestedCommands)
return;
var netMgr = IoCManager.Resolve<IClientNetManager>();
if (!netMgr.IsConnected)
return;
var msg = netMgr.CreateNetMessage<MsgConCmdReg>();
// empty message to request commands
netMgr.ClientSendMessage(msg, NetDeliveryMethod.ReliableUnordered);
_requestedCommands = true;
}
/// <summary>
/// Sends a command directly to the server.
/// </summary>
private void SendServerConsoleCommand(string text)
{
var netMgr = IoCManager.Resolve<IClientNetManager>();
if (netMgr == null || !netMgr.IsConnected)
return;
var msg = netMgr.CreateNetMessage<MsgConCmd>();
msg.Text = text;
netMgr.ClientSendMessage(msg, NetDeliveryMethod.ReliableUnordered);
}
}
/// <summary>
/// These dummies are made purely so list and help can list server-side commands.
/// </summary>
[Reflect(false)]
internal class ServerDummyCommand : IConsoleCommand
{
internal ServerDummyCommand(string command, string help, string description)
{
Command = command;
Help = help;
Description = description;
}
public string Command { get; }
public string Help { get; }
public string Description { get; }
// Always forward to server.
public bool Execute(IDebugConsole console, params string[] args)
{
return true;
}
}
}

View File

@@ -2,13 +2,13 @@
// Not some generic console command type.
// Couldn't think of a better name sorry.
using System;
using OpenTK.Graphics;
using SS14.Client.Interfaces.Console;
using SS14.Client.Interfaces.UserInterface;
using SS14.Shared.IoC;
using System;
using SS14.Shared;
using SS14.Shared.Console;
namespace SS14.Client.Console
namespace SS14.Client.Console.Commands
{
class ClearCommand : IConsoleCommand
{
@@ -35,7 +35,7 @@ namespace SS14.Client.Console
Random random = new Random();
for (int x = 0; x < 50; x++)
{
console.AddLine("filling...", colors[random.Next(0, colors.Length)]);
console.AddLine("filling...", ChatChannel.Default, colors[random.Next(0, colors.Length)]);
}
return false;
}

View File

@@ -1,22 +1,24 @@
using OpenTK.Graphics;
using SS14.Client.GameObjects;
using SS14.Client.Graphics.Render;
using SS14.Client.Interfaces.Console;
using SS14.Client.Interfaces.GameObjects;
using SS14.Shared.Interfaces.GameObjects;
using SS14.Shared.IoC;
using SS14.Shared.GameObjects;
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using OpenTK.Graphics;
using SS14.Client.GameObjects;
using SS14.Client.Graphics;
using SS14.Client.Graphics.Render;
using SS14.Client.Interfaces.Console;
using SS14.Client.Interfaces.GameObjects;
using SS14.Client.Interfaces.State;
using SS14.Client.State.States;
using SS14.Shared;
using SS14.Shared.Console;
using SS14.Shared.ContentPack;
using SS14.Shared.GameObjects;
using SS14.Shared.Interfaces.GameObjects;
using SS14.Shared.IoC;
namespace SS14.Client.Console
namespace SS14.Client.Console.Commands
{
class DumpEntitiesCommand : IConsoleCommand
{
@@ -30,7 +32,7 @@ namespace SS14.Client.Console
foreach (IEntity e in entitymanager.GetEntities(new ComponentEntityQuery()))
{
console.AddLine($"entity {e.Uid}, {e.Prototype.Name}.", Color4.White);
console.AddLine($"entity {e.Uid}, {e.Prototype.Name}.", ChatChannel.Default, Color4.White);
}
return false;
@@ -53,7 +55,7 @@ namespace SS14.Client.Console
foreach (var component in components)
{
console.AddLine($"{component.Owner.Uid}: {component.GetType()}", Color4.White);
console.AddLine($"{component.Owner.Uid}: {component.GetType()}", ChatChannel.Default, Color4.White);
}
return false;
}
@@ -69,7 +71,7 @@ namespace SS14.Client.Console
{
if (args.Length < 1)
{
console.AddLine($"Not enough arguments.", Color4.Red);
console.AddLine($"Not enough arguments.", ChatChannel.Default, Color4.Red);
return false;
}
var componentFactory = IoCManager.Resolve<IComponentFactory>();
@@ -89,16 +91,16 @@ namespace SS14.Client.Console
}
message.Append($", NSE: {registration.NetworkSynchronizeExistence}, references:");
console.AddLine(message.ToString(), Color4.White);
console.AddLine(message.ToString(), ChatChannel.Default, Color4.White);
foreach (Type type in registration.References)
{
console.AddLine($" {type}", Color4.White);
console.AddLine($" {type}", ChatChannel.Default, Color4.White);
}
}
catch (UnknownComponentException)
{
console.AddLine($"No registration found for '{args[0]}'", Color4.Red);
console.AddLine($"No registration found for '{args[0]}'", ChatChannel.Default, Color4.Red);
}
return false;
@@ -131,18 +133,18 @@ List of valid keys: playerocclusion, occluderdebug, light, lightintermediate, co
{
if (args.Length == 0)
{
console.AddLine("No key specified.", Color4.Red);
console.AddLine("No key specified.", ChatChannel.Default, Color4.Red);
return false;
}
if (args.Length > 1)
{
console.AddLine("This command only takes one argument.", Color4.Red);
console.AddLine("This command only takes one argument.", ChatChannel.Default, Color4.Red);
return false;
}
var statemgr = IoCManager.Resolve<IStateManager>();
if (!(statemgr.CurrentState is GameScreen screen))
{
console.AddLine("Wrong game state active. Must be GameScreen", Color4.Red);
console.AddLine("Wrong game state active. Must be GameScreen", ChatChannel.Default, Color4.Red);
return false;
}
RenderImage target;
@@ -182,7 +184,7 @@ List of valid keys: playerocclusion, occluderdebug, light, lightintermediate, co
target = screen.ShadowIntermediate;
break;
default:
console.AddLine("Unknown key", Color4.Red);
console.AddLine("Unknown key", ChatChannel.Default, Color4.Red);
return false;
}
@@ -191,7 +193,7 @@ List of valid keys: playerocclusion, occluderdebug, light, lightintermediate, co
var timestamp = DateTime.Now.ToString("yyyyMMddTHHmmsszzz");
var filename = Path.GetFullPath(PathHelpers.ExecutableRelativeFile($"dumprt-{key}-{timestamp}.png"));
image.SaveToFile(filename);
console.AddLine($"Saved dump to {filename}!", Color4.Green);
console.AddLine($"Saved dump to {filename}!", ChatChannel.Default, Color4.Green);
}
return false;

View File

@@ -1,16 +1,10 @@
using OpenTK.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SS14.Client.Interfaces.Console;
using SS14.Shared.IoC;
using SS14.Client.Interfaces.UserInterface;
using SS14.Client.Interfaces.Network;
using SS14.Shared.Console;
using SS14.Shared.Interfaces.Network;
using SS14.Shared.IoC;
namespace SS14.Client.Console
namespace SS14.Client.Console.Commands
{
class HelpCommand : IConsoleCommand
{
@@ -23,7 +17,7 @@ namespace SS14.Client.Console
switch (args.Length)
{
case 0:
console.AddLine("To display help for a specific command, write 'help <command>'. To list all available commands, write 'list'.", Color4.White);
console.AddLine("To display help for a specific command, write 'help <command>'. To list all available commands, write 'list'.", ChatChannel.Default, Color4.White);
break;
case 1:
@@ -33,19 +27,19 @@ namespace SS14.Client.Console
if (!IoCManager.Resolve<IClientNetManager>().IsConnected)
{
// No server so nothing to respond with unknown command.
console.AddLine("Unknown command: " + commandname, Color4.Red);
console.AddLine("Unknown command: " + commandname, ChatChannel.Default, Color4.Red);
return false;
}
// TODO: Maybe have a server side help?
return false;
}
IConsoleCommand command = console.Commands[commandname];
console.AddLine(string.Format("{0} - {1}", command.Command, command.Description), Color4.White);
console.AddLine(command.Help, Color4.White);
console.AddLine(string.Format("{0} - {1}", command.Command, command.Description), ChatChannel.Default, Color4.White);
console.AddLine(command.Help, ChatChannel.Default, Color4.White);
break;
default:
console.AddLine("Invalid amount of arguments.", Color4.Red);
console.AddLine("Invalid amount of arguments.", ChatChannel.Default, Color4.Red);
break;
}
return false;
@@ -62,7 +56,7 @@ namespace SS14.Client.Console
{
foreach (IConsoleCommand command in console.Commands.Values)
{
console.AddLine(command.Command + ": " + command.Description, Color4.White);
console.AddLine(command.Command + ": " + command.Description, ChatChannel.Default, Color4.White);
}
return false;

View File

@@ -1,12 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SS14.Client.Interfaces.Console;
using SS14.Shared.IoC;
namespace SS14.Client.Console
namespace SS14.Client.Console.Commands
{
class QuitCommand : IConsoleCommand
{

View File

@@ -0,0 +1,23 @@
using System;
using SS14.Client.UserInterface.CustomControls;
namespace SS14.Client.Console
{
/// <summary>
/// Interface for a chat compatible console.
/// </summary>
internal interface IClientChatConsole : IClientConsole
{
/// <summary>
/// Parses a raw chat message the player has submitted.
/// </summary>
/// <param name="text">Raw unsanitized string the player submitted.</param>
/// <param name="defaultFormat"></param>
void ParseChatMessage(string text, string defaultFormat = null);
/// <summary>
/// Parses a raw chat message the player has submitted.
/// </summary>
void ParseChatMessage(Chatbox chatBox, string text);
}
}

View File

@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using SS14.Client.Interfaces.Console;
namespace SS14.Client.Console
{
internal interface IClientConsole : IDisposable
{
/// <summary>
/// Initializes the console into a useable state.
/// </summary>
void Initialize();
/// <summary>
/// Resets the console to a post-initialized state.
/// </summary>
void Reset();
event EventHandler<AddStringArgs> AddString;
event EventHandler ClearText;
IReadOnlyDictionary<string, IConsoleCommand> Commands { get; }
/// <summary>
/// Parses console commands (verbs).
/// </summary>
/// <param name="text"></param>
void ProcessCommand(string text);
void SendServerCommandRequest();
}
}

View File

@@ -20,6 +20,7 @@ using SS14.Shared.Prototypes;
using System;
using System.Diagnostics;
using System.IO;
using SS14.Client.Console;
using SS14.Shared.ContentPack;
using SS14.Shared.Interfaces;
using SS14.Shared.Interfaces.Network;
@@ -66,6 +67,8 @@ namespace SS14.Client
private readonly IPlacementManager _placementManager;
[Dependency]
private readonly IBaseClient _client;
[Dependency]
private readonly IClientChatConsole _console;
#endregion Fields
@@ -109,6 +112,7 @@ namespace SS14.Client
prototypeManager.LoadDirectory(@"Prototypes");
prototypeManager.Resync();
_networkManager.Initialize(false);
_console.Initialize();
_netGrapher.Initialize();
_userInterfaceManager.Initialize();
_mapManager.Initialize();

View File

@@ -21,6 +21,7 @@ using Vector2i = SS14.Shared.Maths.Vector2i;
using SS14.Shared.Map;
using Vector2 = SS14.Shared.Maths.Vector2;
using SS14.Client.Graphics.Utility;
using SS14.Shared.Console;
namespace SS14.Client.GameObjects
{
@@ -136,7 +137,7 @@ namespace SS14.Client.GameObjects
{
string text = list[1].ToString();
if (channel == ChatChannel.Ingame || channel == ChatChannel.Player ||
if (channel == ChatChannel.Local || channel == ChatChannel.Player ||
channel == ChatChannel.Radio)
{
(_speechBubble ?? (_speechBubble = new SpeechBubble(Owner.Name + Owner.Uid))).SetText(text);

View File

@@ -11,6 +11,7 @@ using System.Collections.Generic;
using SS14.Shared.Maths;
using YamlDotNet.RepresentationModel;
using OpenTK.Graphics;
using SS14.Shared.Console;
using Vector2 = SS14.Shared.Maths.Vector2;
namespace SS14.Client.GameObjects
@@ -84,7 +85,7 @@ namespace SS14.Client.GameObjects
{
string text = list[1].ToString();
if (channel == ChatChannel.Ingame || channel == ChatChannel.Player ||
if (channel == ChatChannel.Local || channel == ChatChannel.Player ||
channel == ChatChannel.Radio)
{
(_speechBubble ?? (_speechBubble = new SpeechBubble(Owner.Name + Owner.Uid))).SetText(text);

View File

@@ -1,5 +1,4 @@
using SS14.Shared.Command;
using SS14.Shared.IoC;
using SS14.Shared.Console;
namespace SS14.Client.Interfaces.Console
{

View File

@@ -1,5 +1,7 @@
using OpenTK.Graphics;
using System.Collections.Generic;
using SS14.Shared;
using SS14.Shared.Console;
namespace SS14.Client.Interfaces.Console
{
@@ -10,7 +12,7 @@ namespace SS14.Client.Interfaces.Console
/// <summary>
/// Write a line with a specific color to the console window.
/// </summary>
void AddLine(string text, Color4 color);
void AddLine(string text, ChatChannel channel, Color4 color);
void Clear();
}

View File

@@ -23,10 +23,7 @@ namespace SS14.Client.Interfaces.Player
void Update(float frameTime);
void Shutdown();
void Destroy();
//TODO: Move to console system
void SendVerb(string verb, int uid);
void ApplyEffects(RenderImage image);
void ApplyPlayerStates(List<PlayerState> list);
}

View File

@@ -1,7 +1,6 @@
using System;
using SS14.Client.Graphics;
using SS14.Client.Graphics.Input;
using SS14.Client.Interfaces.Console;
using SS14.Client.UserInterface.Controls;
using System.Collections.Generic;
@@ -10,8 +9,7 @@ namespace SS14.Client.Interfaces.UserInterface
public interface IUserInterfaceManager
{
IDragDropInfo DragInfo { get; }
IDebugConsole Console { get; }
void Initialize();
void AddComponent(Control component);

View File

@@ -124,24 +124,7 @@ namespace SS14.Client.Player
UpdatePlayerList(list);
}
/// <summary>
/// Verb sender
/// If UID is 0, it means its a global verb.
/// </summary>
/// <param name="verb">the verb</param>
/// <param name="uid">a target entity's Uid</param>
public void SendVerb(string verb, int uid)
{
//TODO: Convert this to the ConCmd system.
var message = _network.CreateNetMessage<MsgSession>();
message.MsgType = PlayerSessionMessage.Verb;
message.Uid = uid;
message.Verb = verb;
_network.ClientSendMessage(message, NetDeliveryMethod.ReliableOrdered);
}
/// <summary>
/// Handles an incoming session NetMsg from the server.
/// </summary>

View File

@@ -1,4 +1,5 @@
using SS14.Client.GameObjects;
using SS14.Client.Console;
using SS14.Client.GameObjects;
using SS14.Client.Input;
using SS14.Client.Interfaces;
using SS14.Client.Interfaces.GameObjects;
@@ -105,6 +106,8 @@ namespace SS14.Client
IoCManager.Register<IComponentFactory, ClientComponentFactory>();
IoCManager.Register<IGameStateManager, GameStateManager>();
IoCManager.Register<IBaseClient, BaseClient>();
IoCManager.Register<IClientConsole, ClientChatConsole>();
IoCManager.Register<IClientChatConsole, ClientChatConsole>();
IoCManager.BuildGraph();
}

View File

@@ -152,6 +152,10 @@
</ItemGroup>
<ItemGroup>
<Compile Include="BaseClient.cs" />
<Compile Include="Console\ClientChatConsole.cs" />
<Compile Include="Console\ClientConsole.cs" />
<Compile Include="Console\IClientChatConsole.cs" />
<Compile Include="Console\IClientConsole.cs" />
<Compile Include="GameController.cs" />
<Compile Include="GameObjects\Components\Occluder\OccluderComponent.cs" />
<Compile Include="Interfaces\GameObjects\IClientEntityManager.cs" />
@@ -230,10 +234,10 @@
<Compile Include="UserInterface\Controls\Screen.cs" />
<Compile Include="UserInterface\Style.cs" />
<Compile Include="Utility\Rand.cs" />
<Compile Include="Console\ConsoleCommands.cs" />
<Compile Include="Console\HelpCommands.cs" />
<Compile Include="Console\QuitCommand.cs" />
<Compile Include="Console\Debug.cs" />
<Compile Include="Console\Commands\ConsoleCommands.cs" />
<Compile Include="Console\Commands\HelpCommands.cs" />
<Compile Include="Console\Commands\QuitCommand.cs" />
<Compile Include="Console\Commands\Debug.cs" />
<Compile Include="GameStates\GameStateManager.cs" />
<Compile Include="Helpers\GaussianBlur.cs" />
<Compile Include="Helpers\InterpolationPacket.cs" />
@@ -346,4 +350,4 @@
</ItemGroup>
<Copy SourceFiles="@(WinNatives)" DestinationFolder="$(OutputPath)" />
</Target>
</Project>
</Project>

View File

@@ -33,6 +33,7 @@ using SS14.Shared.Network;
using System;
using System.Collections.Generic;
using System.Linq;
using SS14.Client.Console;
using FrameEventArgs = SS14.Client.Graphics.FrameEventArgs;
using Vector2 = SS14.Shared.Maths.Vector2;
using Vector2i = SS14.Shared.Maths.Vector2i;
@@ -147,6 +148,11 @@ namespace SS14.Client.State.States
_cleanupList = new List<RenderImage>();
_cleanupSpriteList = new List<Sprite>();
//UI
var console = IoCManager.Resolve<IClientChatConsole>();
_gameChat.TextSubmitted += console.ParseChatMessage;
console.AddString += _gameChat.AddLine;
UserInterfaceManager.AddComponent(_uiScreen);
//Init serializer
@@ -241,7 +247,7 @@ namespace SS14.Client.State.States
blendingSettings.ColorDstFactor = BlendMode.Factor.OneMinusDstAlpha;
blendingSettings.AlphaSrcFactor = BlendMode.Factor.SrcAlpha;
blendingSettings.AlphaDstFactor = BlendMode.Factor.OneMinusSrcAlpha;
blendingSettings = blendingSettings;
_gasBatch.BlendingSettings = blendingSettings;
_decalBatch = new SpriteBatch();
blendingSettings = _decalBatch.BlendingSettings;
@@ -274,7 +280,7 @@ namespace SS14.Client.State.States
_gameChat.Alignment = Align.Right;
_gameChat.Size = new Vector2i(475, 175);
_gameChat.Resize += (sender, args) => { _gameChat.LocalPosition = new Vector2i(-10 + -_gameChat.Size.X, 10); };
_gameChat.TextSubmitted += ChatTextboxTextSubmitted;
_gameChat.DefaultChatFormat = "say \"{0}\"";
_uiScreen.AddControl(_gameChat);
}
@@ -480,6 +486,11 @@ namespace SS14.Client.State.States
/// <inheritdoc />
public override void Shutdown()
{
//UI
var console = IoCManager.Resolve<IClientChatConsole>();
_gameChat.TextSubmitted -= console.ParseChatMessage;
console.AddString -= _gameChat.AddLine;
UserInterfaceManager.RemoveComponent(_uiScreen);
IoCManager.Resolve<IPlayerManager>().LocalPlayer.DetachEntity();
@@ -535,7 +546,7 @@ namespace SS14.Client.State.States
}
if (e.Key == Keyboard.Key.F5)
{
PlayerManager.SendVerb("save", 0);
IoCManager.Resolve<IClientConsole>().ProcessCommand("save");
}
if (e.Key == Keyboard.Key.F6)
{
@@ -547,9 +558,7 @@ namespace SS14.Client.State.States
}
if (e.Key == Keyboard.Key.F8)
{
NetOutgoingMessage message = NetworkManager.CreateMessage();
message.Write((byte)NetMessages.ForceRestart);
NetworkManager.ClientSendMessage(message, NetDeliveryMethod.ReliableUnordered);
IoCManager.Resolve<IClientConsole>().ProcessCommand("restart");
}
if (e.Key == Keyboard.Key.Escape)
{
@@ -711,55 +720,13 @@ namespace SS14.Client.State.States
UserInterfaceManager.MouseLeft(e);
}
#endregion Mouse
#region Chat
private void HandleChatMessage(NetIncomingMessage msg)
{
var channel = (ChatChannel)msg.ReadByte();
string text = msg.ReadString();
int entityId = msg.ReadInt32();
string message;
switch (channel)
{
case ChatChannel.Ingame:
case ChatChannel.Server:
case ChatChannel.OOC:
case ChatChannel.Radio:
message = "[" + channel + "] " + text;
break;
default:
message = text;
break;
}
_gameChat.AddLine(message, channel);
if (entityId > 0 && _entityManager.TryGetEntity(entityId, out IEntity a))
{
a.SendMessage(this, ComponentMessageType.EntitySaidSomething, channel, text);
}
}
#endregion Input
private void ChatTextboxTextSubmitted(Chatbox chatbox, string text)
{
SendChatMessage(text);
}
#region Event Handlers
private void SendChatMessage(string text)
{
NetOutgoingMessage message = NetworkManager.CreateMessage();
message.Write((byte)NetMessages.ChatMessage);
message.Write((byte)ChatChannel.Player);
message.Write(text);
message.Write(-1);
NetworkManager.ClientSendMessage(message, NetDeliveryMethod.ReliableUnordered);
}
#endregion Chat
#endregion Input
#region Event Handlers
#region Messages
#region Messages
private void NetworkManagerMessageArrived(object sender, NetMessageArgs args)
{
@@ -788,15 +755,12 @@ namespace SS14.Client.State.States
case NetMessages.PlacementManagerMessage:
PlacementManager.HandleNetMessage(message);
break;
case NetMessages.ChatMessage:
HandleChatMessage(message);
break;
}
break;
}
}
#endregion Messages
#endregion Messages
private void OnPlayerMove(object sender, MoveEventArgs args)
{
@@ -811,9 +775,9 @@ namespace SS14.Client.State.States
RecalculateScene();
}
#endregion Event Handlers
#endregion Event Handlers
#region Lighting in order of call
#region Lighting in order of call
/**
* Calculate lights In player view
@@ -853,9 +817,9 @@ namespace SS14.Client.State.States
//Step 2 - Set up the render targets for the composite lighting.
RenderImage source = ScreenShadows;
#if !MACOS
#if !MACOS
source.Clear(Color.Black);
#else
#else
// For some insane reason, SFML does not clear the texture on MacOS.
// unless you call CopyToImage() on it, which we can't due to performance.
// This works though!
@@ -866,7 +830,7 @@ namespace SS14.Client.State.States
//͎̆̒̿ͤ̀͝͝H̙͇̽ͩ̓̚E̜̘̭̟͓͖̓̔̑̀͞͠ͅL̪̰̺̼̊̐̌P̸̴̴̙̻̻̗̯̤͎͓̿̊͌ͪ ̯̜͊̍̄M̩̻̺̬̗͕̬̈͗́ͯ̚̚͜Ȇ̟̜͙̙ ̅͐͐҉̱̫̼̱h̢̼͎͕̪͉͂̊ͤͣ͛͂̄ͯ͝͠t͎̺̼͙̰͓ͥ̏́ţ͕̼̱̲͈̹̾ͣͯͮ̄̅ͧͦ̚p̧̜̹͚̦ͧ̊̀̽ͫ̓̓ͣ̚͡ş̨̮̣̼̰̞̝̫͋̌ͬ͊͑ͣ:̷͇͚̲̻̩̞ͤ͐͞/̈́͋ͯ͂̀̅ͪ͑͞͏͖̮̯͍̟͚͓͎/̟̩̲͑̚ĩ̶̢̲̬̦͍͈̯͉̓̅͟.̦̭̲̭̂̓̿̈́̄͟ï͋͘҉̘̪̠̣̰m̖͎̮͆̀ͯ̑̃ͅg̢̝͉͔̽̃̀̂u̢̱̞̫̱̹̪̇͟r̸̯̞̹͓̥̮̮̝̹͌̀͌̈́͑.̪̦͕̞̥͕̩̎ͤ̇̉̒̓c̨̩̰̎̂ͬͤ̍̓̓ṍ̵͍͈̣̰m̛̱̥̘͙͈ͫͭ̒ͪͮ/̓͆̽̀͐̿͘҉̘̲͈̬̹̟M̡̺͍̜̺̘̰̼͂̎̃͞͝l̴̫̘̦̺̑ͪ̃͢ͅn̤̱̺̿͌ͨ͡U̧̢̜̞̝̒͒̐̄̊̽ͤͫͅL̡̺͉̠͖͚͉͚ͥͧ͋ͬ̀b̵̶̪̝̟̔ͪ̂̊A̧̧̝̭͖̭͍̬͑̀.̞̬͈́ͫ̍͘ͅp̶͎̠̱̍ͪ̆n̩͕̬̈ͪ̋ͅg̘̗̙̻͎̩̲͙͊ͨͭͣ͌̚̕
CluwneLib.drawRectangle(0, 0, source.Width, source.Height, Color.Black);
source.EndDrawing();
#endif
#endif
RenderImage desto = ShadowIntermediate;
RenderImage copy = null;
@@ -1229,9 +1193,9 @@ namespace SS14.Client.State.States
PlayerManager.ApplyEffects(ComposedSceneTarget);
}
#endregion Lighting in order of call
#endregion Lighting in order of call
#region Helper methods
#region Helper methods
private void RenderList(Vector2 topleft, Vector2 bottomright, IEnumerable<IRenderableComponent> renderables)
{
@@ -1325,9 +1289,9 @@ namespace SS14.Client.State.States
debugWallOccluders = !debugWallOccluders;
}
#endregion Helper methods
#endregion Helper methods
#region Nested type: ClickData
#region Nested type: ClickData
private struct ClickData
{
@@ -1341,7 +1305,7 @@ namespace SS14.Client.State.States
}
}
#endregion Nested type: ClickData
#endregion Nested type: ClickData
class GameScreenDebug
{

View File

@@ -10,6 +10,7 @@ using SS14.Shared.IoC;
using SS14.Shared.Maths;
using System;
using System.Collections.Generic;
using SS14.Client.Console;
namespace SS14.Client.State.States
{
@@ -124,6 +125,7 @@ namespace SS14.Client.State.States
_lobbyChat = new Chatbox(new Vector2i(780, 225));
_lobbyChat.Alignment = Align.HCenter | Align.VCenter;
_lobbyChat.DefaultChatFormat = "ooc \"{0}\"";
imgChatBg.AddControl(_lobbyChat);
var btnReady = new ImageButton();
@@ -147,6 +149,10 @@ namespace SS14.Client.State.States
_plyrMan = IoCManager.Resolve<IPlayerManager>();
_plyrMan.PlayerListUpdated += HandlePlayerList;
var console = IoCManager.Resolve<IClientChatConsole>();
_lobbyChat.TextSubmitted += console.ParseChatMessage;
console.AddString += _lobbyChat.AddLine;
UserInterfaceManager.AddComponent(_uiScreen);
UpdateInfo();
@@ -158,6 +164,10 @@ namespace SS14.Client.State.States
_plyrMan = IoCManager.Resolve<IPlayerManager>();
_plyrMan.PlayerListUpdated -= HandlePlayerList;
var console = IoCManager.Resolve<IClientChatConsole>();
_lobbyChat.TextSubmitted -= console.ParseChatMessage;
console.AddString -= _lobbyChat.AddLine;
UserInterfaceManager.RemoveComponent(_uiScreen);
}
@@ -271,7 +281,7 @@ namespace SS14.Client.State.States
private void _btnReady_Clicked(ImageButton sender)
{
IoCManager.Resolve<IPlayerManager>().SendVerb("joingame", 0);
IoCManager.Resolve<IClientConsole>().ProcessCommand("joingame");
}
private void _btnBack_Clicked(ImageButton sender)

View File

@@ -2,9 +2,10 @@
using System.Linq;
using System.Text.RegularExpressions;
using OpenTK.Graphics;
using SS14.Client.Console;
using SS14.Client.Graphics.Input;
using SS14.Client.UserInterface.Controls;
using SS14.Shared;
using SS14.Shared.Console;
using SS14.Shared.Maths;
namespace SS14.Client.UserInterface.CustomControls
@@ -15,10 +16,12 @@ namespace SS14.Client.UserInterface.CustomControls
private const int MaxLinePixelLength = 500;
private readonly Dictionary<ChatChannel, Color4> _chatColors;
private readonly IList<string> _inputHistory = new List<string>();
private readonly Textbox _input;
private readonly ScrollableContainer _historyBox;
private readonly ListPanel _chatHistoryList;
/// <summary>
/// To prevent the TextEntered from the key toggling chat being registered.
/// </summary>
@@ -34,9 +37,15 @@ namespace SS14.Client.UserInterface.CustomControls
/// </summary>
private string _inputTemp;
private readonly Textbox _input;
private readonly ScrollableContainer _historyBox;
private readonly ListPanel _chatHistoryList;
/// <summary>
/// Default formatting string for the ClientChatConsole.
/// </summary>
public string DefaultChatFormat { get; set; }
/// <summary>
/// Blacklists channels from being displayed.
/// </summary>
public List<ChatChannel> ChannelBlacklist { get; set; }
public override bool Focus
{
@@ -66,18 +75,9 @@ namespace SS14.Client.UserInterface.CustomControls
};
_input.OnSubmit += (sender, text) => input_OnSubmit(sender, text);
_chatColors = new Dictionary<ChatChannel, Color4>
ChannelBlacklist = new List<ChatChannel>()
{
[ChatChannel.Default] = Color4.Gray,
[ChatChannel.Damage] = Color4.Red,
[ChatChannel.Radio] = new Color4(0, 100, 0, 255),
[ChatChannel.Server] = Color4.Blue,
[ChatChannel.Player] = new Color4(0, 128, 0, 255),
[ChatChannel.Lobby] = Color4.White,
[ChatChannel.Ingame] = new Color4(0, 200, 0, 255),
[ChatChannel.OOC] = Color4.White,
[ChatChannel.Emote] = Color4.Cyan,
[ChatChannel.Visual] = Color4.Yellow,
ChatChannel.Default,
};
}
@@ -164,7 +164,6 @@ namespace SS14.Client.UserInterface.CustomControls
TextSubmitted = null;
_input.Dispose();
_chatColors.Clear();
}
public override void DoLayout()
@@ -257,10 +256,13 @@ namespace SS14.Client.UserInterface.CustomControls
return lineList;
}
public void AddLine(string message, ChatChannel channel)
public void AddLine(string message, ChatChannel channel, Color color)
{
if (Disposed) return;
if(ChannelBlacklist.Contains(channel))
return;
//TODO: LineHeight should be from the Font, not hard coded.
const int lineHeight = 12;
@@ -272,7 +274,7 @@ namespace SS14.Client.UserInterface.CustomControls
_chatHistoryList.AddControl(new Label(content, "CALIBRI")
{
Size = new Vector2i(ClientArea.Width - 10, lineHeight),
ForegroundColor = _chatColors[channel],
ForegroundColor = color,
});
}
@@ -312,5 +314,10 @@ namespace SS14.Client.UserInterface.CustomControls
Focus = false;
}
public void AddLine(object sender, AddStringArgs e)
{
AddLine(e.Text, e.Channel, e.Color);
}
}
}

View File

@@ -1,59 +1,27 @@
using System;
using System.Collections.Generic;
using Lidgren.Network;
using System.Collections.Generic;
using OpenTK.Graphics;
using SS14.Client.Console;
using SS14.Client.Graphics.Input;
using SS14.Client.Interfaces.Console;
using SS14.Client.UserInterface.Controls;
using SS14.Shared;
using SS14.Shared.Interfaces.Network;
using SS14.Shared.Interfaces.Reflection;
using SS14.Shared.Console;
using SS14.Shared.IoC;
using SS14.Shared.Network;
using SS14.Shared.Network.Messages;
using SS14.Shared.Reflection;
using SS14.Shared.Utility;
using Vector2i = SS14.Shared.Maths.Vector2i;
namespace SS14.Client.UserInterface.CustomControls
{
public class DebugConsole : ScrollableContainer, IDebugConsole
{
private readonly IClientConsole _console;
private readonly Textbox _txtInput;
private readonly ListPanel _historyList;
private readonly Dictionary<string, IConsoleCommand> _commands = new Dictionary<string, IConsoleCommand>();
private int last_y;
private bool sentCommandRequestToServer;
public override bool Visible
{
get => base.Visible;
set
{
base.Visible = value;
var netMgr = IoCManager.Resolve<IClientNetManager>();
if (value)
{
// Focus doesn't matter because UserInterfaceManager is hardcoded to go to console when it's visible.
// Though TextBox does like focus for the caret and passing KeyDown.
_txtInput.Focus = true;
netMgr.MessageArrived += NetMgr_MessageArrived;
if (netMgr.IsConnected && !sentCommandRequestToServer)
SendServerCommandRequest();
}
else
{
_txtInput.Focus = false;
netMgr.MessageArrived -= NetMgr_MessageArrived;
}
}
}
public DebugConsole(string uniqueName, Vector2i size)
private int _lastY;
public DebugConsole(Vector2i size)
: base(size)
{
_console = IoCManager.Resolve<IClientConsole>();
_txtInput = new Textbox(size.X)
{
ClearFocusOnSubmit = false,
@@ -68,22 +36,23 @@ namespace SS14.Client.UserInterface.CustomControls
BackgroundColor = new Color4(64, 64, 64, 200);
DrawBackground = true;
DrawBorder = true;
InitializeCommands();
_console.AddString += (sender, args) => AddLine(args.Text, args.Channel, args.Color);
_console.ClearText += (sender, args) => Clear();
}
public IReadOnlyDictionary<string, IConsoleCommand> Commands => _commands;
public IReadOnlyDictionary<string, IConsoleCommand> Commands => _console.Commands;
public void AddLine(string text, Color4 color)
public void AddLine(string text, ChatChannel channel, Color4 color)
{
var atBottom = ScrollbarV.Value >= ScrollbarV.Max;
var newLabel = new Label(text, "CALIBRI")
{
Position = new Vector2i(5, last_y),
Position = new Vector2i(5, _lastY),
ForegroundColor = color
};
last_y = newLabel.ClientArea.Bottom;
_lastY = newLabel.ClientArea.Bottom;
_historyList.AddControl(newLabel);
_historyList.DoLayout();
@@ -99,7 +68,7 @@ namespace SS14.Client.UserInterface.CustomControls
{
_historyList.DisposeAllChildren();
_historyList.DoLayout();
last_y = 0;
_lastY = 0;
ScrollbarV.Value = 0;
}
@@ -126,6 +95,7 @@ namespace SS14.Client.UserInterface.CustomControls
public override void Dispose()
{
_txtInput.Dispose();
_console.Dispose();
base.Dispose();
}
@@ -166,145 +136,9 @@ namespace SS14.Client.UserInterface.CustomControls
private void TxtInputOnSubmit(Textbox sender, string text)
{
AddLine("> " + text, new Color4(255, 250, 240, 255));
// debugConsole input is not prefixed with slash
if(!string.IsNullOrWhiteSpace(text))
ProcessCommand(text);
}
private void NetMgr_MessageArrived(object sender, NetMessageArgs e)
{
//Make sure we reset the position - we might receive this message after the gamestates.
if (e.RawMessage.Position > 0)
e.RawMessage.Position = 0;
if (e.RawMessage.MessageType != NetIncomingMessageType.Data)
return;
switch ((NetMessages) e.RawMessage.PeekByte())
{
case NetMessages.ConsoleCommandReply:
e.RawMessage.ReadByte();
AddLine("< " + e.RawMessage.ReadString(), new Color4(65, 105, 225, 255));
break;
case NetMessages.ConsoleCommandRegister:
e.RawMessage.ReadByte();
for (var amount = e.RawMessage.ReadUInt16(); amount > 0; amount--)
{
var commandName = e.RawMessage.ReadString();
// Do not do duplicate commands.
if (_commands.ContainsKey(commandName))
{
AddLine("Server sent console command {0}, but we already have one with the same name. Ignoring." + commandName, Color4.White);
continue;
}
var description = e.RawMessage.ReadString();
var help = e.RawMessage.ReadString();
var command = new ServerDummyCommand(commandName, help, description);
_commands[commandName] = command;
}
break;
}
//Again, make sure we reset the position - we might get it before the gamestate and then that would break.
e.RawMessage.Position = 0;
}
/// <summary>
/// Processes commands (chat messages starting with /)
/// </summary>
/// <param name="text">input text</param>
private void ProcessCommand(string text)
{
//Commands are processed locally and then sent to the server to be processed there again.
var args = new List<string>();
CommandParsing.ParseArguments(text, args);
var commandname = args[0];
var forward = true;
if (_commands.ContainsKey(commandname))
{
var command = _commands[commandname];
args.RemoveAt(0);
forward = command.Execute(this, args.ToArray());
}
else if (!IoCManager.Resolve<IClientNetManager>().IsConnected)
{
AddLine("Unknown command: " + commandname, Color4.Red);
return;
}
if (forward)
SendServerConsoleCommand(text);
}
private void InitializeCommands()
{
var manager = IoCManager.Resolve<IReflectionManager>();
foreach (var t in manager.GetAllChildren<IConsoleCommand>())
{
var instance = Activator.CreateInstance(t, null) as IConsoleCommand;
if (_commands.ContainsKey(instance.Command))
throw new Exception(string.Format("Command already registered: {0}", instance.Command));
_commands[instance.Command] = instance;
}
}
private void SendServerConsoleCommand(string text)
{
var netMgr = IoCManager.Resolve<IClientNetManager>();
if (netMgr != null && netMgr.IsConnected)
{
var outMsg = netMgr.CreateMessage();
outMsg.Write((byte) NetMessages.ConsoleCommand);
outMsg.Write(text);
netMgr.ClientSendMessage(outMsg, NetDeliveryMethod.ReliableUnordered);
}
}
private void SendServerCommandRequest()
{
var netMgr = IoCManager.Resolve<IClientNetManager>();
if (!netMgr.IsConnected)
return;
var msg = netMgr.CreateNetMessage<MsgConCmdReg>();
// empty message to request commands
netMgr.ClientSendMessage(msg, NetDeliveryMethod.ReliableUnordered);
sentCommandRequestToServer = true;
}
}
/// <summary>
/// These dummies are made purely so list and help can list server-side commands.
/// </summary>
[Reflect(false)]
internal class ServerDummyCommand : IConsoleCommand
{
internal ServerDummyCommand(string command, string help, string description)
{
Command = command;
Help = help;
Description = description;
}
public string Command { get; }
public string Help { get; }
public string Description { get; }
// Always forward to server.
public bool Execute(IDebugConsole console, params string[] args)
{
return true;
_console.ProcessCommand(text);
}
}
}

View File

@@ -52,7 +52,7 @@ namespace SS14.Client.UserInterface
public void Initialize()
{
_console = new DebugConsole("dbgConsole", new Vector2i((int) CluwneLib.Window.Viewport.Size.X, 400));
_console = new DebugConsole(new Vector2i((int) CluwneLib.Window.Viewport.Size.X, 400));
_console.Visible = false;
}

View File

@@ -35,6 +35,7 @@ using SS14.Shared.Prototypes;
using SS14.Shared.ServerEnums;
using SS14.Shared.Map;
using SS14.Server.Interfaces.Maps;
using SS14.Shared.Console;
namespace SS14.Server
{
@@ -193,19 +194,12 @@ namespace SS14.Server
netMan.RegisterNetMessage<MsgPlayerListReq>(MsgPlayerListReq.NAME, (int)MsgPlayerListReq.ID, HandlePlayerListReq);
netMan.RegisterNetMessage<MsgPlayerList>(MsgPlayerList.NAME, (int)MsgPlayerList.ID, HandleErrorMessage);
// Unused: NetMessages.LobbyChat
netMan.RegisterNetMessage<MsgChat>(MsgChat.NAME, (int)MsgChat.ID, message => IoCManager.Resolve<IChatManager>().HandleNetMessage((MsgChat)message));
netMan.RegisterNetMessage<MsgSession>(MsgSession.NAME, (int)MsgSession.ID, message => IoCManager.Resolve<IPlayerManager>().HandleNetworkMessage((MsgSession)message));
netMan.RegisterNetMessage<MsgConCmd>(MsgConCmd.NAME, (int)MsgConCmd.ID, message => IoCManager.Resolve<IClientConsoleHost>().ProcessCommand((MsgConCmd)message));
netMan.RegisterNetMessage<MsgConCmdAck>(MsgConCmdAck.NAME, (int)MsgConCmdAck.ID, HandleErrorMessage);
netMan.RegisterNetMessage<MsgConCmdReg>(MsgConCmdReg.NAME, (int)MsgConCmdReg.ID, message => IoCManager.Resolve<IClientConsoleHost>().HandleRegistrationRequest(message.MsgChannel));
netMan.RegisterNetMessage<MsgMapReq>(MsgMapReq.NAME, (int)MsgMapReq.ID, message => SendMap(message.MsgChannel));
netMan.RegisterNetMessage<MsgPlacement>(MsgPlacement.NAME, (int)MsgPlacement.ID, message => IoCManager.Resolve<IPlacementManager>().HandleNetMessage((MsgPlacement)message));
netMan.RegisterNetMessage<MsgUi>(MsgUi.NAME, (int)MsgUi.ID, HandleErrorMessage);
netMan.RegisterNetMessage<MsgJoinGame>(MsgJoinGame.NAME, (int)MsgJoinGame.ID, HandleErrorMessage);
netMan.RegisterNetMessage<MsgRestartReq>(MsgRestartReq.NAME, (int)MsgRestartReq.ID, message => Restart());
netMan.RegisterNetMessage<MsgEntity>(MsgEntity.NAME, (int)MsgEntity.ID, message => _entities.HandleEntityNetworkMessage((MsgEntity)message));
netMan.RegisterNetMessage<MsgAdmin>(MsgAdmin.NAME, (int)MsgAdmin.ID, message => HandleAdminMessage((MsgAdmin)message));
@@ -213,10 +207,6 @@ namespace SS14.Server
netMan.RegisterNetMessage<MsgStateAck>(MsgStateAck.NAME, (int)MsgStateAck.ID, message => HandleStateAck((MsgStateAck)message));
netMan.RegisterNetMessage<MsgFullState>(MsgFullState.NAME, (int)MsgFullState.ID, message => HandleErrorMessage(message));
IoCManager.Resolve<IChatManager>().Initialize();
IoCManager.Resolve<IPlayerManager>().Initialize(this, MaxPlayers);
IoCManager.Resolve<IMapManager>().Initialize();
// Set up the VFS
_resources.Initialize();
@@ -236,6 +226,11 @@ namespace SS14.Server
// TODO: solve this properly.
Serializer.Initialize();
// Initialize Tier 2 services
IoCManager.Resolve<IChatManager>().Initialize();
IoCManager.Resolve<IPlayerManager>().Initialize(this, MaxPlayers);
IoCManager.Resolve<IMapManager>().Initialize();
// Call Init in game assemblies.
AssemblyLoader.BroadcastRunLevel(AssemblyLoader.RunLevel.Init);
@@ -539,9 +534,8 @@ namespace SS14.Server
if (_lastAnnounced != countdown.Seconds)
{
_lastAnnounced = countdown.Seconds;
IoCManager.Resolve<IChatManager>().SendChatMessage(ChatChannel.Server,
"Starting in " + _lastAnnounced + " seconds...",
"", 0);
IoCManager.Resolve<IChatManager>()
.DispatchMessage(ChatChannel.Server, $"Starting in {_lastAnnounced} seconds...");
}
if (countdown.Seconds <= 0)
StartGame();

View File

@@ -1,278 +1,110 @@
using OpenTK;
using SS14.Server.Interfaces;
using SS14.Server.Interfaces.Chat;
using SS14.Server.Interfaces.GameObjects;
using SS14.Server.Interfaces.Player;
using SS14.Shared;
using SS14.Shared.GameObjects;
using SS14.Shared.Interfaces.GameObjects.Components;
using SS14.Shared.Interfaces.Reflection;
using SS14.Shared.IoC;
using SS14.Shared.Log;
using SS14.Shared.Reflection;
using SS14.Shared.Utility;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Collections.Generic;
using System.Xml.Serialization;
using SS14.Shared.ContentPack;
using SS14.Server.Interfaces.Chat;
using SS14.Server.Interfaces.Player;
using SS14.Shared.Console;
using SS14.Shared.Interfaces;
using SS14.Shared.Interfaces.Network;
using SS14.Shared.Network;
using SS14.Shared.IoC;
using SS14.Shared.Network.Messages;
using SS14.Shared.Players;
namespace SS14.Server.Chat
{
/// <summary>
/// Dispatches chat messages to clients.
/// </summary>
public class ChatManager : IChatManager
{
private const string DefaultPronoun = "their";
[Dependency]
private readonly IReflectionManager reflectionManager;
private readonly IServerNetManager _network;
[Dependency]
private readonly IServerEntityManager entityManager;
private readonly IResourceManager _resources;
private readonly Dictionary<string, Emote> _emotes = new Dictionary<string, Emote>();
private readonly Dictionary<string, IChatCommand> _commands = new Dictionary<string, IChatCommand>();
private readonly string _emotePath = PathHelpers.ExecutableRelativeFile("emotes.xml");
public IDictionary<string, IChatCommand> Commands => _commands;
#region IChatManager Members
/// <inheritdoc />
public void Initialize()
{
_network.RegisterNetMessage<MsgChat>(MsgChat.NAME, (int) MsgChat.ID);
LoadEmotes();
LoadCommands();
}
public void HandleNetMessage(MsgChat message)
/// <inheritdoc />
public void DispatchMessage(INetChannel client, ChatChannel channel, string text, PlayerIndex? index = null, int? entityUid = null)
{
var channel = message.Channel;
string text = message.Text;
var client = message.MsgChannel;
var session = IoCManager.Resolve<IPlayerManager>().GetSessionByChannel(message.MsgChannel);
var playerName = session.Name;
Logger.Debug("CHAT:: Channel: {0} :: Player: {1} :: Message: {2}", channel, playerName, text);
var entityId = IoCManager.Resolve<IPlayerManager>().GetSessionByChannel(message.MsgChannel).AttachedEntityUid;
bool hasChannelIdentifier = false;
if (channel != ChatChannel.Lobby)
channel = DetectChannel(text, out hasChannelIdentifier);
if (hasChannelIdentifier)
text = text.Substring(1);
text = text.Trim(); // Remove whitespace
if (text[0] == '/')
ProcessCommand(text, playerName, channel, entityId, client);
else if (text[0] == '*')
ProcessEmote(text, playerName, channel, entityId, message.MsgChannel);
else
SendChatMessage(channel, text, playerName, entityId);
var msg = BuildChatMessage(channel, text, index, entityUid);
_network.ServerSendMessage(msg, client);
}
public void SendChatMessage(ChatChannel channel, string text, string name, int? entityId)
/// <inheritdoc />
public void DispatchMessage(List<INetChannel> clients, ChatChannel channel, string text, PlayerIndex? index = null, int? entityUid = null)
{
MsgChat message = MakeNetChatMessage(channel, text, name, entityId);
var msg = BuildChatMessage(channel, text, index, entityUid);
_network.ServerSendToMany(msg, clients);
}
switch (channel)
/// <inheritdoc />
public void DispatchMessage(ChatChannel channel, string text, PlayerIndex? index = null, int? entityUid = null)
{
var msg = BuildChatMessage(channel, text, index, entityUid);
_network.ServerSendToAll(msg);
}
/// <inheritdoc />
public bool ExpandEmote(string input, IPlayerSession session, out string self, out string other)
{
if (_emotes.TryGetValue(input, out var emote))
{
case ChatChannel.Server:
case ChatChannel.OOC:
case ChatChannel.Radio:
case ChatChannel.Player:
case ChatChannel.Default:
IoCManager.Resolve<IServerNetManager>().ServerSendToAll(message);
break;
//TODO: Notify content, allow it to override expansion
// args: session, Emote
case ChatChannel.Damage:
case ChatChannel.Ingame:
case ChatChannel.Visual:
case ChatChannel.Emote:
SendToPlayersInRange(message, entityId);
break;
case ChatChannel.Lobby:
SendToLobby(message);
break;
self = string.Format(emote.SelfText);
other = string.Format(emote.OtherText, session.Name, DefaultPronoun);
return true;
}
self = string.Empty;
other = string.Empty;
return false;
}
public void SendPrivateMessage(INetChannel client, ChatChannel channel, string text, string name, int? entityId)
private MsgChat BuildChatMessage(ChatChannel channel, string text, PlayerIndex? index, int? entityUid)
{
MsgChat message = MakeNetChatMessage(channel, text, name, entityId);
IoCManager.Resolve<IServerNetManager>().ServerSendMessage(message, client);
}
private MsgChat MakeNetChatMessage(ChatChannel channel, string text, string name, int? entityId)
{
string fullmsg = text;
if (!string.IsNullOrEmpty(name) && channel == ChatChannel.Emote)
fullmsg = text; //Emote already has name in it probably...
else if (channel == ChatChannel.Ingame || channel == ChatChannel.OOC || channel == ChatChannel.Radio ||
channel == ChatChannel.Lobby)
fullmsg = name + ": " + text;
MsgChat message = IoCManager.Resolve<IServerNetManager>().CreateNetMessage<MsgChat>();
var message = _network.CreateNetMessage<MsgChat>();
message.Channel = channel;
message.Text = fullmsg;
message.EntityId = entityId;
message.Text = text;
message.Index = index;
message.EntityId = entityUid;
return message;
}
#endregion IChatManager Members
private ChatChannel DetectChannel(string message, out bool hasChannelIdentifier)
{
hasChannelIdentifier = false;
var channel = ChatChannel.Ingame;
switch (message[0])
{
case '[':
channel = ChatChannel.OOC;
hasChannelIdentifier = true;
break;
case ':':
channel = ChatChannel.Radio;
hasChannelIdentifier = true;
break;
case '@':
channel = ChatChannel.Emote;
hasChannelIdentifier = true;
break;
}
return channel;
}
private void LoadEmotes()
{
if (File.Exists(_emotePath))
{
using (var emoteFileStream = new FileStream(_emotePath, FileMode.Open, FileAccess.Read))
{
XmlSerializer serializer = new XmlSerializer(typeof(List<Emote>));
var emotes = (List<Emote>)serializer.Deserialize(emoteFileStream);
emoteFileStream.Close();
foreach (var emote in emotes)
{
_emotes.Add(emote.Command, emote);
}
}
}
else
{
using (var emoteFileStream = new FileStream(_emotePath, FileMode.OpenOrCreate, FileAccess.ReadWrite))
{
var emote = new Emote()
{
Command = "default",
OtherText = "{0} does something!",
SelfText = "You do something!"
};
_emotes.Add("default", emote);
XmlSerializer serializer = new XmlSerializer(typeof(List<Emote>));
serializer.Serialize(emoteFileStream, _emotes.Values.ToList());
emoteFileStream.Close();
}
}
}
// Load all command types.
private void LoadCommands()
{
foreach (Type t in reflectionManager.GetAllChildren<IChatCommand>())
{
IChatCommand instance = (IChatCommand)Activator.CreateInstance(t, null);
if (_commands.ContainsKey(instance.Command))
{
Logger.Error("Command has duplicate name: {0}", instance.Command);
continue;
}
_commands[instance.Command] = instance;
}
}
private void SendToPlayersInRange(NetMessage message, int? entityId)
{
//TODO: Move this to a real PVS system.
int withinRange = 512;
if (entityId == null)
return;
List<INetChannel> recipients = IoCManager.Resolve<IPlayerManager>()
.GetPlayersInRange(entityManager.GetEntity((int) entityId)
.GetComponent<ITransformComponent>().LocalPosition, withinRange)
.Select(p => p.ConnectedClient).ToList();
IoCManager.Resolve<IServerNetManager>().ServerSendToMany(message, recipients);
}
private void SendToLobby(NetMessage message)
{
//TODO: Move this to the Content Assembly.
List<INetChannel> recipients = IoCManager.Resolve<IPlayerManager>().GetPlayersInLobby().Select(p => p.ConnectedClient).ToList();
IoCManager.Resolve<IServerNetManager>().ServerSendToMany(message, recipients);
}
private void ProcessEmote(string text, string name, ChatChannel channel, int? entityId, INetChannel client)
{
if (entityId == null)
return; //No emotes from non-entities!
var args = new List<string>();
CommandParsing.ParseArguments(text, args);
if (_emotes.ContainsKey(args[0]))
{
// todo make a user-only channel that only the sender can see i.e. for emotes and game feedback ('you put the coins in the jar' or whatever)
var otherText = String.Format(_emotes[args[0]].OtherText, name, "his"); //todo pronouns, gender
SendChatMessage(ChatChannel.Emote, otherText, name, entityId);
}
else
{
//todo Bitch at the user
}
}
/// <summary>
/// Processes commands (chat messages starting with /)
/// </summary>
/// <param name="text">Text content.</param>
/// <param name="name">Player name that sent the chat text.</param>
/// <param name="channel">Channel message was received on.</param>
/// <param name="client">Client that sent the command.</param>
private void ProcessCommand(string text, string name, ChatChannel channel, int? entityId, INetChannel client)
{
List<string> args = new List<string>();
CommandParsing.ParseArguments(text.Substring(1), args); // Parse, but cut out the first character (/).
if (args.Count <= 0)
if (!_resources.TryContentFileRead(@"emotes.xml", out var emoteFileStream))
return;
string command = args[0];
if (!_commands.ContainsKey(command))
var serializer = new XmlSerializer(typeof(List<Emote>));
var emotes = (List<Emote>) serializer.Deserialize(emoteFileStream);
emoteFileStream.Close();
foreach (var emote in emotes)
{
string message = string.Format("Command '{0}' not found.", command);
SendPrivateMessage(client, ChatChannel.Default, message, "Server", null);
return;
_emotes.Add(emote.Command, emote);
}
_commands[command].Execute(this, client, args.ToArray());
}
}
public struct Emote
{
public string Command { get; set; }
public string SelfText { get; set; }
public string OtherText { get; set; }
// xml serializer requires this to be public
public struct Emote
{
public string Command { get; set; }
public string SelfText { get; set; }
public string OtherText { get; set; }
}
}
}

View File

@@ -1,28 +0,0 @@
using SS14.Server.Interfaces;
using SS14.Server.Interfaces.Chat;
using SS14.Shared.IoC;
using System;
using System.Text;
using SS14.Shared.Interfaces.Network;
using SS14.Shared.Network;
namespace SS14.Server.Chat.Commands
{
public class ListCommands : IChatCommand
{
public string Command => "list";
public string Description => "Lists all available commands.";
public string Help => "Outputs a list of all commands which are currently available to you, and a total command number.";
public void Execute(IChatManager manager, INetChannel client, params string[] args)
{
StringBuilder builder = new StringBuilder("Available commands:\n");
foreach (IChatCommand command in manager.Commands.Values)
{
builder.AppendFormat("{0}: {1}\n", command.Command, command.Description);
}
string message = builder.ToString().Trim(' ', '\n');
manager.SendPrivateMessage(client, Shared.ChatChannel.Default, message, "Server", null);
}
}
}

View File

@@ -1,21 +0,0 @@
using SS14.Server.Interfaces;
using SS14.Server.Interfaces.Chat;
using SS14.Shared;
using SS14.Shared.Interfaces.Network;
using SS14.Shared.IoC;
using SS14.Shared.Network;
namespace SS14.Server.Chat.Commands
{
public class Test : IChatCommand
{
public string Command => "test";
public string Description => "It's just a test bro.";
public string Help => "This thing tests stuff. If you got this message that means it worked. Hooray!";
public void Execute(IChatManager manager, INetChannel client, params string[] args)
{
IoCManager.Resolve<IChatManager>().SendChatMessage(ChatChannel.Server, "Test worked!", "retarded shitcode", null); // That retarded shitcode is chat code fyi.
}
}
}

View File

@@ -1,19 +1,14 @@
using SS14.Server.Interfaces;
using System;
using System.Collections.Generic;
using SS14.Server.Interfaces.ClientConsoleHost;
using SS14.Server.Interfaces.GameObjects;
using SS14.Server.Interfaces.Player;
using SS14.Shared;
using SS14.Shared.GameObjects;
using SS14.Shared.Interfaces.Network;
using SS14.Shared.Interfaces.Reflection;
using SS14.Shared.IoC;
using SS14.Shared.IoC.Exceptions;
using SS14.Shared.Utility;
using System.Collections.Generic;
using System.Reflection;
using System;
using SS14.Shared.Interfaces.Network;
using SS14.Shared.Network;
using SS14.Shared.Log;
using SS14.Shared.Network.Messages;
using SS14.Shared.Utility;
namespace SS14.Server.ClientConsoleHost
{
@@ -21,6 +16,12 @@ namespace SS14.Server.ClientConsoleHost
{
[Dependency]
private readonly IReflectionManager reflectionManager;
[Dependency]
private readonly IPlayerManager _players;
[Dependency]
private readonly IServerNetManager _net;
private readonly Dictionary<string, IClientCommand> availableCommands = new Dictionary<string, IClientCommand>();
public IDictionary<string, IClientCommand> AvailableCommands => availableCommands;
@@ -33,7 +34,7 @@ namespace SS14.Server.ClientConsoleHost
message.Commands = new MsgConCmdReg.Command[AvailableCommands.Count];
foreach (var command in AvailableCommands.Values)
{
message.Commands[counter++] = new MsgConCmdReg.Command()
message.Commands[counter++] = new MsgConCmdReg.Command
{
Name = command.Command,
Description = command.Description,
@@ -46,49 +47,52 @@ namespace SS14.Server.ClientConsoleHost
public void Initialize()
{
foreach (Type type in reflectionManager.GetAllChildren<IClientCommand>())
foreach (var type in reflectionManager.GetAllChildren<IClientCommand>())
{
var instance = Activator.CreateInstance(type, null) as IClientCommand;
if (AvailableCommands.TryGetValue(instance.Command, out IClientCommand duplicate))
{
if (AvailableCommands.TryGetValue(instance.Command, out var duplicate))
throw new InvalidImplementationException(instance.GetType(), typeof(IClientCommand), $"Command name already registered: {instance.Command}, previous: {duplicate.GetType()}");
}
AvailableCommands[instance.Command] = instance;
}
_net.RegisterNetMessage<MsgConCmd>(MsgConCmd.NAME, (int)MsgConCmd.ID, message => ProcessCommand((MsgConCmd)message));
_net.RegisterNetMessage<MsgConCmdAck>(MsgConCmdAck.NAME, (int)MsgConCmdAck.ID);
_net.RegisterNetMessage<MsgConCmdReg>(MsgConCmdReg.NAME, (int)MsgConCmdReg.ID, message => HandleRegistrationRequest(message.MsgChannel));
}
public void ProcessCommand(MsgConCmd message)
{
string text = message.Text;
INetChannel sender = message.MsgChannel;
var text = message.Text;
var sender = message.MsgChannel;
var session = _players.GetSessionByChannel(sender);
var args = new List<string>();
Logger.Info($"[{(int)session.Index}]{session.Name}:{text}");
CommandParsing.ParseArguments(text, args);
if (args.Count == 0)
{
return;
}
string cmd = args[0];
var cmd = args[0];
try
{
IClientCommand command = AvailableCommands[cmd];
args.RemoveAt(0);
command.Execute(this, sender, args.ToArray());
}
catch (KeyNotFoundException)
{
SendConsoleReply(string.Format("Unknown command: '{0}'", cmd), sender);
if (availableCommands.TryGetValue(cmd, out var command))
{
args.RemoveAt(0);
command.Execute(this, session, args.ToArray());
}
else
SendConsoleReply(sender, $"Unknown command: '{cmd}'");
}
catch (Exception e)
{
SendConsoleReply(string.Format("There was an error while executing the command: {0}", e.Message), sender);
SendConsoleReply(sender, $"There was an error while executing the command: {e.Message}");
}
}
public void SendConsoleReply(string text, INetChannel target)
public void SendConsoleReply(INetChannel target, string text)
{
var netMgr = IoCManager.Resolve<IServerNetManager>();
var replyMsg = netMgr.CreateNetMessage<MsgConCmdAck>();

View File

@@ -0,0 +1,135 @@
using System.Linq;
using SS14.Server.Interfaces.Chat;
using SS14.Server.Interfaces.ClientConsoleHost;
using SS14.Server.Interfaces.Player;
using SS14.Shared;
using SS14.Shared.Console;
using SS14.Shared.Interfaces.GameObjects;
using SS14.Shared.Interfaces.GameObjects.Components;
using SS14.Shared.IoC;
namespace SS14.Server.ClientConsoleHost.Commands
{
internal class SayCommand : IClientCommand
{
private const char RadioChar = ':'; // first char of first argument to designate radio messages
private const int VoiceRange = 7; // how far voice goes in world units
public string Command => "say";
public string Description => "Send chat messages to the local channel or a specified radio channel.";
public string Help => "say [<:channel>] <text>";
public void Execute(IClientConsoleHost host, IPlayerSession player, params string[] args)
{
if (player.Status != SessionStatus.InGame || !player.AttachedEntityUid.HasValue)
return;
if (args.Length < 1)
return;
var sessions = IoCManager.Resolve<IPlayerManager>();
var ents = IoCManager.Resolve<IEntityManager>();
var chat = IoCManager.Resolve<IChatManager>();
var message = args[0];
string text;
if (message[0] == RadioChar)
{
// all they sent was the channel
if (args.Length < 2)
return;
var channel = args[0];
var listArgs = args.ToList();
listArgs.RemoveAt(0);
text = string.Concat(listArgs);
//TODO: Parse channel and broadcast over radio.
}
else
{
text = string.Concat(args);
}
var pos = ents.GetEntity(player.AttachedEntityUid.Value).GetComponent<ITransformComponent>().LocalPosition;
var clients = sessions.GetPlayersInRange(pos, VoiceRange).Select(p => p.ConnectedClient);
chat.DispatchMessage(clients.ToList(), ChatChannel.Local, text, player.Index);
}
}
internal class WhisperCommand : IClientCommand
{
private const int WhisperRange = 1; // how far voice goes in world units
public string Command => "whisper";
public string Description => "Send chat messages to the local channel in a 1 meter radius.";
public string Help => "whisper <text>";
public void Execute(IClientConsoleHost host, IPlayerSession player, params string[] args)
{
if (player.Status != SessionStatus.InGame || !player.AttachedEntityUid.HasValue)
return;
var sessions = IoCManager.Resolve<IPlayerManager>();
var ents = IoCManager.Resolve<IEntityManager>();
var chat = IoCManager.Resolve<IChatManager>();
var pos = ents.GetEntity(player.AttachedEntityUid.Value).GetComponent<ITransformComponent>().LocalPosition;
var clients = sessions.GetPlayersInRange(pos, WhisperRange).Select(p => p.ConnectedClient);
chat.DispatchMessage(clients.ToList(), ChatChannel.Local, args[0], player.Index);
}
}
internal class MeCommand : IClientCommand
{
private const int VoiceRange = 7;
public string Command => "me";
public string Description => "Send third person chat messages to the local channel.";
public string Help => "me <text>";
public void Execute(IClientConsoleHost host, IPlayerSession player, params string[] args)
{
if (player.Status != SessionStatus.InGame || !player.AttachedEntityUid.HasValue)
return;
var sessions = IoCManager.Resolve<IPlayerManager>();
var ents = IoCManager.Resolve<IEntityManager>();
var chat = IoCManager.Resolve<IChatManager>();
if (chat.ExpandEmote(args[0], player, out var self, out var other))
{
//TODO: Dispatch in PVS range instead
var pos = ents.GetEntity(player.AttachedEntityUid.Value).GetComponent<ITransformComponent>().LocalPosition;
var clients = sessions.GetPlayersInRange(pos, VoiceRange).Where(p => p != player).Select(p => p.ConnectedClient);
chat.DispatchMessage(player.ConnectedClient, ChatChannel.Emote, self, player.Index);
chat.DispatchMessage(clients.ToList(), ChatChannel.Emote, other, player.Index);
}
else
{
//TODO: Dispatch in PVS range instead
var pos = ents.GetEntity(player.AttachedEntityUid.Value).GetComponent<ITransformComponent>().LocalPosition;
var clients = sessions.GetPlayersInRange(pos, VoiceRange).Select(p => p.ConnectedClient);
chat.DispatchMessage(clients.ToList(), ChatChannel.Emote, $"{player.Name} {args[0]}", player.Index);
}
}
}
internal class OocCommand : IClientCommand
{
public string Command => "ooc";
public string Description => "Send Out of Character chat messages.";
public string Help => "ooc <text>";
public void Execute(IClientConsoleHost host, IPlayerSession player, params string[] args)
{
var chat = IoCManager.Resolve<IChatManager>();
chat.DispatchMessage(ChatChannel.OOC, args[0], player.Index);
}
}
}

View File

@@ -1,15 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SS14.Server.Interfaces;
using SS14.Server.Interfaces.ClientConsoleHost;
using SS14.Server.Interfaces.ClientConsoleHost;
using SS14.Server.Interfaces.Player;
using SS14.Shared.IoC;
using SS14.Shared;
using SS14.Shared.Interfaces.Network;
using SS14.Shared.Network;
using SS14.Shared.IoC;
namespace SS14.Server.ClientConsoleHost.Commands
{
@@ -19,12 +11,12 @@ namespace SS14.Server.ClientConsoleHost.Commands
public string Description => "Fuck no idea what this does honestly.";
public string Help => "Nope! no clue!";
public void Execute(IClientConsoleHost host, INetChannel client, params string[] args)
public void Execute(IClientConsoleHost host, IPlayerSession player, params string[] args)
{
foreach (IPlayerSession playerfordrugs in IoCManager.Resolve<IPlayerManager>().GetAllPlayers())
foreach (var targetPlayer in IoCManager.Resolve<IPlayerManager>().GetAllPlayers())
{
playerfordrugs.AddPostProcessingEffect(PostProcessingEffectType.Acid, 60);
host.SendConsoleReply("Okay then.", client);
targetPlayer.AddPostProcessingEffect(PostProcessingEffectType.Acid, 60);
host.SendConsoleReply(player.ConnectedClient, "Okay then.");
}
}
}

View File

@@ -0,0 +1,20 @@
using System;
using SS14.Server.Interfaces.ClientConsoleHost;
using SS14.Server.Interfaces.Player;
using SS14.Shared;
namespace SS14.Server.ClientConsoleHost.Commands
{
class JoinGameCommand : IClientCommand
{
public string Command => "joingame";
public string Description => "Moves the player from the lobby to the game.";
public string Help => String.Empty;
public void Execute(IClientConsoleHost host, IPlayerSession player, params string[] args)
{
if (player.Status == SessionStatus.InLobby)
player.JoinGame();
}
}
}

View File

@@ -0,0 +1,24 @@
using System.Text;
using SS14.Server.Interfaces.ClientConsoleHost;
using SS14.Server.Interfaces.Player;
namespace SS14.Server.ClientConsoleHost.Commands
{
public class ListCommands : IClientCommand
{
public string Command => "sv_list";
public string Description => "Lists all available commands.";
public string Help => "Outputs a list of all commands which are currently available to you, and a total command number.";
public void Execute(IClientConsoleHost host, IPlayerSession player, params string[] args)
{
var builder = new StringBuilder("Available commands:\n");
foreach (var command in host.AvailableCommands.Values)
{
builder.AppendFormat("{0}: {1}\n", command.Command, command.Description);
}
var message = builder.ToString().Trim(' ', '\n');
host.SendConsoleReply(player.ConnectedClient, message);
}
}
}

View File

@@ -0,0 +1,32 @@
using SS14.Server.Interfaces;
using SS14.Server.Interfaces.ClientConsoleHost;
using SS14.Server.Interfaces.Player;
using SS14.Shared.IoC;
namespace SS14.Server.ClientConsoleHost.Commands
{
class SaveCommand : IClientCommand
{
public string Command => "save";
public string Description => "Saves the current map to disk.";
public string Help => "save";
public void Execute(IClientConsoleHost host, IPlayerSession player, params string[] args)
{
//TODO: Check permissions here.
IoCManager.Resolve<IBaseServer>().SaveGame();
}
}
class RestartCommand : IClientCommand
{
public string Command => "restart";
public string Description => "restarts the current round.";
public string Help => "restart";
public void Execute(IClientConsoleHost host, IPlayerSession player, params string[] args)
{
//TODO: Check permissions here.
IoCManager.Resolve<IBaseServer>().Restart();
}
}
}

View File

@@ -0,0 +1,21 @@
using SS14.Server.Interfaces.Chat;
using SS14.Server.Interfaces.ClientConsoleHost;
using SS14.Server.Interfaces.Player;
using SS14.Shared.Console;
using SS14.Shared.Interfaces.Network;
using SS14.Shared.IoC;
namespace SS14.Server.ClientConsoleHost.Commands
{
public class Test : IClientCommand
{
public string Command => "test";
public string Description => "It's just a test bro.";
public string Help => "This thing tests stuff. If you got this message that means it worked. Hooray!";
public void Execute(IClientConsoleHost host, IPlayerSession player, params string[] args)
{
IoCManager.Resolve<IChatManager>().DispatchMessage(player.ConnectedClient, ChatChannel.Server, "Test worked!");
}
}
}

View File

@@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SS14.Shared.Command;
using SS14.Shared.Console;
using SS14.Shared.Interfaces.Network;
using SS14.Shared.IoC;
using SS14.Shared.Network;

View File

@@ -1,19 +1,55 @@
using SS14.Shared;
using System.Collections.Generic;
using System.Collections.Generic;
using SS14.Server.Interfaces.Player;
using SS14.Shared.Console;
using SS14.Shared.Interfaces.Network;
using SS14.Shared.IoC;
using SS14.Shared.Network;
using SS14.Shared.Network.Messages;
using SS14.Shared.Players;
namespace SS14.Server.Interfaces.Chat
{
public interface IChatManager
{
void SendChatMessage(ChatChannel channel, string text, string name, int? entityID);
void SendPrivateMessage(INetChannel client, ChatChannel channel, string text, string name, int? entityId);
/// <summary>
/// Sets up the ChatManager into a usable state.
/// </summary>
void Initialize();
void HandleNetMessage(MsgChat message);
IDictionary<string, IChatCommand> Commands { get; }
/// <summary>
/// Sends a chat message to a single client.
/// </summary>
/// <param name="client">Clients to send the message to.</param>
/// <param name="channel">Channel that the chat is broadcast on.</param>
/// <param name="text">Text to broadcast.</param>
/// <param name="index">Optional PlayerIndex of the client that the message is bound to.</param>
/// <param name="entityUid">Optional entity Uid that the message is bound to.</param>
void DispatchMessage(INetChannel client, ChatChannel channel, string text, PlayerIndex? index = null, int? entityUid = null);
/// <summary>
/// Sends a chat message to multiple clients.
/// </summary>
/// <param name="clients"></param>
/// <param name="channel">Channel that the chat is broadcast on.</param>
/// <param name="text">Text to broadcast.</param>
/// <param name="index">Optional PlayerIndex of the client that the message is bound to.</param>
/// <param name="entityUid">Optional entity Uid that the message is bound to.</param>
void DispatchMessage(List<INetChannel> clients, ChatChannel channel, string text, PlayerIndex? index = null, int? entityUid = null);
/// <summary>
/// Sends a chat message to all connected clients.
/// </summary>
/// <param name="channel">Channel that the chat is broadcast on.</param>
/// <param name="text">Text to broadcast.</param>
/// <param name="index">Optional PlayerIndex of the client that the message is bound to.</param>
/// <param name="entityUid">Optional entity Uid that the message is bound to.</param>
void DispatchMessage(ChatChannel channel, string text, PlayerIndex? index = null, int? entityUid = null);
/// <summary>
/// Checks a string to see if it is an emote, and expands it to self/other chat.
/// </summary>
/// <param name="input">String to check.</param>
/// <param name="session">Player that the emote is acting on.</param>
/// <param name="self">First person emote text.</param>
/// <param name="other">Third person emote text.</param>
/// <returns>If the string was a valid emote.</returns>
bool ExpandEmote(string input, IPlayerSession session, out string self, out string other);
}
}

View File

@@ -1,16 +1,13 @@
using SS14.Shared.Command;
using SS14.Shared.IoC;
using System.Collections.Generic;
using SS14.Shared.Interfaces.Network;
using SS14.Shared.Network;
using SS14.Server.Interfaces.Player;
using SS14.Shared.Console;
namespace SS14.Server.Interfaces.ClientConsoleHost
{
/// <summary>
/// A command, executed from the debug console of a client.
/// A command, executed from the debug console of a client.
/// </summary>
public interface IClientCommand : ICommand
{
void Execute(IClientConsoleHost host, INetChannel client, params string[] args);
void Execute(IClientConsoleHost host, IPlayerSession player, params string[] args);
}
}

View File

@@ -6,10 +6,11 @@ namespace SS14.Server.Interfaces.ClientConsoleHost
{
public interface IClientConsoleHost
{
void Initialize();
IDictionary<string, IClientCommand> AvailableCommands { get; }
void Initialize();
void ProcessCommand(MsgConCmd message);
void SendConsoleReply(string text, INetChannel target);
void SendConsoleReply(INetChannel target, string text);
void HandleRegistrationRequest(INetChannel senderConnection);
}
}

View File

@@ -19,6 +19,7 @@ namespace SS14.Server.Interfaces.Player
PlayerIndex Index { get; }
void JoinLobby();
void JoinGame();
void SetName(string name);

View File

@@ -1,4 +1,4 @@
using SS14.Shared.Command;
using SS14.Shared.Console;
namespace SS14.Server.Interfaces.ServerConsole
{

View File

@@ -96,9 +96,6 @@ namespace SS14.Server.Player
var messageType = message.MsgType;
switch (messageType)
{
case PlayerSessionMessage.Verb:
HandleVerb(message);
break;
case PlayerSessionMessage.JoinLobby:
JoinLobby();
break;
@@ -153,33 +150,7 @@ namespace SS14.Server.Player
net.ServerSendMessage(message, ConnectedClient);
}
private void HandleVerb(MsgSession message)
{
DispatchVerb(message.Verb, message.Uid);
}
private void DispatchVerb(string verb, int uid)
{
//Handle global verbs
Logger.Log("Verb: " + verb + " from " + uid, LogLevel.Debug);
if (uid == 0)
{
switch (verb)
{
case "joingame":
JoinGame();
break;
case "toxins":
//Need debugging function to add more gas
case "save":
_playerManager.Server.SaveGame();
break;
}
}
}
private void SetAttachedEntityName()
{
if (Name != null && attachedEntity != null)
@@ -208,11 +179,7 @@ namespace SS14.Server.Player
{
if (ConnectedClient == null || Status == SessionStatus.InGame || _playerManager.RunLevel != RunLevel.Game)
return;
var net = IoCManager.Resolve<IServerNetManager>();
var message = net.CreateNetMessage<MsgJoinGame>();
net.ServerSendMessage(message, ConnectedClient);
Status = SessionStatus.InGame;
UpdatePlayerState();
}

View File

@@ -1,8 +1,8 @@
using SS14.Server.Interfaces;
using SS14.Server.Interfaces;
using SS14.Server.Interfaces.Chat;
using SS14.Server.Interfaces.GameMode;
using SS14.Server.Interfaces.Player;
using SS14.Shared;
using SS14.Shared.Console;
using SS14.Shared.IoC;
namespace SS14.Server.Round
@@ -55,14 +55,12 @@ namespace SS14.Server.Round
public virtual void PlayerLeft(IPlayerSession player)
{
IoCManager.Resolve<IChatManager>().SendChatMessage(ChatChannel.Server, "Gamemode: Player left!", null,
player.AttachedEntityUid);
IoCManager.Resolve<IChatManager>().DispatchMessage(ChatChannel.Server, "Gamemode: Player left!", player.Index);
}
public virtual void PlayerDied(IPlayerSession player)
{
IoCManager.Resolve<IChatManager>().SendChatMessage(ChatChannel.Server, "Gamemode: Player died!", null,
player.AttachedEntityUid);
IoCManager.Resolve<IChatManager>().DispatchMessage(ChatChannel.Server, "Gamemode: Player died!", player.Index);
}
public virtual void Begin()
@@ -82,4 +80,4 @@ namespace SS14.Server.Round
#endregion
}
}
}

View File

@@ -124,6 +124,9 @@
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Compile Include="ClientConsoleHost\Commands\ChatCommands.cs" />
<Compile Include="ClientConsoleHost\Commands\JoinGameCommand.cs" />
<Compile Include="ClientConsoleHost\Commands\SysCommands.cs" />
<Compile Include="GameObjects\Components\Container\Container.cs" />
<Compile Include="GameObjects\Components\Container\ContainerManagerComponent.cs" />
<Compile Include="Interfaces\GameObjects\IServerTransformComponent.cs" />
@@ -143,10 +146,6 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<None Include="app.config" />
<Content Include="emotes.xml">
<SubType>Code</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="..\Resources\EngineContentPack.zip">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -156,8 +155,8 @@
<None Include="packages.config" />
<Compile Include="CommandLineArgs.cs" />
<Compile Include="Chat\ChatManager.cs" />
<Compile Include="Chat\Commands\ListCommands.cs" />
<Compile Include="Chat\Commands\Test.cs" />
<Compile Include="ClientConsoleHost\Commands\ListCommands.cs" />
<Compile Include="ClientConsoleHost\Commands\Test.cs" />
<Compile Include="ClientConsoleHost\ClientConsoleHost.cs" />
<Compile Include="ClientConsoleHost\Commands\DrugsCommand.cs" />
<Compile Include="GameObjects\ServerComponentFactory.cs" />
@@ -228,4 +227,4 @@
</PostBuildEvent>
</PropertyGroup>
<Target Name="AfterBuild" DependsOnTargets="CopyResourcesFromShared" />
</Project>
</Project>

View File

@@ -1,46 +0,0 @@
namespace SS14.Shared
{
public enum ChatChannel
{
/// <summary>
/// Default, unspecified
/// </summary>
Default,
/// <summary>
/// Players in the lobby chat
/// </summary>
Lobby,
/// <summary>
/// Chat heard by players within earshot
/// </summary>
Ingame,
/// <summary>
/// Messages from the server
/// </summary>
Server,
/// <summary>
/// Damage messages
/// </summary>
Damage,
/// <summary>
/// Messages that are sent by the player directly
/// </summary>
Player,
/// <summary>
/// Radio messages
/// </summary>
Radio,
/// <summary>
/// Emotes
/// </summary>
Emote,
/// <summary>
/// Out-of-character channel
/// </summary>
OOC,
/// <summary>
/// Things the character can see
/// </summary>
Visual,
}
}

View File

@@ -0,0 +1,50 @@
namespace SS14.Shared.Console
{
public enum ChatChannel
{
/// <summary>
/// Default, unspecified
/// </summary>
Default = 0,
/// <summary>
/// Chat heard by players within earshot
/// </summary>
Local,
/// <summary>
/// Messages from the server
/// </summary>
Server,
/// <summary>
/// Damage messages
/// </summary>
Damage,
/// <summary>
/// Messages that are sent by the player directly
/// </summary>
Player,
/// <summary>
/// Radio messages
/// </summary>
Radio,
/// <summary>
/// Emotes
/// </summary>
Emote,
/// <summary>
/// Out-of-character channel
/// </summary>
OOC,
/// <summary>
/// Things the character can see
/// </summary>
Visual
}
}

View File

@@ -0,0 +1,34 @@
namespace SS14.Shared.Console
{
/// <summary>
/// Basic abstract to handle console commands.
/// Note that there is no Execute() function, this is due to chat & client commands needing a client,
/// While client-side and server console commands don't.
/// </summary>
public interface ICommand
{
/// <summary>
/// Name of the command.
/// </summary>
/// <value>
/// A string as identifier for this command.
/// </value>
string Command { get; }
/// <summary>
/// Short description of the command.
/// </summary>
/// <value>
/// String printed as short summary in the "help" command.
/// </value>
string Description { get; }
/// <summary>
/// Extended description for the command.
/// </summary>
/// <value>
/// String printed as summary when "help Command" is used.
/// </value>
string Help { get; }
}
}

View File

@@ -1,32 +0,0 @@
// Basic abstract to handle console commands.
// Note that there is no Execute() function, this is due to chat & client commands needing a client,
// While client-side and server console commands don't.
namespace SS14.Shared.Command
{
public interface ICommand
{
/// <summary>
/// Name of the command.
/// </summary>
/// <value>
/// A string as indentifier for this command.
/// </value>
string Command { get; }
/// <summary>
/// Short description of the command.
/// </summary>
/// <value>
/// String printed as short summary in the "help" command.
/// </value>
string Description { get; }
/// <summary>
/// Extended description for the command.
/// </summary>
/// <value>
/// String printed as summary when "help Command" is used.
/// </value>
string Help { get; }
}
}

View File

@@ -1,5 +1,4 @@
using SS14.Shared.Interfaces.Log;
using SS14.Shared.IoC;
using System;
namespace SS14.Shared.Log
@@ -40,10 +39,10 @@ namespace SS14.Shared.Log
string name = LogLevelToName(level);
ConsoleColor color = LogLevelToConsoleColor(level);
Console.ForegroundColor = color;
Console.Write(name);
Console.ResetColor();
Console.WriteLine(": {0}", message);
System.Console.ForegroundColor = color;
System.Console.Write(name);
System.Console.ResetColor();
System.Console.WriteLine(": {0}", message);
}
// If you make either of these methods public.

View File

@@ -44,7 +44,7 @@ namespace SS14.Shared.Maths
if (ang < 0.0f) // convert -PI > PI to 0 > 2PI
ang += 2 * (float) Math.PI;
return (Direction) Math.Floor((ang + Offset) / Segment);
return (Direction) (Math.Floor((ang + Offset) / Segment) % 8);
}
/// <summary>

View File

@@ -1,5 +1,7 @@
using Lidgren.Network;
using SS14.Shared.Console;
using SS14.Shared.Interfaces.Network;
using SS14.Shared.Players;
namespace SS14.Shared.Network.Messages
{
@@ -15,6 +17,7 @@ namespace SS14.Shared.Network.Messages
public ChatChannel Channel { get; set; }
public string Text { get; set; }
public PlayerIndex? Index { get; set; }
public int? EntityId { get; set; }
public override void ReadFromBuffer(NetIncomingMessage buffer)
@@ -22,6 +25,12 @@ namespace SS14.Shared.Network.Messages
Channel = (ChatChannel)buffer.ReadByte();
Text = buffer.ReadString();
var index = buffer.ReadInt32();
if (index == -1)
Index = null;
else
Index = new PlayerIndex(index);
var id = buffer.ReadInt32();
if (id == -1)
EntityId = null;
@@ -33,6 +42,12 @@ namespace SS14.Shared.Network.Messages
{
buffer.Write((byte)Channel);
buffer.Write(Text);
if (Index == null)
buffer.Write(-1);
else
buffer.Write(Index.Value);
if (EntityId == null)
buffer.Write(-1);
else

View File

@@ -1,25 +0,0 @@
using Lidgren.Network;
using SS14.Shared.Interfaces.Network;
namespace SS14.Shared.Network.Messages
{
public class MsgJoinGame : NetMessage
{
#region REQUIRED
public static readonly NetMessages ID = NetMessages.JoinGame;
public static readonly MsgGroups GROUP = MsgGroups.Command;
public static readonly string NAME = ID.ToString();
public MsgJoinGame(INetChannel channel) : base(NAME, GROUP, ID) { }
#endregion
public override void ReadFromBuffer(NetIncomingMessage buffer)
{
}
public override void WriteToBuffer(NetOutgoingMessage buffer)
{
}
}
}

View File

@@ -1,25 +0,0 @@
using Lidgren.Network;
using SS14.Shared.Interfaces.Network;
namespace SS14.Shared.Network.Messages
{
public class MsgRestartReq : NetMessage
{
#region REQUIRED
public static readonly NetMessages ID = NetMessages.ForceRestart;
public static readonly MsgGroups GROUP = MsgGroups.Command;
public static readonly string NAME = ID.ToString();
public MsgRestartReq(INetChannel channel) : base(NAME, GROUP, ID) { }
#endregion
public override void ReadFromBuffer(NetIncomingMessage buffer)
{
}
public override void WriteToBuffer(NetOutgoingMessage buffer)
{
}
}
}

View File

@@ -16,7 +16,6 @@ namespace SS14.Shared.Network.Messages
#endregion
public PlayerSessionMessage MsgType { get; set; }
public string Verb { get; set; }
public int Uid { get; set; }
public PostProcessingEffectType PpType { get; set; }
public float PpDuration { get; set; }
@@ -27,10 +26,6 @@ namespace SS14.Shared.Network.Messages
switch (MsgType)
{
case PlayerSessionMessage.Verb:
Verb = buffer.ReadString();
Uid = buffer.ReadInt32();
break;
case PlayerSessionMessage.AttachToEntity:
Uid = buffer.ReadInt32();
break;
@@ -46,10 +41,6 @@ namespace SS14.Shared.Network.Messages
buffer.Write((byte)MsgType);
switch (MsgType)
{
case PlayerSessionMessage.Verb:
buffer.Write(Verb);
buffer.Write(Uid);
break;
case PlayerSessionMessage.AttachToEntity:
buffer.Write(Uid);
break;

View File

@@ -16,7 +16,6 @@
StringTableEntry, // S>C An entry into the string table.
// Console Commands
LobbyChat, // C>S Does nothing atm, Obsolete?
ChatMessage, // C<>S Contains all of the chat messages.
PlayerSessionMessage, // C>S Tells (lol.) the server about state changes.
ConsoleCommand, // C>S Sends the server a console command.
@@ -30,8 +29,6 @@
// misc stuff that will prob be removed
PlacementManagerMessage,// S<>C Contains all placement messages.
PlayerUiMessage, // S>C Sends a user interface message.
JoinGame, // S>C Tells the client to join the game.
ForceRestart, // C>S Tells (lol.) the server to restart.
// entity stuff
EntityMessage, // S<>C Contains all entity messages.
@@ -120,7 +117,6 @@
public enum PlayerSessionMessage
{
AttachToEntity,
Verb,
JoinLobby,
AddPostProcessingEffect
}

View File

@@ -128,7 +128,7 @@
<Compile Include="ContentPack\ResourceManager.cs" />
<Compile Include="ContentPack\AssemblyTypeChecker.cs" />
<Compile Include="Interfaces\Configuration\IConfigurationManager.cs" />
<Compile Include="ConsoleCommand.cs" />
<Compile Include="Console\ICommand.cs" />
<Compile Include="EntityEvents.cs" />
<Compile Include="Exceptions\TypeArgumentException.cs" />
<Compile Include="GameObjects\Entity.cs" />
@@ -206,13 +206,11 @@
<Compile Include="Network\Messages\MsgConCmdAck.cs" />
<Compile Include="Network\Messages\MsgConCmdReg.cs" />
<Compile Include="Network\Messages\MsgEntity.cs" />
<Compile Include="Network\Messages\MsgJoinGame.cs" />
<Compile Include="Network\Messages\MsgMap.cs" />
<Compile Include="Network\Messages\MsgMapReq.cs" />
<Compile Include="Network\Messages\MsgPlacement.cs" />
<Compile Include="Network\Messages\MsgPlayerList.cs" />
<Compile Include="Network\Messages\MsgPlayerListReq.cs" />
<Compile Include="Network\Messages\MsgRestartReq.cs" />
<Compile Include="Network\Messages\MsgServerInfo.cs" />
<Compile Include="Network\Messages\MsgServerInfoReq.cs" />
<Compile Include="Network\Messages\MsgSession.cs" />
@@ -255,7 +253,7 @@
<Compile Include="Utility\CollectionExtensions.cs" />
<Compile Include="NetworkEnums.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ChatConfig.cs" />
<Compile Include="Console\ChatChannel.cs" />
<Compile Include="UIComponents.cs" />
<Compile Include="VectorEventArgs.cs" />
<Compile Include="IoC\IoCManager.cs" />
@@ -375,6 +373,10 @@
<ResourceRoot />
<Visible>False</Visible>
</Resources>
<Resources Include="..\Resources\emotes.xml">
<ResourceRoot />
<Visible>False</Visible>
</Resources>
</ItemGroup>
<Target Name="AfterBuild">
<RemoveDir Directories="$(OutputPath)Resources" />

View File

@@ -20,7 +20,9 @@ namespace SS14.UnitTesting.Shared.Maths
(-1, 0, Direction.West, System.Math.PI),
(-1, -1, Direction.SouthWest, -3 * System.Math.PI / 4.0),
(0, -1, Direction.South, -System.Math.PI / 2.0),
(1, -1, Direction.SouthEast, -System.Math.PI / 4.0)
(1, -1, Direction.SouthEast, -System.Math.PI / 4.0),
(0.92387953251128674f, -0.38268343236508978f, Direction.East, -System.Math.PI / 8.0)
};
[Test]