Overhaul the IRC implementation.
* Simplified UI plumbing. * Improves handling of errors and kicks. * Persists chat history between session. * Fixes leaks of the old widget tree when exiting. * A few small UI polish improvements.
This commit is contained in:
@@ -16,6 +16,7 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using OpenRA.Chat;
|
||||||
using OpenRA.FileSystem;
|
using OpenRA.FileSystem;
|
||||||
using OpenRA.Graphics;
|
using OpenRA.Graphics;
|
||||||
using OpenRA.Network;
|
using OpenRA.Network;
|
||||||
@@ -45,6 +46,8 @@ namespace OpenRA
|
|||||||
public static Sound Sound;
|
public static Sound Sound;
|
||||||
public static bool HasInputFocus = false;
|
public static bool HasInputFocus = false;
|
||||||
|
|
||||||
|
public static GlobalChat GlobalChat;
|
||||||
|
|
||||||
public static OrderManager JoinServer(string host, int port, string password, bool recordReplay = true)
|
public static OrderManager JoinServer(string host, int port, string password, bool recordReplay = true)
|
||||||
{
|
{
|
||||||
IConnection connection = new NetworkConnection(host, port);
|
IConnection connection = new NetworkConnection(host, port);
|
||||||
@@ -204,6 +207,7 @@ namespace OpenRA
|
|||||||
Log.AddChannel("sound", "sound.log");
|
Log.AddChannel("sound", "sound.log");
|
||||||
Log.AddChannel("graphics", "graphics.log");
|
Log.AddChannel("graphics", "graphics.log");
|
||||||
Log.AddChannel("geoip", "geoip.log");
|
Log.AddChannel("geoip", "geoip.log");
|
||||||
|
Log.AddChannel("irc", "irc.log");
|
||||||
|
|
||||||
if (Settings.Server.DiscoverNatDevices)
|
if (Settings.Server.DiscoverNatDevices)
|
||||||
UPnP.TryNatDiscovery();
|
UPnP.TryNatDiscovery();
|
||||||
@@ -237,6 +241,8 @@ namespace OpenRA
|
|||||||
|
|
||||||
Sound = new Sound(Settings.Server.Dedicated ? "Null" : Settings.Sound.Engine);
|
Sound = new Sound(Settings.Server.Dedicated ? "Null" : Settings.Sound.Engine);
|
||||||
|
|
||||||
|
GlobalChat = new GlobalChat();
|
||||||
|
|
||||||
Console.WriteLine("Available mods:");
|
Console.WriteLine("Available mods:");
|
||||||
foreach (var mod in ModMetadata.AllMods)
|
foreach (var mod in ModMetadata.AllMods)
|
||||||
Console.WriteLine("\t{0}: {1} ({2})", mod.Key, mod.Value.Title, mod.Value.Version);
|
Console.WriteLine("\t{0}: {1} ({2})", mod.Key, mod.Value.Title, mod.Value.Version);
|
||||||
@@ -688,6 +694,8 @@ namespace OpenRA
|
|||||||
worldRenderer.Dispose();
|
worldRenderer.Dispose();
|
||||||
ModData.Dispose();
|
ModData.Dispose();
|
||||||
ChromeProvider.Deinitialize();
|
ChromeProvider.Deinitialize();
|
||||||
|
|
||||||
|
GlobalChat.Dispose();
|
||||||
Sound.Dispose();
|
Sound.Dispose();
|
||||||
Renderer.Dispose();
|
Renderer.Dispose();
|
||||||
|
|
||||||
|
|||||||
371
OpenRA.Game/GlobalChat.cs
Normal file
371
OpenRA.Game/GlobalChat.cs
Normal file
@@ -0,0 +1,371 @@
|
|||||||
|
#region Copyright & License Information
|
||||||
|
/*
|
||||||
|
* Copyright 2007-2015 The OpenRA Developers (see AUTHORS)
|
||||||
|
* This file is part of OpenRA, which is free software. It is made
|
||||||
|
* available to you under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation. For more information,
|
||||||
|
* see COPYING.
|
||||||
|
*/
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using Meebey.SmartIrc4net;
|
||||||
|
using OpenRA;
|
||||||
|
using OpenRA.Primitives;
|
||||||
|
|
||||||
|
namespace OpenRA.Chat
|
||||||
|
{
|
||||||
|
public enum ChatConnectionStatus { Disconnected, Connecting, Connected, Disconnecting, Joined, Error }
|
||||||
|
public enum ChatMessageType { Message, Notification }
|
||||||
|
|
||||||
|
public sealed class ChatUser
|
||||||
|
{
|
||||||
|
public readonly string Name;
|
||||||
|
public bool IsOp;
|
||||||
|
public bool IsVoiced;
|
||||||
|
|
||||||
|
public ChatUser(string name, bool isOp, bool isVoice)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
IsOp = isOp;
|
||||||
|
IsVoiced = isVoice;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class ChatMessage
|
||||||
|
{
|
||||||
|
static long nextUID;
|
||||||
|
|
||||||
|
public readonly DateTime Time;
|
||||||
|
public readonly ChatMessageType Type;
|
||||||
|
public readonly string Nick;
|
||||||
|
public readonly string Message;
|
||||||
|
public readonly string UID;
|
||||||
|
|
||||||
|
public ChatMessage(DateTime time, ChatMessageType type, string nick, string message)
|
||||||
|
{
|
||||||
|
Time = time;
|
||||||
|
Type = type;
|
||||||
|
Nick = nick;
|
||||||
|
Message = message;
|
||||||
|
|
||||||
|
UID = Interlocked.Increment(ref nextUID).ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
var time = Time.ToString(Game.Settings.Chat.TimestampFormat);
|
||||||
|
if (Type == ChatMessageType.Notification)
|
||||||
|
return "{0} {1}".F(time, Message);
|
||||||
|
|
||||||
|
return "{0} {1}: {2}".F(time, Nick, Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GlobalChat : IDisposable
|
||||||
|
{
|
||||||
|
readonly IrcClient client = new IrcClient();
|
||||||
|
volatile Channel channel;
|
||||||
|
|
||||||
|
public readonly ObservableSortedDictionary<string, ChatUser> Users = new ObservableSortedDictionary<string, ChatUser>(StringComparer.InvariantCultureIgnoreCase);
|
||||||
|
public readonly ObservableList<ChatMessage> History = new ObservableList<ChatMessage>();
|
||||||
|
|
||||||
|
volatile string topic;
|
||||||
|
public string Topic { get { return topic; } }
|
||||||
|
|
||||||
|
volatile ChatConnectionStatus connectionStatus = ChatConnectionStatus.Disconnected;
|
||||||
|
public ChatConnectionStatus ConnectionStatus { get { return connectionStatus; } }
|
||||||
|
|
||||||
|
public GlobalChat()
|
||||||
|
{
|
||||||
|
client.Encoding = System.Text.Encoding.UTF8;
|
||||||
|
client.SendDelay = 100;
|
||||||
|
client.ActiveChannelSyncing = true;
|
||||||
|
|
||||||
|
client.OnConnecting += OnConnecting;
|
||||||
|
client.OnConnected += OnConnected;
|
||||||
|
client.OnDisconnecting += OnDisconnecting;
|
||||||
|
client.OnDisconnected += OnDisconnected;
|
||||||
|
client.OnError += OnError;
|
||||||
|
client.OnKick += OnKick;
|
||||||
|
|
||||||
|
client.OnRawMessage += (_, e) => Game.RunAfterTick(() => Log.Write("irc", e.Data.RawMessage));
|
||||||
|
client.OnJoin += OnJoin;
|
||||||
|
client.OnChannelActiveSynced += OnChannelActiveSynced;
|
||||||
|
client.OnTopic += (_, e) => topic = e.Topic;
|
||||||
|
client.OnTopicChange += (_, e) => topic = e.NewTopic;
|
||||||
|
client.OnNickChange += OnNickChange;
|
||||||
|
|
||||||
|
client.OnChannelMessage += (_, e) => AddMessage(e.Data.Nick, e.Data.Message);
|
||||||
|
client.OnOp += (_, e) => SetUserOp(e.Whom, true);
|
||||||
|
client.OnDeop += (_, e) => SetUserOp(e.Whom, false);
|
||||||
|
client.OnVoice += (_, e) => SetUserVoiced(e.Whom, true);
|
||||||
|
client.OnDevoice += (_, e) => SetUserVoiced(e.Whom, false);
|
||||||
|
client.OnPart += OnPart;
|
||||||
|
client.OnQuit += OnQuit;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetUserOp(string whom, bool isOp)
|
||||||
|
{
|
||||||
|
Game.RunAfterTick(() =>
|
||||||
|
{
|
||||||
|
ChatUser user;
|
||||||
|
if (Users.TryGetValue(whom, out user))
|
||||||
|
user.IsOp = isOp;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetUserVoiced(string whom, bool isVoiced)
|
||||||
|
{
|
||||||
|
Game.RunAfterTick(() =>
|
||||||
|
{
|
||||||
|
ChatUser user;
|
||||||
|
if (Users.TryGetValue(whom, out user))
|
||||||
|
user.IsVoiced = isVoiced;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Connect()
|
||||||
|
{
|
||||||
|
if (client.IsConnected)
|
||||||
|
return;
|
||||||
|
|
||||||
|
new Thread(() =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
client.Connect(Game.Settings.Chat.Hostname, Game.Settings.Chat.Port);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
connectionStatus = ChatConnectionStatus.Error;
|
||||||
|
AddNotification(e.Message);
|
||||||
|
Game.RunAfterTick(() => Log.Write("irc", e.ToString()));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
client.Listen();
|
||||||
|
}) { Name = "IrcListenThread" }.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddNotification(string text)
|
||||||
|
{
|
||||||
|
var message = new ChatMessage(DateTime.Now, ChatMessageType.Notification, null, text);
|
||||||
|
Game.RunAfterTick(() =>
|
||||||
|
{
|
||||||
|
History.Add(message);
|
||||||
|
Log.Write("irc", text);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddMessage(string nick, string text)
|
||||||
|
{
|
||||||
|
var message = new ChatMessage(DateTime.Now, ChatMessageType.Message, nick, text);
|
||||||
|
Game.RunAfterTick(() =>
|
||||||
|
{
|
||||||
|
History.Add(message);
|
||||||
|
Log.Write("irc", text);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnConnecting(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
AddNotification("Connecting to {0}:{1}...".F(Game.Settings.Chat.Hostname, Game.Settings.Chat.Port));
|
||||||
|
connectionStatus = ChatConnectionStatus.Connecting;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnConnected(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
AddNotification("Connected.");
|
||||||
|
connectionStatus = ChatConnectionStatus.Connected;
|
||||||
|
|
||||||
|
// Guard against settings.yaml modification
|
||||||
|
var nick = SanitizedName(Game.Settings.Chat.Nickname);
|
||||||
|
if (nick != Game.Settings.Chat.Nickname)
|
||||||
|
Game.RunAfterTick(() => Game.Settings.Chat.Nickname = nick);
|
||||||
|
|
||||||
|
client.Login(nick, "in-game IRC client", 0, "OpenRA");
|
||||||
|
client.RfcJoin("#" + Game.Settings.Chat.Channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnDisconnecting(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (connectionStatus != ChatConnectionStatus.Error)
|
||||||
|
connectionStatus = ChatConnectionStatus.Disconnecting;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnDisconnected(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
Game.RunAfterTick(Users.Clear);
|
||||||
|
|
||||||
|
// Keep the chat window open if there is an error
|
||||||
|
// It will be cleared by the Disconnect button
|
||||||
|
if (connectionStatus != ChatConnectionStatus.Error)
|
||||||
|
{
|
||||||
|
Game.RunAfterTick(History.Clear);
|
||||||
|
topic = null;
|
||||||
|
connectionStatus = ChatConnectionStatus.Disconnected;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnError(object sender, ErrorEventArgs e)
|
||||||
|
{
|
||||||
|
// Ignore any errors that happen during disconnect
|
||||||
|
if (connectionStatus != ChatConnectionStatus.Disconnecting)
|
||||||
|
{
|
||||||
|
connectionStatus = ChatConnectionStatus.Error;
|
||||||
|
AddNotification("Error: " + e.ErrorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnKick(object sender, KickEventArgs e)
|
||||||
|
{
|
||||||
|
Disconnect();
|
||||||
|
connectionStatus = ChatConnectionStatus.Error;
|
||||||
|
AddNotification("Error: You were kicked from the chat by {0}".F(e.Who));
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnJoin(object sender, JoinEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Who == client.Nickname || e.Channel != channel.Name)
|
||||||
|
return;
|
||||||
|
|
||||||
|
AddNotification("{0} joined the chat.".F(e.Who));
|
||||||
|
Game.RunAfterTick(() => Users.Add(e.Who, new ChatUser(e.Who, false, false)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnChannelActiveSynced(object sender, IrcEventArgs e)
|
||||||
|
{
|
||||||
|
channel = client.GetChannel(e.Data.Channel);
|
||||||
|
AddNotification("{0} users online".F(channel.Users.Count));
|
||||||
|
connectionStatus = ChatConnectionStatus.Joined;
|
||||||
|
|
||||||
|
foreach (DictionaryEntry user in channel.Users)
|
||||||
|
{
|
||||||
|
var u = (ChannelUser)user.Value;
|
||||||
|
Game.RunAfterTick(() => Users.Add(u.Nick, new ChatUser(u.Nick, u.IsOp, u.IsVoice)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnNickChange(object sender, NickChangeEventArgs e)
|
||||||
|
{
|
||||||
|
AddNotification("{0} is now known as {1}.".F(e.OldNickname, e.NewNickname));
|
||||||
|
|
||||||
|
Game.RunAfterTick(() =>
|
||||||
|
{
|
||||||
|
ChatUser user;
|
||||||
|
if (!Users.TryGetValue(e.OldNickname, out user))
|
||||||
|
return;
|
||||||
|
|
||||||
|
Users.Remove(e.OldNickname);
|
||||||
|
Users.Add(e.NewNickname, new ChatUser(e.NewNickname, user.IsOp, user.IsVoiced));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnQuit(object sender, QuitEventArgs e)
|
||||||
|
{
|
||||||
|
AddNotification("{0} left the chat.".F(e.Who));
|
||||||
|
Game.RunAfterTick(() => Users.Remove(e.Who));
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnPart(object sender, PartEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Data.Channel != channel.Name)
|
||||||
|
return;
|
||||||
|
|
||||||
|
AddNotification("{0} left the chat.".F(e.Who));
|
||||||
|
Game.RunAfterTick(() => Users.Remove(e.Who));
|
||||||
|
}
|
||||||
|
|
||||||
|
public string SanitizedName(string dirty)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(dirty))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// There is no need to mangle the nick if it is already valid
|
||||||
|
if (Rfc2812.IsValidNickname(dirty))
|
||||||
|
return dirty;
|
||||||
|
|
||||||
|
// TODO: some special chars are allowed as well, but not at every position
|
||||||
|
var clean = new string(dirty.Where(c => char.IsLetterOrDigit(c)).ToArray());
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(clean))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (char.IsDigit(clean[0]))
|
||||||
|
return SanitizedName(clean.Substring(1));
|
||||||
|
|
||||||
|
// Source: https://tools.ietf.org/html/rfc2812#section-1.2.1
|
||||||
|
if (clean.Length > 9)
|
||||||
|
clean = clean.Substring(0, 9);
|
||||||
|
|
||||||
|
return clean;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsValidNickname(string name)
|
||||||
|
{
|
||||||
|
return Rfc2812.IsValidNickname(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SendMessage(string text)
|
||||||
|
{
|
||||||
|
if (connectionStatus != ChatConnectionStatus.Joined)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Guard against a last-moment disconnection
|
||||||
|
try
|
||||||
|
{
|
||||||
|
client.SendMessage(SendType.Message, channel.Name, text);
|
||||||
|
AddMessage(client.Nickname, text);
|
||||||
|
}
|
||||||
|
catch (NotConnectedException) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TrySetNickname(string nick)
|
||||||
|
{
|
||||||
|
// TODO: This is inconsistent with the other check
|
||||||
|
if (Rfc2812.IsValidNickname(nick))
|
||||||
|
{
|
||||||
|
client.RfcNick(nick);
|
||||||
|
Game.Settings.Chat.Nickname = nick;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Disconnect()
|
||||||
|
{
|
||||||
|
// Error is an alias for disconnect, but keeps the panel open
|
||||||
|
// so that clients can see the error
|
||||||
|
if (connectionStatus == ChatConnectionStatus.Error)
|
||||||
|
{
|
||||||
|
Game.RunAfterTick(History.Clear);
|
||||||
|
topic = null;
|
||||||
|
connectionStatus = ChatConnectionStatus.Disconnected;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
connectionStatus = ChatConnectionStatus.Disconnecting;
|
||||||
|
|
||||||
|
if (!client.IsConnected)
|
||||||
|
return;
|
||||||
|
|
||||||
|
client.RfcQuit(Game.Settings.Chat.QuitMessage);
|
||||||
|
|
||||||
|
AddNotification("Disconnecting from {0}...".F(client.Address));
|
||||||
|
|
||||||
|
Game.RunAfterTick(() => Game.Settings.Chat.ConnectAutomatically = false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (client.IsConnected)
|
||||||
|
client.Disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -84,6 +84,9 @@
|
|||||||
<HintPath>..\thirdparty\download\MaxMind.Db.dll</HintPath>
|
<HintPath>..\thirdparty\download\MaxMind.Db.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
|
<Reference Include="SmarIrc4net">
|
||||||
|
<HintPath>..\thirdparty\download\SmarIrc4net.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="Activities\Activity.cs" />
|
<Compile Include="Activities\Activity.cs" />
|
||||||
|
|||||||
@@ -294,9 +294,9 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class IrcSettings
|
public class ChatSettings
|
||||||
{
|
{
|
||||||
public string[] Hostname = { "irc.openra.net" };
|
public string Hostname = "irc.openra.net";
|
||||||
public int Port = 6667;
|
public int Port = 6667;
|
||||||
public string Channel = "lobby";
|
public string Channel = "lobby";
|
||||||
public string Nickname = "Newbie";
|
public string Nickname = "Newbie";
|
||||||
@@ -316,7 +316,7 @@ namespace OpenRA
|
|||||||
public ServerSettings Server = new ServerSettings();
|
public ServerSettings Server = new ServerSettings();
|
||||||
public DebugSettings Debug = new DebugSettings();
|
public DebugSettings Debug = new DebugSettings();
|
||||||
public KeySettings Keys = new KeySettings();
|
public KeySettings Keys = new KeySettings();
|
||||||
public IrcSettings Irc = new IrcSettings();
|
public ChatSettings Chat = new ChatSettings();
|
||||||
|
|
||||||
public Dictionary<string, object> Sections;
|
public Dictionary<string, object> Sections;
|
||||||
|
|
||||||
@@ -332,7 +332,7 @@ namespace OpenRA
|
|||||||
{ "Server", Server },
|
{ "Server", Server },
|
||||||
{ "Debug", Debug },
|
{ "Debug", Debug },
|
||||||
{ "Keys", Keys },
|
{ "Keys", Keys },
|
||||||
{ "Irc", Irc }
|
{ "Chat", Chat }
|
||||||
};
|
};
|
||||||
|
|
||||||
// Override fieldloader to ignore invalid entries
|
// Override fieldloader to ignore invalid entries
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
#region Copyright & License Information
|
|
||||||
/*
|
|
||||||
* Copyright 2007-2015 The OpenRA Developers (see AUTHORS)
|
|
||||||
* This file is part of OpenRA, which is free software. It is made
|
|
||||||
* available to you under the terms of the GNU General Public License
|
|
||||||
* as published by the Free Software Foundation. For more information,
|
|
||||||
* see COPYING.
|
|
||||||
*/
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
using Meebey.SmartIrc4net;
|
|
||||||
|
|
||||||
namespace OpenRA.Mods.Common
|
|
||||||
{
|
|
||||||
public static class Irc
|
|
||||||
{
|
|
||||||
public static volatile IrcClient Client;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -78,9 +78,6 @@
|
|||||||
<HintPath>..\thirdparty\download\ICSharpCode.SharpZipLib.dll</HintPath>
|
<HintPath>..\thirdparty\download\ICSharpCode.SharpZipLib.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="SmarIrc4net">
|
|
||||||
<HintPath>..\thirdparty\download\SmarIrc4net.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\OpenRA.Game\OpenRA.Game.csproj">
|
<ProjectReference Include="..\OpenRA.Game\OpenRA.Game.csproj">
|
||||||
@@ -601,8 +598,6 @@
|
|||||||
<Compile Include="Widgets\Logic\Installation\DownloadPackagesLogic.cs" />
|
<Compile Include="Widgets\Logic\Installation\DownloadPackagesLogic.cs" />
|
||||||
<Compile Include="Widgets\Logic\Installation\InstallLogic.cs" />
|
<Compile Include="Widgets\Logic\Installation\InstallLogic.cs" />
|
||||||
<Compile Include="Widgets\Logic\Installation\InstallMusicLogic.cs" />
|
<Compile Include="Widgets\Logic\Installation\InstallMusicLogic.cs" />
|
||||||
<Compile Include="Widgets\Logic\IrcLogic.cs" />
|
|
||||||
<Compile Include="Irc.cs" />
|
|
||||||
<Compile Include="Widgets\Logic\Lobby\ClientTooltipLogic.cs" />
|
<Compile Include="Widgets\Logic\Lobby\ClientTooltipLogic.cs" />
|
||||||
<Compile Include="Widgets\Logic\Lobby\KickClientLogic.cs" />
|
<Compile Include="Widgets\Logic\Lobby\KickClientLogic.cs" />
|
||||||
<Compile Include="Widgets\Logic\Lobby\KickSpectatorsLogic.cs" />
|
<Compile Include="Widgets\Logic\Lobby\KickSpectatorsLogic.cs" />
|
||||||
@@ -707,6 +702,7 @@
|
|||||||
<Compile Include="Traits\World\MusicPlaylist.cs" />
|
<Compile Include="Traits\World\MusicPlaylist.cs" />
|
||||||
<Compile Include="Scripting\Global\LightingGlobal.cs" />
|
<Compile Include="Scripting\Global\LightingGlobal.cs" />
|
||||||
<Compile Include="Traits\SupportPowers\ProduceActorPower.cs" />
|
<Compile Include="Traits\SupportPowers\ProduceActorPower.cs" />
|
||||||
|
<Compile Include="Widgets\Logic\GlobalChatLogic.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
|||||||
163
OpenRA.Mods.Common/Widgets/Logic/GlobalChatLogic.cs
Normal file
163
OpenRA.Mods.Common/Widgets/Logic/GlobalChatLogic.cs
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
#region Copyright & License Information
|
||||||
|
/*
|
||||||
|
* Copyright 2007-2015 The OpenRA Developers (see AUTHORS)
|
||||||
|
* This file is part of OpenRA, which is free software. It is made
|
||||||
|
* available to you under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation. For more information,
|
||||||
|
* see COPYING.
|
||||||
|
*/
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using OpenRA.Chat;
|
||||||
|
using OpenRA.Widgets;
|
||||||
|
|
||||||
|
namespace OpenRA.Mods.Common.Widgets.Logic
|
||||||
|
{
|
||||||
|
class GlobalChatLogic : IDisposable
|
||||||
|
{
|
||||||
|
readonly ScrollPanelWidget historyPanel;
|
||||||
|
readonly LabelWidget historyTemplate;
|
||||||
|
readonly ScrollPanelWidget nicknamePanel;
|
||||||
|
readonly Widget nicknameTemplate;
|
||||||
|
readonly TextFieldWidget inputBox;
|
||||||
|
|
||||||
|
[ObjectCreator.UseCtor]
|
||||||
|
public GlobalChatLogic(Widget widget)
|
||||||
|
{
|
||||||
|
historyPanel = widget.Get<ScrollPanelWidget>("HISTORY_PANEL");
|
||||||
|
historyTemplate = historyPanel.Get<LabelWidget>("HISTORY_TEMPLATE");
|
||||||
|
nicknamePanel = widget.Get<ScrollPanelWidget>("NICKNAME_PANEL");
|
||||||
|
nicknameTemplate = nicknamePanel.Get("NICKNAME_TEMPLATE");
|
||||||
|
|
||||||
|
historyPanel.Bind(Game.GlobalChat.History, MakeHistoryWidget, HistoryWidgetEquals, true);
|
||||||
|
nicknamePanel.Bind(Game.GlobalChat.Users, MakeUserWidget, UserWidgetEquals, false);
|
||||||
|
|
||||||
|
inputBox = widget.Get<TextFieldWidget>("CHAT_TEXTFIELD");
|
||||||
|
inputBox.IsDisabled = () => Game.GlobalChat.ConnectionStatus != ChatConnectionStatus.Joined;
|
||||||
|
inputBox.OnEnterKey = EnterPressed;
|
||||||
|
|
||||||
|
// Set a random default nick
|
||||||
|
if (Game.Settings.Chat.Nickname == new ChatSettings().Nickname)
|
||||||
|
Game.Settings.Chat.Nickname += Game.CosmeticRandom.Next(100, 999);
|
||||||
|
|
||||||
|
var nicknameBox = widget.Get<TextFieldWidget>("NICKNAME_TEXTFIELD");
|
||||||
|
nicknameBox.Text = Game.GlobalChat.SanitizedName(Game.Settings.Chat.Nickname);
|
||||||
|
nicknameBox.OnTextEdited = () =>
|
||||||
|
{
|
||||||
|
nicknameBox.Text = Game.GlobalChat.SanitizedName(nicknameBox.Text);
|
||||||
|
};
|
||||||
|
|
||||||
|
var connectPanel = widget.Get("GLOBALCHAT_CONNECT_PANEL");
|
||||||
|
connectPanel.IsVisible = () => Game.GlobalChat.ConnectionStatus == ChatConnectionStatus.Disconnected;
|
||||||
|
|
||||||
|
var disconnectButton = widget.Get<ButtonWidget>("DISCONNECT_BUTTON");
|
||||||
|
disconnectButton.OnClick = Game.GlobalChat.Disconnect;
|
||||||
|
|
||||||
|
var connectAutomaticallyCheckBox = connectPanel.Get<CheckboxWidget>("CONNECT_AUTOMATICALLY_CHECKBOX");
|
||||||
|
connectAutomaticallyCheckBox.IsChecked = () => Game.Settings.Chat.ConnectAutomatically;
|
||||||
|
connectAutomaticallyCheckBox.OnClick = () => { Game.Settings.Chat.ConnectAutomatically ^= true; Game.Settings.Save(); };
|
||||||
|
|
||||||
|
var connectButton = connectPanel.Get<ButtonWidget>("CONNECT_BUTTON");
|
||||||
|
connectButton.IsDisabled = () => !Game.GlobalChat.IsValidNickname(nicknameBox.Text);
|
||||||
|
connectButton.OnClick = () =>
|
||||||
|
{
|
||||||
|
Game.Settings.Chat.Nickname = nicknameBox.Text;
|
||||||
|
Game.Settings.Save();
|
||||||
|
Game.GlobalChat.Connect();
|
||||||
|
};
|
||||||
|
|
||||||
|
var mainPanel = widget.Get("GLOBALCHAT_MAIN_PANEL");
|
||||||
|
mainPanel.IsVisible = () => Game.GlobalChat.ConnectionStatus != ChatConnectionStatus.Disconnected;
|
||||||
|
|
||||||
|
mainPanel.Get<LabelWidget>("CHANNEL_TOPIC").GetText = () => Game.GlobalChat.Topic;
|
||||||
|
|
||||||
|
if (Game.Settings.Chat.ConnectAutomatically && Game.GlobalChat.IsValidNickname(Game.Settings.Chat.Nickname))
|
||||||
|
Game.GlobalChat.Connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget MakeHistoryWidget(object o)
|
||||||
|
{
|
||||||
|
var message = (ChatMessage)o;
|
||||||
|
var widget = (LabelWidget)historyTemplate.Clone();
|
||||||
|
var font = Game.Renderer.Fonts[widget.Font];
|
||||||
|
|
||||||
|
var color = message.Type == ChatMessageType.Notification ?
|
||||||
|
ChromeMetrics.Get<Color>("GlobalChatNotificationColor") :
|
||||||
|
ChromeMetrics.Get<Color>("GlobalChatTextColor");
|
||||||
|
|
||||||
|
var display = WidgetUtils.WrapText(message.ToString(), widget.Bounds.Width, font);
|
||||||
|
widget.Bounds.Height = font.Measure(display).Y;
|
||||||
|
widget.GetText = () => display;
|
||||||
|
widget.GetColor = () => color;
|
||||||
|
widget.Id = message.UID;
|
||||||
|
return widget;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HistoryWidgetEquals(Widget widget, object o)
|
||||||
|
{
|
||||||
|
return ((LabelWidget)widget).Id == ((ChatMessage)o).UID;
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget MakeUserWidget(object o)
|
||||||
|
{
|
||||||
|
var nick = (string)o;
|
||||||
|
var client = Game.GlobalChat.Users[nick];
|
||||||
|
|
||||||
|
var item = nicknameTemplate.Clone();
|
||||||
|
item.Id = client.Name;
|
||||||
|
item.IsVisible = () => true;
|
||||||
|
var name = item.Get<LabelWidget>("NICK");
|
||||||
|
name.GetText = () => client.Name;
|
||||||
|
name.IsVisible = () => true;
|
||||||
|
|
||||||
|
// TODO: Add custom image for voice
|
||||||
|
var indicator = item.Get<ImageWidget>("INDICATOR");
|
||||||
|
indicator.IsVisible = () => client.IsOp || client.IsVoiced;
|
||||||
|
indicator.GetImageName = () => client.IsOp || client.IsVoiced ? "admin" : "";
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UserWidgetEquals(Widget widget, object o)
|
||||||
|
{
|
||||||
|
var nick = (string)o;
|
||||||
|
return widget.Id == nick;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EnterPressed()
|
||||||
|
{
|
||||||
|
if (inputBox.Text.Length == 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (inputBox.Text.StartsWith("/nick "))
|
||||||
|
{
|
||||||
|
var nick = inputBox.Text.Replace("/nick ", string.Empty);
|
||||||
|
Game.GlobalChat.TrySetNickname(nick);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Game.GlobalChat.SendMessage(inputBox.Text);
|
||||||
|
|
||||||
|
inputBox.Text = "";
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool disposed;
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (disposed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
historyPanel.Unbind();
|
||||||
|
nicknamePanel.Unbind();
|
||||||
|
|
||||||
|
disposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,374 +0,0 @@
|
|||||||
#region Copyright & License Information
|
|
||||||
/*
|
|
||||||
* Copyright 2007-2015 The OpenRA Developers (see AUTHORS)
|
|
||||||
* This file is part of OpenRA, which is free software. It is made
|
|
||||||
* available to you under the terms of the GNU General Public License
|
|
||||||
* as published by the Free Software Foundation. For more information,
|
|
||||||
* see COPYING.
|
|
||||||
*/
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
using Meebey.SmartIrc4net;
|
|
||||||
using OpenRA.Widgets;
|
|
||||||
|
|
||||||
namespace OpenRA.Mods.Common.Widgets.Logic
|
|
||||||
{
|
|
||||||
class IrcLogic
|
|
||||||
{
|
|
||||||
readonly TextFieldWidget inputBox;
|
|
||||||
readonly TextFieldWidget nicknameBox;
|
|
||||||
readonly Widget connectBG;
|
|
||||||
readonly Widget ircContainer;
|
|
||||||
|
|
||||||
readonly ScrollPanelWidget historyPanel;
|
|
||||||
readonly LabelWidget historyTemplate;
|
|
||||||
|
|
||||||
readonly ScrollPanelWidget nicknamePanel;
|
|
||||||
readonly LabelWidget nicknameTemplate;
|
|
||||||
|
|
||||||
bool pingSent;
|
|
||||||
Channel channel;
|
|
||||||
|
|
||||||
[ObjectCreator.UseCtor]
|
|
||||||
public IrcLogic(Widget widget)
|
|
||||||
{
|
|
||||||
Log.AddChannel("irc", "irc.log");
|
|
||||||
|
|
||||||
historyPanel = widget.Get<ScrollPanelWidget>("HISTORY_PANEL");
|
|
||||||
historyTemplate = widget.Get<LabelWidget>("HISTORY_TEMPLATE");
|
|
||||||
nicknamePanel = widget.Get<ScrollPanelWidget>("NICKNAME_PANEL");
|
|
||||||
nicknameTemplate = widget.Get<LabelWidget>("NICKNAME_TEMPLATE");
|
|
||||||
|
|
||||||
inputBox = widget.Get<TextFieldWidget>("INPUT_BOX");
|
|
||||||
inputBox.OnEnterKey = EnterPressed;
|
|
||||||
inputBox.IsDisabled = () => Irc.Client == null;
|
|
||||||
|
|
||||||
if (Game.Settings.Irc.Nickname == new IrcSettings().Nickname)
|
|
||||||
Game.Settings.Irc.Nickname += Game.CosmeticRandom.Next(100, 999);
|
|
||||||
|
|
||||||
nicknameBox = widget.Get<TextFieldWidget>("NICKNAME_BOX");
|
|
||||||
nicknameBox.Text = SanitizedName(Game.Settings.Irc.Nickname);
|
|
||||||
nicknameBox.OnTextEdited = () =>
|
|
||||||
{
|
|
||||||
nicknameBox.Text = SanitizedName(nicknameBox.Text);
|
|
||||||
Game.Settings.Irc.Nickname = nicknameBox.Text;
|
|
||||||
Game.Settings.Save();
|
|
||||||
};
|
|
||||||
|
|
||||||
connectBG = widget.Get("IRC_CONNECT_BG");
|
|
||||||
ircContainer = widget.Get("IRC_CONTAINER");
|
|
||||||
|
|
||||||
var disconnectButton = widget.Get<ButtonWidget>("DISCONNECT_BUTTON");
|
|
||||||
disconnectButton.IsDisabled = () => Irc.Client == null;
|
|
||||||
disconnectButton.OnClick = Disconnect;
|
|
||||||
|
|
||||||
MaybeShowConnectPanel();
|
|
||||||
}
|
|
||||||
|
|
||||||
static string SanitizedName(string dirty)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(dirty))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
// TODO: some special chars are allowed as well, but not at every position
|
|
||||||
var clean = new string(dirty.Where(c => char.IsLetterOrDigit(c)).ToArray());
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(clean))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
if (char.IsDigit(clean[0]))
|
|
||||||
return SanitizedName(clean.Substring(1));
|
|
||||||
|
|
||||||
// Source: https://tools.ietf.org/html/rfc2812#section-1.2.1
|
|
||||||
if (clean.Length > 9)
|
|
||||||
clean = clean.Substring(0, 9);
|
|
||||||
|
|
||||||
return clean;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MaybeShowConnectPanel()
|
|
||||||
{
|
|
||||||
if (Irc.Client != null && Irc.Client.IsConnected)
|
|
||||||
{
|
|
||||||
ircContainer.Visible = true;
|
|
||||||
connectBG.Visible = false;
|
|
||||||
|
|
||||||
Initialize();
|
|
||||||
|
|
||||||
if (Irc.Client.JoinedChannels.Count > 0)
|
|
||||||
channel = Irc.Client.GetChannel(Irc.Client.JoinedChannels[0]);
|
|
||||||
|
|
||||||
SyncNicknamePanel();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Game.Settings.Irc.ConnectAutomatically)
|
|
||||||
{
|
|
||||||
ircContainer.Visible = true;
|
|
||||||
connectBG.Visible = false;
|
|
||||||
Connect();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ircContainer.Visible = false;
|
|
||||||
connectBG.Visible = true;
|
|
||||||
|
|
||||||
var connectAutomaticallyCheckBox = connectBG.Get<CheckboxWidget>("CONNECT_AUTOMATICALLY_CHECKBOX");
|
|
||||||
connectAutomaticallyCheckBox.IsChecked = () => Game.Settings.Irc.ConnectAutomatically;
|
|
||||||
connectAutomaticallyCheckBox.OnClick = () => Game.Settings.Irc.ConnectAutomatically ^= true;
|
|
||||||
|
|
||||||
var connectButton = connectBG.Get<ButtonWidget>("CONNECT_BUTTON");
|
|
||||||
connectButton.IsDisabled = () => string.IsNullOrEmpty(nicknameBox.Text);
|
|
||||||
connectButton.OnClick = () =>
|
|
||||||
{
|
|
||||||
ircContainer.Visible = true;
|
|
||||||
connectBG.Visible = false;
|
|
||||||
|
|
||||||
Game.Settings.Irc.ConnectAutomatically = connectAutomaticallyCheckBox.IsChecked();
|
|
||||||
Game.Settings.Save();
|
|
||||||
Connect();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
void Initialize()
|
|
||||||
{
|
|
||||||
Irc.Client.OnConnected += OnConnected;
|
|
||||||
Irc.Client.OnError += OnError;
|
|
||||||
Irc.Client.OnRawMessage += OnRawMessage;
|
|
||||||
Irc.Client.OnJoin += OnJoin;
|
|
||||||
Irc.Client.OnChannelActiveSynced += OnChannelActiveSynced;
|
|
||||||
Irc.Client.OnNickChange += OnNickChange;
|
|
||||||
Irc.Client.OnPart += OnPart;
|
|
||||||
Irc.Client.OnQuit += OnQuit;
|
|
||||||
Irc.Client.OnChannelMessage += OnChannelMessage;
|
|
||||||
Irc.Client.OnPong += OnPong;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Connect()
|
|
||||||
{
|
|
||||||
Irc.Client = new IrcClient();
|
|
||||||
Irc.Client.Encoding = System.Text.Encoding.UTF8;
|
|
||||||
Irc.Client.SendDelay = 100;
|
|
||||||
Irc.Client.ActiveChannelSyncing = true;
|
|
||||||
|
|
||||||
Initialize();
|
|
||||||
|
|
||||||
Game.OnQuit += Disconnect;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
AddChatLine("Connecting to {0}...".F(Game.Settings.Irc.Hostname));
|
|
||||||
Irc.Client.Connect(Game.Settings.Irc.Hostname, Game.Settings.Irc.Port);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
AddChatLine("Connection error: {0}".F(e.Message));
|
|
||||||
Game.RunAfterTick(() =>
|
|
||||||
{
|
|
||||||
Log.Write("irc", e.ToString());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
new Thread(Irc.Client.Listen) { Name = "IrcListenThread" }.Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
void OnPong(object sender, PongEventArgs e)
|
|
||||||
{
|
|
||||||
if (pingSent)
|
|
||||||
{
|
|
||||||
AddChatLine("PONG recieved after {0} ms.".F(e.Lag.Milliseconds));
|
|
||||||
pingSent = false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Game.RunAfterTick(() =>
|
|
||||||
{
|
|
||||||
Log.Write("irc", "PONG sent after {0} ms.".F(e.Lag.Milliseconds));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget MakeLabelWidget(LabelWidget template, string item)
|
|
||||||
{
|
|
||||||
var widget = (LabelWidget)template.Clone();
|
|
||||||
var font = Game.Renderer.Fonts[widget.Font];
|
|
||||||
item = WidgetUtils.WrapText(item, widget.Bounds.Width, font);
|
|
||||||
widget.Bounds.Height = font.Measure(item).Y;
|
|
||||||
widget.GetText = () => item;
|
|
||||||
widget.Id = item;
|
|
||||||
return widget;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddChatLine(string text)
|
|
||||||
{
|
|
||||||
Game.RunAfterTick(() =>
|
|
||||||
{
|
|
||||||
Log.Write("irc", text);
|
|
||||||
|
|
||||||
var scrolledToBottom = historyPanel.ScrolledToBottom;
|
|
||||||
|
|
||||||
var newChild = MakeLabelWidget(historyTemplate, text);
|
|
||||||
historyPanel.AddChild(newChild);
|
|
||||||
|
|
||||||
if (scrolledToBottom)
|
|
||||||
historyPanel.ScrollToBottom(smooth: true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
bool EnterPressed()
|
|
||||||
{
|
|
||||||
if (inputBox.Text.Length == 0)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
var text = inputBox.Text;
|
|
||||||
inputBox.Text = "";
|
|
||||||
|
|
||||||
if (text.StartsWith("/nick "))
|
|
||||||
{
|
|
||||||
var nick = text.Replace("/nick ", string.Empty);
|
|
||||||
if (Rfc2812.IsValidNickname(nick))
|
|
||||||
Irc.Client.RfcNick(nick);
|
|
||||||
else
|
|
||||||
AddChatLine("Invalid nickname.");
|
|
||||||
}
|
|
||||||
else if (text.StartsWith("/ping "))
|
|
||||||
{
|
|
||||||
Irc.Client.RfcPing(Irc.Client.GetIrcUser(text.Replace("/ping ", string.Empty)).Host);
|
|
||||||
pingSent = true;
|
|
||||||
}
|
|
||||||
else if (text.StartsWith("/"))
|
|
||||||
AddChatLine("Unknown command.");
|
|
||||||
else
|
|
||||||
{
|
|
||||||
AddChatLine("[{0}] <{1}> {2}".F(DateTime.Now.ToString(Game.Settings.Irc.TimestampFormat), Irc.Client.Nickname, text));
|
|
||||||
Irc.Client.SendMessage(SendType.Message, "#" + Game.Settings.Irc.Channel, text);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void OnConnected(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
AddChatLine("Connected.");
|
|
||||||
|
|
||||||
if (!Rfc2812.IsValidNickname(Game.Settings.Irc.Nickname))
|
|
||||||
{
|
|
||||||
AddChatLine("Invalid nickname. Can't login.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Irc.Client.Login(new[] { Game.Settings.Irc.Nickname }, "in-game IRC client", 0, "OpenRA");
|
|
||||||
|
|
||||||
Irc.Client.RfcJoin("#" + Game.Settings.Irc.Channel);
|
|
||||||
}
|
|
||||||
|
|
||||||
void OnError(object sender, ErrorEventArgs e)
|
|
||||||
{
|
|
||||||
AddChatLine("Error: " + e.ErrorMessage);
|
|
||||||
Game.RunAfterTick(() =>
|
|
||||||
{
|
|
||||||
Log.Write("irc", e.ToString());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void OnRawMessage(object sender, IrcEventArgs e)
|
|
||||||
{
|
|
||||||
Game.RunAfterTick(() =>
|
|
||||||
{
|
|
||||||
Log.Write("irc", e.Data.RawMessage);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void OnChannelMessage(object sender, IrcEventArgs e)
|
|
||||||
{
|
|
||||||
AddChatLine("[{0}] <{1}> {2}".F(DateTime.Now.ToString(Game.Settings.Irc.TimestampFormat), e.Data.Nick, e.Data.Message));
|
|
||||||
}
|
|
||||||
|
|
||||||
void OnJoin(object sender, JoinEventArgs e)
|
|
||||||
{
|
|
||||||
if (e.Who == Irc.Client.Nickname)
|
|
||||||
return;
|
|
||||||
|
|
||||||
AddChatLine("{0} joined channel {1}.".F(e.Who, e.Channel));
|
|
||||||
channel = Irc.Client.GetChannel(e.Channel);
|
|
||||||
SyncNicknamePanel();
|
|
||||||
}
|
|
||||||
|
|
||||||
void OnChannelActiveSynced(object sender, IrcEventArgs e)
|
|
||||||
{
|
|
||||||
channel = Irc.Client.GetChannel(e.Data.Channel);
|
|
||||||
|
|
||||||
AddChatLine("{0} users online".F(channel.Users.Count));
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(channel.Topic))
|
|
||||||
AddChatLine("*** Topic: {0}".F(channel.Topic));
|
|
||||||
|
|
||||||
SyncNicknamePanel();
|
|
||||||
}
|
|
||||||
|
|
||||||
void OnNickChange(object sender, NickChangeEventArgs e)
|
|
||||||
{
|
|
||||||
AddChatLine("{0} is now known as {1}.".F(e.OldNickname, e.NewNickname));
|
|
||||||
SyncNicknamePanel();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SyncNicknamePanel()
|
|
||||||
{
|
|
||||||
if (channel == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var users = channel.Users;
|
|
||||||
Game.RunAfterTick(() =>
|
|
||||||
{
|
|
||||||
nicknamePanel.RemoveChildren();
|
|
||||||
|
|
||||||
foreach (DictionaryEntry user in users)
|
|
||||||
{
|
|
||||||
var channeluser = (ChannelUser)user.Value;
|
|
||||||
var prefix = channeluser.IsOp ? "@" : channeluser.IsVoice ? "+" : "";
|
|
||||||
var newChild = MakeLabelWidget(nicknameTemplate, prefix + channeluser.Nick);
|
|
||||||
nicknamePanel.AddChild(newChild);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void OnQuit(object sender, QuitEventArgs e)
|
|
||||||
{
|
|
||||||
AddChatLine("{0} quit.".F(e.Who));
|
|
||||||
}
|
|
||||||
|
|
||||||
void OnPart(object sender, PartEventArgs e)
|
|
||||||
{
|
|
||||||
AddChatLine("{0} left {1}.".F(e.Who, e.Data.Channel));
|
|
||||||
channel = Irc.Client.GetChannel(e.Data.Channel);
|
|
||||||
SyncNicknamePanel();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Disconnect()
|
|
||||||
{
|
|
||||||
if (Irc.Client == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Irc.Client.RfcQuit(Game.Settings.Irc.QuitMessage);
|
|
||||||
|
|
||||||
AddChatLine("Disconnecting from {0}...".F(Irc.Client.Address));
|
|
||||||
|
|
||||||
if (Irc.Client.IsConnected)
|
|
||||||
Irc.Client.Disconnect();
|
|
||||||
|
|
||||||
nicknamePanel.RemoveChildren();
|
|
||||||
|
|
||||||
Game.Settings.Irc.ConnectAutomatically = false;
|
|
||||||
|
|
||||||
Irc.Client = null;
|
|
||||||
|
|
||||||
MaybeShowConnectPanel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -135,14 +135,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
showIncompatibleCheckbox.OnClick = () => { showIncompatible ^= true; RefreshServerList(); };
|
showIncompatibleCheckbox.OnClick = () => { showIncompatible ^= true; RefreshServerList(); };
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
Game.LoadWidget(null, "GLOBALCHAT_PANEL", panel.Get("GLOBALCHAT_ROOT"), new WidgetArgs());
|
||||||
{
|
|
||||||
Game.LoadWidget(null, "SERVERBROWSER_IRC", panel.Get("IRC_ROOT"), new WidgetArgs());
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
Log.Write("debug", "Failed to load server browser IRC chrome layout");
|
|
||||||
}
|
|
||||||
|
|
||||||
RefreshServerList();
|
RefreshServerList();
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +1,36 @@
|
|||||||
Container@SERVERBROWSER_IRC:
|
Container@GLOBALCHAT_PANEL:
|
||||||
Logic: IrcLogic
|
Logic: GlobalChatLogic
|
||||||
Width: 700
|
Width: PARENT_RIGHT
|
||||||
Height: 250
|
Height: PARENT_BOTTOM
|
||||||
Children:
|
Children:
|
||||||
Container@IRC_CONTAINER:
|
Container@GLOBALCHAT_MAIN_PANEL:
|
||||||
Width: PARENT_RIGHT
|
Width: PARENT_RIGHT
|
||||||
Height: PARENT_BOTTOM
|
Height: PARENT_BOTTOM
|
||||||
Children:
|
Children:
|
||||||
|
Background@TOPIC:
|
||||||
|
Width: 565
|
||||||
|
Height: 20
|
||||||
|
Background: panel-transparent
|
||||||
|
Children:
|
||||||
|
Label@CHANNEL_TOPIC:
|
||||||
|
X: 10
|
||||||
|
Y: 0-1
|
||||||
|
Width: PARENT_RIGHT - 20
|
||||||
|
Height: PARENT_BOTTOM
|
||||||
|
Font: TinyBold
|
||||||
|
Align: Center
|
||||||
ScrollPanel@HISTORY_PANEL:
|
ScrollPanel@HISTORY_PANEL:
|
||||||
Width: 565
|
Width: 565
|
||||||
Height: PARENT_BOTTOM - 30
|
Y: 19
|
||||||
|
Height: PARENT_BOTTOM - 49
|
||||||
ItemSpacing: 5
|
ItemSpacing: 5
|
||||||
Label@HISTORY_TEMPLATE:
|
Children:
|
||||||
X: 5
|
Label@HISTORY_TEMPLATE:
|
||||||
Width: 530
|
X: 5
|
||||||
Height: 25
|
Width: 530
|
||||||
WordWrap: True
|
Height: 25
|
||||||
TextField@INPUT_BOX:
|
WordWrap: True
|
||||||
|
TextField@CHAT_TEXTFIELD:
|
||||||
Y: PARENT_BOTTOM - 25
|
Y: PARENT_BOTTOM - 25
|
||||||
Width: 565
|
Width: 565
|
||||||
Height: 25
|
Height: 25
|
||||||
@@ -24,8 +38,20 @@ Container@SERVERBROWSER_IRC:
|
|||||||
X: 570
|
X: 570
|
||||||
Width: 130
|
Width: 130
|
||||||
Height: PARENT_BOTTOM - 30
|
Height: PARENT_BOTTOM - 30
|
||||||
Label@NICKNAME_TEMPLATE:
|
Children:
|
||||||
X: 5
|
Container@NICKNAME_TEMPLATE:
|
||||||
|
Height: 20
|
||||||
|
Width: PARENT_RIGHT-25
|
||||||
|
Children:
|
||||||
|
Image@INDICATOR:
|
||||||
|
ImageCollection: lobby-bits
|
||||||
|
ImageName: admin
|
||||||
|
X: 4
|
||||||
|
Y: 9
|
||||||
|
Label@NICK:
|
||||||
|
X: 15
|
||||||
|
Width: PARENT_RIGHT-15
|
||||||
|
Height: 20
|
||||||
Button@DISCONNECT_BUTTON:
|
Button@DISCONNECT_BUTTON:
|
||||||
X: 570
|
X: 570
|
||||||
Y: PARENT_BOTTOM - 25
|
Y: PARENT_BOTTOM - 25
|
||||||
@@ -33,7 +59,7 @@ Container@SERVERBROWSER_IRC:
|
|||||||
Height: 25
|
Height: 25
|
||||||
Text: Disconnect
|
Text: Disconnect
|
||||||
Font: Bold
|
Font: Bold
|
||||||
Background@IRC_CONNECT_BG:
|
Background@GLOBALCHAT_CONNECT_PANEL:
|
||||||
Width: PARENT_RIGHT
|
Width: PARENT_RIGHT
|
||||||
Height: PARENT_BOTTOM
|
Height: PARENT_BOTTOM
|
||||||
Background: scrollpanel-bg
|
Background: scrollpanel-bg
|
||||||
@@ -48,7 +74,7 @@ Container@SERVERBROWSER_IRC:
|
|||||||
X: 200
|
X: 200
|
||||||
Y: PARENT_BOTTOM / 4 + 35
|
Y: PARENT_BOTTOM / 4 + 35
|
||||||
Text: Nickname:
|
Text: Nickname:
|
||||||
TextField@NICKNAME_BOX:
|
TextField@NICKNAME_TEXTFIELD:
|
||||||
X: 270
|
X: 270
|
||||||
Y: PARENT_BOTTOM / 4 + 25
|
Y: PARENT_BOTTOM / 4 + 25
|
||||||
Width: 150
|
Width: 150
|
||||||
@@ -18,9 +18,11 @@ Container@SERVERBROWSER_PANEL:
|
|||||||
Background: panel-black
|
Background: panel-black
|
||||||
Y: 15
|
Y: 15
|
||||||
Children:
|
Children:
|
||||||
Container@IRC_ROOT:
|
Container@GLOBALCHAT_ROOT:
|
||||||
X: 15
|
X: 15
|
||||||
Y: 15
|
Y: 15
|
||||||
|
Width: 700
|
||||||
|
Height: 260
|
||||||
ScrollPanel@SERVER_LIST:
|
ScrollPanel@SERVER_LIST:
|
||||||
X: 15
|
X: 15
|
||||||
Y: 280
|
Y: 280
|
||||||
|
|||||||
@@ -33,4 +33,6 @@ Metrics:
|
|||||||
WaitingGameColor: 0,255,0
|
WaitingGameColor: 0,255,0
|
||||||
IncompatibleWaitingGameColor: 50,205,50
|
IncompatibleWaitingGameColor: 50,205,50
|
||||||
GameStartedColor: 255,165,0
|
GameStartedColor: 255,165,0
|
||||||
IncompatibleGameStartedColor: 210,105,30
|
IncompatibleGameStartedColor: 210,105,30
|
||||||
|
GlobalChatTextColor: 255,255,255
|
||||||
|
GlobalChatNotificationColor: 211,211,211
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ ChromeLayout:
|
|||||||
./mods/cnc/chrome/assetbrowser.yaml
|
./mods/cnc/chrome/assetbrowser.yaml
|
||||||
./mods/cnc/chrome/missionbrowser.yaml
|
./mods/cnc/chrome/missionbrowser.yaml
|
||||||
./mods/cnc/chrome/editor.yaml
|
./mods/cnc/chrome/editor.yaml
|
||||||
./mods/cnc/chrome/irc.yaml
|
./mods/cnc/chrome/globalchat.yaml
|
||||||
|
|
||||||
Voices:
|
Voices:
|
||||||
./mods/cnc/audio/voices.yaml
|
./mods/cnc/audio/voices.yaml
|
||||||
|
|||||||
@@ -33,4 +33,6 @@ Metrics:
|
|||||||
WaitingGameColor: 0,255,0
|
WaitingGameColor: 0,255,0
|
||||||
IncompatibleWaitingGameColor: 50,205,50
|
IncompatibleWaitingGameColor: 50,205,50
|
||||||
GameStartedColor: 255,165,0
|
GameStartedColor: 255,165,0
|
||||||
IncompatibleGameStartedColor: 210,105,30
|
IncompatibleGameStartedColor: 210,105,30
|
||||||
|
GlobalChatTextColor: 255,255,255
|
||||||
|
GlobalChatNotificationColor: 211,211,211
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ ChromeLayout:
|
|||||||
./mods/d2k/chrome/missionbrowser.yaml
|
./mods/d2k/chrome/missionbrowser.yaml
|
||||||
./mods/ra/chrome/confirmation-dialogs.yaml
|
./mods/ra/chrome/confirmation-dialogs.yaml
|
||||||
./mods/ra/chrome/editor.yaml
|
./mods/ra/chrome/editor.yaml
|
||||||
./mods/ra/chrome/irc.yaml
|
./mods/ra/chrome/globalchat.yaml
|
||||||
|
|
||||||
Weapons:
|
Weapons:
|
||||||
./mods/d2k/weapons.yaml
|
./mods/d2k/weapons.yaml
|
||||||
|
|||||||
@@ -1,22 +1,36 @@
|
|||||||
Container@SERVERBROWSER_IRC:
|
Container@GLOBALCHAT_PANEL:
|
||||||
Logic: IrcLogic
|
Logic: GlobalChatLogic
|
||||||
Width: 700
|
Width: PARENT_RIGHT
|
||||||
Height: 250
|
Height: PARENT_BOTTOM
|
||||||
Children:
|
Children:
|
||||||
Container@IRC_CONTAINER:
|
Container@GLOBALCHAT_MAIN_PANEL:
|
||||||
Width: PARENT_RIGHT
|
Width: PARENT_RIGHT
|
||||||
Height: PARENT_BOTTOM
|
Height: PARENT_BOTTOM
|
||||||
Children:
|
Children:
|
||||||
ScrollPanel@HISTORY_PANEL:
|
Background@TOPIC:
|
||||||
Width: 565
|
Width: 565
|
||||||
Height: PARENT_BOTTOM - 30
|
Height: 20
|
||||||
|
Background: dialog2
|
||||||
|
Children:
|
||||||
|
Label@CHANNEL_TOPIC:
|
||||||
|
X: 10
|
||||||
|
Y: 0-1
|
||||||
|
Width: PARENT_RIGHT - 20
|
||||||
|
Height: PARENT_BOTTOM
|
||||||
|
Font: TinyBold
|
||||||
|
Align: Center
|
||||||
|
ScrollPanel@HISTORY_PANEL:
|
||||||
|
Y:20
|
||||||
|
Width: 565
|
||||||
|
Height: PARENT_BOTTOM - 50
|
||||||
ItemSpacing: 5
|
ItemSpacing: 5
|
||||||
Label@HISTORY_TEMPLATE:
|
Children:
|
||||||
X: 5
|
Label@HISTORY_TEMPLATE:
|
||||||
Width: 530
|
X: 5
|
||||||
Height: 25
|
Width: 530
|
||||||
WordWrap: True
|
Height: 25
|
||||||
TextField@INPUT_BOX:
|
WordWrap: True
|
||||||
|
TextField@CHAT_TEXTFIELD:
|
||||||
Y: PARENT_BOTTOM - 25
|
Y: PARENT_BOTTOM - 25
|
||||||
Width: 565
|
Width: 565
|
||||||
Height: 25
|
Height: 25
|
||||||
@@ -24,8 +38,20 @@ Container@SERVERBROWSER_IRC:
|
|||||||
X: 570
|
X: 570
|
||||||
Width: 130
|
Width: 130
|
||||||
Height: PARENT_BOTTOM - 30
|
Height: PARENT_BOTTOM - 30
|
||||||
Label@NICKNAME_TEMPLATE:
|
Children:
|
||||||
X: 5
|
Container@NICKNAME_TEMPLATE:
|
||||||
|
Height: 20
|
||||||
|
Width: PARENT_RIGHT-25
|
||||||
|
Children:
|
||||||
|
Image@INDICATOR:
|
||||||
|
ImageCollection: lobby-bits
|
||||||
|
ImageName: admin
|
||||||
|
X: 4
|
||||||
|
Y: 9
|
||||||
|
Label@NICK:
|
||||||
|
X: 15
|
||||||
|
Width: PARENT_RIGHT-15
|
||||||
|
Height: 20
|
||||||
Button@DISCONNECT_BUTTON:
|
Button@DISCONNECT_BUTTON:
|
||||||
X: 570
|
X: 570
|
||||||
Y: PARENT_BOTTOM - 25
|
Y: PARENT_BOTTOM - 25
|
||||||
@@ -33,7 +59,7 @@ Container@SERVERBROWSER_IRC:
|
|||||||
Height: 25
|
Height: 25
|
||||||
Text: Disconnect
|
Text: Disconnect
|
||||||
Font: Bold
|
Font: Bold
|
||||||
Background@IRC_CONNECT_BG:
|
Background@GLOBALCHAT_CONNECT_PANEL:
|
||||||
Width: PARENT_RIGHT
|
Width: PARENT_RIGHT
|
||||||
Height: PARENT_BOTTOM
|
Height: PARENT_BOTTOM
|
||||||
Background: scrollpanel-bg
|
Background: scrollpanel-bg
|
||||||
@@ -48,7 +74,7 @@ Container@SERVERBROWSER_IRC:
|
|||||||
X: 200
|
X: 200
|
||||||
Y: PARENT_BOTTOM / 4 + 35
|
Y: PARENT_BOTTOM / 4 + 35
|
||||||
Text: Nickname:
|
Text: Nickname:
|
||||||
TextField@NICKNAME_BOX:
|
TextField@NICKNAME_TEXTFIELD:
|
||||||
X: 270
|
X: 270
|
||||||
Y: PARENT_BOTTOM / 4 + 25
|
Y: PARENT_BOTTOM / 4 + 25
|
||||||
Width: 150
|
Width: 150
|
||||||
@@ -118,10 +118,11 @@ Background@SERVERBROWSER_PANEL:
|
|||||||
Y: 40
|
Y: 40
|
||||||
Align: Right
|
Align: Right
|
||||||
Height: 25
|
Height: 25
|
||||||
Container@IRC_ROOT:
|
Container@GLOBALCHAT_ROOT:
|
||||||
X: 20
|
X: 20
|
||||||
Y: 370
|
Y: 370
|
||||||
Width: 260
|
Width: 700
|
||||||
|
Height: 255
|
||||||
Label@PROGRESS_LABEL:
|
Label@PROGRESS_LABEL:
|
||||||
X: (PARENT_RIGHT - WIDTH) / 2
|
X: (PARENT_RIGHT - WIDTH) / 2
|
||||||
Y: PARENT_BOTTOM / 2 - HEIGHT
|
Y: PARENT_BOTTOM / 2 - HEIGHT
|
||||||
|
|||||||
@@ -41,3 +41,5 @@ Metrics:
|
|||||||
IncompatibleWaitingGameColor: 50,205,50
|
IncompatibleWaitingGameColor: 50,205,50
|
||||||
GameStartedColor: 255,165,0
|
GameStartedColor: 255,165,0
|
||||||
IncompatibleGameStartedColor: 210,105,30
|
IncompatibleGameStartedColor: 210,105,30
|
||||||
|
GlobalChatTextColor: 255,255,255
|
||||||
|
GlobalChatNotificationColor: 211,211,211
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ ChromeLayout:
|
|||||||
./mods/ra/chrome/missionbrowser.yaml
|
./mods/ra/chrome/missionbrowser.yaml
|
||||||
./mods/ra/chrome/confirmation-dialogs.yaml
|
./mods/ra/chrome/confirmation-dialogs.yaml
|
||||||
./mods/ra/chrome/editor.yaml
|
./mods/ra/chrome/editor.yaml
|
||||||
./mods/ra/chrome/irc.yaml
|
./mods/ra/chrome/globalchat.yaml
|
||||||
|
|
||||||
Weapons:
|
Weapons:
|
||||||
./mods/ra/weapons/explosions.yaml
|
./mods/ra/weapons/explosions.yaml
|
||||||
|
|||||||
@@ -33,4 +33,6 @@ Metrics:
|
|||||||
WaitingGameColor: 0,255,0
|
WaitingGameColor: 0,255,0
|
||||||
IncompatibleWaitingGameColor: 50,205,50
|
IncompatibleWaitingGameColor: 50,205,50
|
||||||
GameStartedColor: 255,165,0
|
GameStartedColor: 255,165,0
|
||||||
IncompatibleGameStartedColor: 210,105,30
|
IncompatibleGameStartedColor: 210,105,30
|
||||||
|
GlobalChatTextColor: 255,255,255
|
||||||
|
GlobalChatNotificationColor: 211,211,211
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ ChromeLayout:
|
|||||||
./mods/ra/chrome/missionbrowser.yaml
|
./mods/ra/chrome/missionbrowser.yaml
|
||||||
./mods/ra/chrome/confirmation-dialogs.yaml
|
./mods/ra/chrome/confirmation-dialogs.yaml
|
||||||
./mods/ra/chrome/editor.yaml
|
./mods/ra/chrome/editor.yaml
|
||||||
./mods/ra/chrome/irc.yaml
|
./mods/ra/chrome/globalchat.yaml
|
||||||
|
|
||||||
Voices:
|
Voices:
|
||||||
./mods/ts/audio/voices.yaml
|
./mods/ts/audio/voices.yaml
|
||||||
|
|||||||
Reference in New Issue
Block a user