From f3fa81b8f14681bfa4b04a3102ac59c924916374 Mon Sep 17 00:00:00 2001 From: ScottNZ Date: Fri, 20 Sep 2013 20:47:03 +1200 Subject: [PATCH 1/6] Fix ActionQueue.PerformActions executing actions out of order --- OpenRA.FileFormats/Primitives/ActionQueue.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenRA.FileFormats/Primitives/ActionQueue.cs b/OpenRA.FileFormats/Primitives/ActionQueue.cs index d7a5939a83..483537d942 100644 --- a/OpenRA.FileFormats/Primitives/ActionQueue.cs +++ b/OpenRA.FileFormats/Primitives/ActionQueue.cs @@ -36,7 +36,7 @@ namespace OpenRA.FileFormats while (!actions.Empty && actions.Peek().Time <= t) { var da = actions.Pop(); - a += da.Action; + a = da.Action + a; } } a(); From 8b89952d59221d091c8088fdbfa74a9eb19ff538 Mon Sep 17 00:00:00 2001 From: ScottNZ Date: Fri, 20 Sep 2013 20:47:24 +1200 Subject: [PATCH 2/6] Add observable collections --- OpenRA.FileFormats/OpenRA.FileFormats.csproj | 3 + .../Primitives/IObservableCollection.cs | 25 ++++ .../Primitives/ObservableCollection.cs | 59 ++++++++ .../Primitives/ObservableDictionary.cs | 137 ++++++++++++++++++ 4 files changed, 224 insertions(+) create mode 100644 OpenRA.FileFormats/Primitives/IObservableCollection.cs create mode 100644 OpenRA.FileFormats/Primitives/ObservableCollection.cs create mode 100644 OpenRA.FileFormats/Primitives/ObservableDictionary.cs diff --git a/OpenRA.FileFormats/OpenRA.FileFormats.csproj b/OpenRA.FileFormats/OpenRA.FileFormats.csproj index 02c58d7291..95e2f659fa 100644 --- a/OpenRA.FileFormats/OpenRA.FileFormats.csproj +++ b/OpenRA.FileFormats/OpenRA.FileFormats.csproj @@ -120,7 +120,10 @@ + + + diff --git a/OpenRA.FileFormats/Primitives/IObservableCollection.cs b/OpenRA.FileFormats/Primitives/IObservableCollection.cs new file mode 100644 index 0000000000..f7da4fe6c3 --- /dev/null +++ b/OpenRA.FileFormats/Primitives/IObservableCollection.cs @@ -0,0 +1,25 @@ +#region Copyright & License Information +/* + * Copyright 2007-2013 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; + +namespace OpenRA.FileFormats.Primitives +{ + public interface IObservableCollection + { + event Action OnAdd; + event Action OnRemove; + event Action OnRemoveAt; + event Action OnSet; + event Action OnRefresh; + IEnumerable ObservedItems { get; } + } +} diff --git a/OpenRA.FileFormats/Primitives/ObservableCollection.cs b/OpenRA.FileFormats/Primitives/ObservableCollection.cs new file mode 100644 index 0000000000..610cb28762 --- /dev/null +++ b/OpenRA.FileFormats/Primitives/ObservableCollection.cs @@ -0,0 +1,59 @@ +#region Copyright & License Information +/* + * Copyright 2007-2013 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.Collections.ObjectModel; + +namespace OpenRA.FileFormats.Primitives +{ + public class ObservableCollection : Collection, IObservableCollection + { + public event Action OnAdd = k => { }; + public event Action OnRemove = k => { }; + public event Action OnRemoveAt = i => { }; + public event Action OnSet = (o, n) => { }; + public event Action OnRefresh = () => { }; + + public ObservableCollection() : base() { } + public ObservableCollection(IList list) : base(list) { } + + protected override void SetItem(int index, T item) + { + var old = this[index]; + base.SetItem(index, item); + OnSet(old, item); + } + + protected override void InsertItem(int index, T item) + { + base.InsertItem(index, item); + OnAdd(item); + } + + protected override void ClearItems() + { + base.ClearItems(); + OnRefresh(); + } + + protected override void RemoveItem(int index) + { + base.RemoveItem(index); + OnRemoveAt(index); + } + + public IEnumerable ObservedItems + { + get { return base.Items; } + } + } +} diff --git a/OpenRA.FileFormats/Primitives/ObservableDictionary.cs b/OpenRA.FileFormats/Primitives/ObservableDictionary.cs new file mode 100644 index 0000000000..2f078cb451 --- /dev/null +++ b/OpenRA.FileFormats/Primitives/ObservableDictionary.cs @@ -0,0 +1,137 @@ +#region Copyright & License Information +/* + * Copyright 2007-2013 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; + +namespace OpenRA.FileFormats.Primitives +{ + public class ObservableSortedDictionary : ObservableDictionary + { + public ObservableSortedDictionary(IComparer comparer) + { + InnerDict = new SortedDictionary(comparer); + } + + public override void Add(TKey key, TValue value) + { + InnerDict.Add(key, value); + FireOnRefresh(); + } + } + + public class ObservableDictionary : IDictionary, IObservableCollection + { + protected IDictionary InnerDict; + + public event Action OnAdd = k => { }; + public event Action OnRemove = k => { }; + public event Action OnRemoveAt = i => { }; + public event Action OnSet = (o, n) => { }; + public event Action OnRefresh = () => { }; + + protected void FireOnRefresh() + { + OnRefresh(); + } + + protected ObservableDictionary() { } + + public ObservableDictionary(IEqualityComparer comparer) + { + InnerDict = new Dictionary(comparer); + } + + public virtual void Add(TKey key, TValue value) + { + InnerDict.Add(key, value); + OnAdd(key); + } + + public bool Remove(TKey key) + { + var found = InnerDict.Remove(key); + if (found) + OnRemove(key); + return found; + } + + public bool ContainsKey(TKey key) + { + return InnerDict.ContainsKey(key); + } + + public ICollection Keys { get { return InnerDict.Keys; } } + public ICollection Values { get { return InnerDict.Values; } } + + public bool TryGetValue(TKey key, out TValue value) + { + return InnerDict.TryGetValue(key, out value); + } + + public TValue this[TKey key] + { + get { return InnerDict[key]; } + set { InnerDict[key] = value; } + } + + public void Clear() + { + InnerDict.Clear(); + OnRefresh(); + } + + public int Count + { + get { return InnerDict.Count; } + } + + public void Add(KeyValuePair item) + { + Add(item.Key, item.Value); + } + + public bool Contains(KeyValuePair item) + { + return InnerDict.Contains(item); + } + + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + InnerDict.CopyTo(array, arrayIndex); + } + + public bool IsReadOnly + { + get { return InnerDict.IsReadOnly; } + } + + public bool Remove(KeyValuePair item) + { + return Remove(item.Key); + } + + public IEnumerator> GetEnumerator() + { + return InnerDict.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return InnerDict.GetEnumerator(); + } + + public IEnumerable ObservedItems + { + get { return InnerDict.Keys; } + } + } +} From 3ee1628b13a28af4fe03f8b3fe239d041d2403b6 Mon Sep 17 00:00:00 2001 From: ScottNZ Date: Fri, 20 Sep 2013 20:50:24 +1200 Subject: [PATCH 3/6] Add databinding support to ScrollPanelWidget --- OpenRA.Game/Widgets/ScrollPanelWidget.cs | 123 ++++++++++++++++++++++- 1 file changed, 119 insertions(+), 4 deletions(-) diff --git a/OpenRA.Game/Widgets/ScrollPanelWidget.cs b/OpenRA.Game/Widgets/ScrollPanelWidget.cs index 4572b18273..b3dbd1d74c 100644 --- a/OpenRA.Game/Widgets/ScrollPanelWidget.cs +++ b/OpenRA.Game/Widgets/ScrollPanelWidget.cs @@ -11,6 +11,7 @@ using System; using System.Drawing; using System.Linq; +using OpenRA.FileFormats.Primitives; using OpenRA.Graphics; namespace OpenRA.Widgets @@ -45,6 +46,7 @@ namespace OpenRA.Widgets base.RemoveChildren(); } + public override void AddChild(Widget child) { // Initial setup of margins/height @@ -61,7 +63,6 @@ namespace OpenRA.Widgets public void ReplaceChild(Widget oldChild, Widget newChild) { - oldChild.Removed(); newChild.Parent = this; Children[Children.IndexOf(oldChild)] = newChild; @@ -69,8 +70,6 @@ namespace OpenRA.Widgets Scroll(0); } - - public override void DrawOuter() { if (!IsVisible()) @@ -85,7 +84,7 @@ namespace OpenRA.Widgets if (thumbHeight == ScrollbarHeight) thumbHeight = 0; - backgroundRect = new Rectangle(rb.X, rb.Y, rb.Width - ScrollbarWidth+1, rb.Height); + backgroundRect = new Rectangle(rb.X, rb.Y, rb.Width - ScrollbarWidth + 1, rb.Height); upButtonRect = new Rectangle(rb.Right - ScrollbarWidth, rb.Y, ScrollbarWidth, ScrollbarWidth); downButtonRect = new Rectangle(rb.Right - ScrollbarWidth, rb.Bottom - ScrollbarWidth, ScrollbarWidth, ScrollbarWidth); scrollbarRect = new Rectangle(rb.Right - ScrollbarWidth, rb.Y + ScrollbarWidth - 1, ScrollbarWidth, ScrollbarHeight + 2); @@ -145,6 +144,11 @@ namespace OpenRA.Widgets ListOffset = 0; } + public bool ScrolledToBottom + { + get { return ListOffset == Math.Min(0, Bounds.Height - ContentHeight); } + } + public void ScrollToItem(string itemKey) { var item = Children.FirstOrDefault(c => @@ -226,5 +230,116 @@ namespace OpenRA.Widgets return UpPressed || DownPressed || ThumbPressed; } + + IObservableCollection collection; + Func makeWidget; + Func widgetItemEquals; + bool autoScroll; + + public void Unbind() + { + Bind(null, null, null, false); + } + + public void Bind(IObservableCollection c, Func makeWidget, Func widgetItemEquals, bool autoScroll) + { + this.autoScroll = autoScroll; + + Game.RunAfterTick(() => + { + if (collection != null) + { + collection.OnAdd -= BindingAdd; + collection.OnRemove -= BindingRemove; + collection.OnRemoveAt -= BindingRemoveAt; + collection.OnSet -= BindingSet; + collection.OnRefresh -= BindingRefresh; + } + + this.makeWidget = makeWidget; + this.widgetItemEquals = widgetItemEquals; + + RemoveChildren(); + collection = c; + + if (c != null) + { + foreach (var item in c.ObservedItems) + BindingAddImpl(item); + + c.OnAdd += BindingAdd; + c.OnRemove += BindingRemove; + c.OnRemoveAt += BindingRemoveAt; + c.OnSet += BindingSet; + c.OnRefresh += BindingRefresh; + } + }); + } + + void BindingAdd(object item) + { + Game.RunAfterTick(() => BindingAddImpl(item)); + } + + void BindingAddImpl(object item) + { + var widget = makeWidget(item); + var scrollToBottom = autoScroll && ScrolledToBottom; + + AddChild(widget); + + if (scrollToBottom) + ScrollToBottom(); + } + + void BindingRemove(object item) + { + Game.RunAfterTick(() => + { + var widget = Children.FirstOrDefault(w => widgetItemEquals(w, item)); + if (widget != null) + RemoveChild(widget); + }); + } + + void BindingRemoveAt(int index) + { + Game.RunAfterTick(() => + { + if (index < 0 || index >= Children.Count) + return; + RemoveChild(Children[index]); + }); + } + + void BindingSet(object oldItem, object newItem) + { + Game.RunAfterTick(() => + { + var newWidget = makeWidget(newItem); + newWidget.Parent = this; + + var i = Children.FindIndex(w => widgetItemEquals(w, oldItem)); + if (i >= 0) + { + var oldWidget = Children[i]; + oldWidget.Removed(); + Children[i] = newWidget; + Layout.AdjustChildren(); + } + else + AddChild(newWidget); + }); + } + + void BindingRefresh() + { + Game.RunAfterTick(() => + { + RemoveChildren(); + foreach (var item in collection.ObservedItems) + BindingAddImpl(item); + }); + } } } From 5bdd0705b25de6c74e957450754b7bccca24bfcf Mon Sep 17 00:00:00 2001 From: ScottNZ Date: Fri, 20 Sep 2013 20:54:08 +1200 Subject: [PATCH 4/6] Add main IRC logic --- Makefile | 14 +- OpenRA.Game/GameRules/Settings.cs | 17 ++ OpenRA.Irc/Channel.cs | 27 ++ OpenRA.Irc/IrcClient.cs | 402 ++++++++++++++++++++++++++++++ OpenRA.Irc/IrcClientUser.cs | 112 +++++++++ OpenRA.Irc/IrcConnection.cs | 83 ++++++ OpenRA.Irc/IrcUtils.cs | 67 +++++ OpenRA.Irc/Line.cs | 155 ++++++++++++ OpenRA.Irc/NumericCommand.cs | 25 ++ OpenRA.Irc/OpenRA.Irc.csproj | 70 ++++++ OpenRA.Irc/Topic.cs | 30 +++ OpenRA.Irc/User.cs | 73 ++++++ OpenRA.sln | 216 ++++++++-------- 13 files changed, 1185 insertions(+), 106 deletions(-) create mode 100644 OpenRA.Irc/Channel.cs create mode 100644 OpenRA.Irc/IrcClient.cs create mode 100644 OpenRA.Irc/IrcClientUser.cs create mode 100644 OpenRA.Irc/IrcConnection.cs create mode 100644 OpenRA.Irc/IrcUtils.cs create mode 100644 OpenRA.Irc/Line.cs create mode 100644 OpenRA.Irc/NumericCommand.cs create mode 100644 OpenRA.Irc/OpenRA.Irc.csproj create mode 100644 OpenRA.Irc/Topic.cs create mode 100644 OpenRA.Irc/User.cs diff --git a/Makefile b/Makefile index 8442ab00db..9352d34d51 100644 --- a/Makefile +++ b/Makefile @@ -63,7 +63,7 @@ INSTALL_PROGRAM = $(INSTALL) -m755 INSTALL_DATA = $(INSTALL) -m644 # program targets -CORE = fileformats rcg rgl rsdl rnull game utility geoip +CORE = fileformats rcg rgl rsdl rnull game utility geoip irc TOOLS = editor tsbuild ralint VERSION = $(shell git name-rev --name-only --tags --no-undefined HEAD 2>/dev/null || echo git-`git rev-parse --short HEAD`) @@ -97,6 +97,14 @@ game_FLAGS = -win32icon:OpenRA.Game/OpenRA.ico PROGRAMS += game game: $(game_TARGET) +irc_SRCS := $(shell find OpenRA.Irc/ -iname '*.cs') +irc_TARGET = OpenRA.Irc.dll +irc_KIND = library +irc_DEPS = $(fileformats_TARGET) $(game_TARGET) +irc_LIBS = $(COMMON_LIBS) $(irc_DEPS) +PROGRAMS += irc +irc: $(irc_TARGET) + # Renderer dlls rsdl_SRCS := $(shell find OpenRA.Renderer.SdlCommon/ -iname '*.cs') rsdl_TARGET = OpenRA.Renderer.SdlCommon.dll @@ -141,8 +149,8 @@ STD_MOD_DEPS = $(STD_MOD_LIBS) $(ralint_TARGET) mod_ra_SRCS := $(shell find OpenRA.Mods.RA/ -iname '*.cs') mod_ra_TARGET = mods/ra/OpenRA.Mods.RA.dll mod_ra_KIND = library -mod_ra_DEPS = $(STD_MOD_DEPS) $(utility_TARGET) $(geoip_TARGET) -mod_ra_LIBS = $(COMMON_LIBS) $(STD_MOD_LIBS) $(utility_TARGET) $(geoip_TARGET) +mod_ra_DEPS = $(STD_MOD_DEPS) $(utility_TARGET) $(geoip_TARGET) $(irc_TARGET) +mod_ra_LIBS = $(COMMON_LIBS) $(STD_MOD_LIBS) $(utility_TARGET) $(geoip_TARGET) $(irc_TARGET) PROGRAMS += mod_ra mod_ra: $(mod_ra_TARGET) diff --git a/OpenRA.Game/GameRules/Settings.cs b/OpenRA.Game/GameRules/Settings.cs index 89ee438400..2133b20e91 100644 --- a/OpenRA.Game/GameRules/Settings.cs +++ b/OpenRA.Game/GameRules/Settings.cs @@ -162,6 +162,21 @@ namespace OpenRA.GameRules public string CycleTabsKey = "tab"; } + public class IrcSettings + { + public string Hostname = "irc.open-ra.org"; + public int Port = 6667; + public string Nickname = null; + public string Username = "openra"; + public string Realname = null; + public string DefaultNickname = "Newbie"; + public string Channel = "global"; + public string TimestampFormat = "HH:mm:ss"; + public int ReconnectDelay = 10000; + public int ConnectionTimeout = 300000; + public bool Debug = false; + public bool ConnectAutomatically = false; + } public class Settings { @@ -174,6 +189,7 @@ namespace OpenRA.GameRules public ServerSettings Server = new ServerSettings(); public DebugSettings Debug = new DebugSettings(); public KeySettings Keys = new KeySettings(); + public IrcSettings Irc = new IrcSettings(); public Dictionary Sections; @@ -189,6 +205,7 @@ namespace OpenRA.GameRules {"Server", Server}, {"Debug", Debug}, {"Keys", Keys}, + {"Irc", Irc} }; // Override fieldloader to ignore invalid entries diff --git a/OpenRA.Irc/Channel.cs b/OpenRA.Irc/Channel.cs new file mode 100644 index 0000000000..aec534758a --- /dev/null +++ b/OpenRA.Irc/Channel.cs @@ -0,0 +1,27 @@ +#region Copyright & License Information +/* + * Copyright 2007-2013 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 OpenRA.FileFormats.Primitives; + +namespace OpenRA.Irc +{ + public class Channel + { + public readonly string Name; + public readonly ObservableSortedDictionary Users = new ObservableSortedDictionary(StringComparer.OrdinalIgnoreCase); + public Topic Topic = new Topic(); + + public Channel(string name) + { + Name = name; + } + } +} diff --git a/OpenRA.Irc/IrcClient.cs b/OpenRA.Irc/IrcClient.cs new file mode 100644 index 0000000000..306678c305 --- /dev/null +++ b/OpenRA.Irc/IrcClient.cs @@ -0,0 +1,402 @@ +#region Copyright & License Information +/* + * Copyright 2007-2013 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.IO; +using System.Linq; +using System.Net.Sockets; +using System.Threading; +using OpenRA.FileFormats.Primitives; + +namespace OpenRA.Irc +{ + public class IrcClient : IDisposable + { + public static readonly IrcClient Instance = new IrcClient(); + + public static string MainChannel { get { return '#' + Game.Settings.Irc.Channel; } } + + public static void AddHistory(string line) + { + Instance.History.Add("{0} {1}".F(DateTime.Now.ToString(Game.Settings.Irc.TimestampFormat), line)); + } + + public static void AddMessage(string nickname, string message) + { + AddHistory("{0}: {1}".F(nickname, message)); + } + + public static void AddNotice(string nickname, string message) + { + AddHistory("-{0}- {1}".F(nickname, message)); + } + + public static void AddSelfNotice(string nickname, string message) + { + AddHistory("-> -{0}- {1}".F(nickname, message)); + } + + public static void AddAction(string nickname, string message) + { + AddHistory("* {0} {1}".F(nickname, message)); + } + + static void InstanceInitialize() + { + var s = Game.Settings.Irc; + + Instance.OnPublicMessage += l => + { + var action = IrcUtils.FromAction(l.Message); + if (action != null) + AddAction(l.Prefix.Nickname, action); + else + AddMessage(l.Prefix.Nickname, l.Message); + }; + Instance.OnPrivateMessage += l => + { + var ctcp = IrcUtils.FromCtcp(l.Message); + if (ctcp == null) + return; + + var split = ctcp.Split(new[] { ' ' }, 2); + var command = split[0]; + if (command.EqualsIC("VERSION")) + { + var mod = Game.CurrentMods.Values.FirstOrDefault(); + if (mod == null) + return; + Instance.CtcpRespond(l.Prefix.Nickname, command, "{0}: {1}".F(mod.Title, mod.Version)); + } + }; + Instance.OnPrivateNotice += l => + { + if (l.Target == "*") // Drop pre-register notices + return; + AddNotice(l.Prefix.Nickname, l.Message); + }; + Instance.OnRegister += l => + { + Instance.Join(MainChannel); + Game.Settings.Irc.Nickname = Instance.LocalUser.Nickname; + Game.Settings.Save(); + }; + Instance.OnConnecting += () => AddHistory("Connecting"); + Instance.OnConnect += () => AddHistory("Connected"); + Instance.OnPart += l => AddHistory("{0} left{1}".F(l.Prefix.Nickname, l.Message != null ? ": " + l.Message : "")); + Instance.OnJoin += l => AddHistory("{0} joined".F(l.Prefix.Nickname)); + Instance.OnQuit += l => AddHistory("{0} quit{1}".F(l.Prefix.Nickname, l.Message != null ? ": " + l.Message : "")); + Instance.OnKick += l => AddHistory("{0} kicked {1}{2}".F(l.Prefix.Nickname, l.KickeeNickname, l.Message != null ? ": " + l.Message : "")); + Instance.OnNicknameSet += l => + { + AddHistory("{0} set their nickname to {1}".F(l.Prefix.Nickname, l.NewNickname)); + if (l.NewNickname == Instance.LocalUser.Nickname) + { + Instance.Nickname = l.NewNickname; + Game.Settings.Irc.Nickname = l.NewNickname; + Game.Settings.Save(); + } + }; + Instance.OnTopicSet += l => AddHistory("{0} set the topic to {1}".F(l.Prefix.Nickname, l.Message)); + Instance.OnNumeric += l => + { + if (l.Numeric == NumericCommand.RPL_TOPIC) + { + var topic = Instance.GetChannel(MainChannel).Topic; + AddHistory("Topic is {0}".F(topic.Message)); + } + else if (l.Numeric == NumericCommand.RPL_TOPICWHOTIME) + { + var topic = Instance.GetChannel(MainChannel).Topic; + AddHistory("Topic set by {0} at {1}".F(topic.Author.Nickname, topic.Time.ToLocalTime())); + } + else if (l.Numeric == NumericCommand.RPL_NOTOPIC) + AddHistory("No topic is set"); + else if (l.Numeric == NumericCommand.ERR_NICKNAMEINUSE) + AddHistory("Nickname {0} is already in use".F(l.AltTarget)); + else if (l.Numeric == NumericCommand.ERR_ERRONEUSNICKNAME) + AddHistory("Nickname {0} is erroneus".F(l.AltTarget)); + }; + Instance.OnDisconnect += () => + { + if (Instance.ConnectionFailure != null) + { + AddHistory("Disconnected: {0}".F(Instance.ConnectionFailure.Message)); + if (s.ReconnectDelay >= 0) + { + AddHistory("Reconnecting in {0} seconds".F(s.ReconnectDelay / 1000)); + Instance.ConnectionState = IrcConnectionState.Reconnecting; + Game.RunAfterDelay(s.ReconnectDelay, () => + { + if (Instance.IsReconnecting) + Instance.Connect(Instance.Hostname, Instance.Port, Instance.ConnectionTimeout, Instance.Nickname, Instance.Username, Instance.Realname); + }); + } + } + else + AddHistory("Disconnected"); + }; + Instance.OnLineRead += l => + { + if (s.Debug) + AddHistory(l.RawString); + }; + Game.OnQuit += Instance.Disconnect; + } + + static IrcClient() + { + Log.AddChannel("irc", "irc.log"); + InstanceInitialize(); + } + + public readonly ObservableCollection History = new ObservableCollection(); + + IrcConnection connection; + Thread thread; + public IrcConnectionState ConnectionState { get; private set; } + public IrcClientUser LocalUser { get; private set; } + public Exception ConnectionFailure { get; private set; } + + public string Hostname { get; private set; } + public int Port { get; private set; } + public int ConnectionTimeout { get; private set; } + public string Nickname { get; private set; } + public string Username { get; private set; } + public string Realname { get; private set; } + + public bool IsConnected + { + get { return ConnectionState == IrcConnectionState.Connected; } + } + + public bool IsReconnecting + { + get { return ConnectionState == IrcConnectionState.Reconnecting; } + } + + public IrcClient() + { + ConnectionState = IrcConnectionState.Disconnected; + } + + public void Connect(string hostname, int port, int connectionTimeout, string nickname, string username, string realname) + { + ConnectionFailure = null; + if (IsConnected) + Disconnect(); + + Hostname = hostname; + Port = port; + ConnectionTimeout = connectionTimeout; + Nickname = nickname; + Username = username; + Realname = realname; + + thread = new Thread(() => + { + try + { + ConnectionState = IrcConnectionState.Connecting; + LocalUser = new IrcClientUser(this); + connection = new IrcConnection(); + OnConnecting(); + connection.Connect(hostname, port, connectionTimeout); + ConnectionState = IrcConnectionState.Connected; + OnConnect(); + SetNickname(nickname); + SetUser(username, realname); + ProcessLines(); + } + catch (Exception e) + { + Log.Write("irc", e.ToString()); + if (e is SocketException || e is IOException) + ConnectionFailure = e; + } + finally + { + Disconnect(); + } + }) { IsBackground = true }; + thread.Start(); + } + + public void WriteLine(string format, params object[] args) + { + try + { + connection.WriteLine(format, args); + } + catch (Exception e) + { + Log.Write("irc", e.ToString()); + if (e is SocketException || e is IOException) + ConnectionFailure = e; + Disconnect(); + } + } + + public void Disconnect() + { + if (!IsConnected || IsReconnecting) + { + ConnectionState = IrcConnectionState.Disconnected; + return; + } + + ConnectionState = IrcConnectionState.Disconnecting; + OnDisconnecting(); + connection.Close(); + ConnectionState = IrcConnectionState.Disconnected; + OnDisconnect(); + LocalUser = null; + connection = null; + } + + void IDisposable.Dispose() + { + Disconnect(); + } + + void ProcessLines() + { + string line; + while (IsConnected && (line = connection.ReadLine()) != null) + ProcessLine(line); + } + + void ProcessLine(string line) + { + if (string.IsNullOrEmpty(line)) + return; + + var l = new Line(this, line); + OnLineRead(l); + + int numeric; + if (int.TryParse(l.Command, out numeric)) + { + var nl = new NumericLine(l, numeric); + LocalUser.OnNumeric(nl); + OnNumeric(nl); + switch (nl.Numeric) + { + case NumericCommand.RPL_WELCOME: + OnRegister(nl); + break; + case NumericCommand.RPL_ENDOFNAMES: + OnSync(nl); + break; + } + } + else + { + switch (l.Command) + { + case "PING": + Pong(l.Message); + OnPing(l); + break; + case "PRIVMSG": + if (IrcUtils.IsChannel(l.Target)) + OnPublicMessage(l); + else + OnPrivateMessage(l); + break; + case "NOTICE": + if (IrcUtils.IsChannel(l.Target)) + OnPublicNotice(l); + else + OnPrivateNotice(l); + break; + case "JOIN": + var jl = new JoinLine(l); + LocalUser.OnJoin(jl); + OnJoin(jl); + break; + case "PART": + LocalUser.OnPart(l); + OnPart(l); + break; + case "NICK": + var nsl = new NicknameSetLine(l); + LocalUser.OnNicknameSet(nsl); + OnNicknameSet(nsl); + break; + case "QUIT": + OnQuit(l); + LocalUser.OnQuit(l); + break; + case "KICK": + var kl = new KickLine(l); + LocalUser.OnKick(kl); + OnKick(kl); + break; + case "TOPIC": + LocalUser.OnTopicSet(l); + OnTopicSet(l); + break; + } + } + } + + public event Action OnRegister = l => { }; + public event Action OnSync = l => { }; + public event Action OnLineRead = _ => { }; + public event Action OnConnect = () => { }; + public event Action OnConnecting = () => { }; + public event Action OnDisconnect = () => { }; + public event Action OnDisconnecting = () => { }; + public event Action OnPublicMessage = _ => { }; + public event Action OnPublicNotice = _ => { }; + public event Action OnPrivateMessage = _ => { }; + public event Action OnPrivateNotice = _ => { }; + public event Action OnJoin = _ => { }; + public event Action OnPart = _ => { }; + public event Action OnNicknameSet = _ => { }; + public event Action OnQuit = _ => { }; + public event Action OnPing = _ => { }; + public event Action OnNumeric = _ => { }; + public event Action OnKick = _ => { }; + public event Action OnTopicSet = _ => { }; + + public void SetNickname(string nickname) { WriteLine("NICK {0}", nickname); } + public void SetUser(string username, string realname) { WriteLine("USER {0} 0 * :{1}", username, realname); } + public void Join(string channel) { WriteLine("JOIN {0}", channel); } + public void Part(string channel) { WriteLine("PART {0}", channel); } + public void Message(string target, string message) { WriteLine("PRIVMSG {0} :{1}", target, message); } + public void Notice(string target, string message) { WriteLine("NOTICE {0} :{1}", target, message); } + public void Pong(string reply) { WriteLine("PONG :{0}", reply); } + public void CtcpRequest(string target, string command, string request) { Message(target, IrcUtils.ToCtcp("{0} {1}".F(command, request))); } + public void CtcpRespond(string target, string command, string response) { Notice(target, IrcUtils.ToCtcp("{0} {1}".F(command, response))); } + public void Act(string target, string message) { Message(target, IrcUtils.ToAction(message)); } + public void GetTopic(string channel) { WriteLine("TOPIC {0}", channel); } + public void Quit(string message) { WriteLine("QUIT :{0}", message); } + + public Channel GetChannel(string channel) + { + if (!IsConnected) + return null; + Channel c; + LocalUser.Channels.TryGetValue(channel, out c); + return c; + } + } + + public enum IrcConnectionState + { + Disconnected, + Connected, + Disconnecting, + Connecting, + Reconnecting + } +} diff --git a/OpenRA.Irc/IrcClientUser.cs b/OpenRA.Irc/IrcClientUser.cs new file mode 100644 index 0000000000..b6f054164b --- /dev/null +++ b/OpenRA.Irc/IrcClientUser.cs @@ -0,0 +1,112 @@ +#region Copyright & License Information +/* + * Copyright 2007-2013 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.Linq; +using OpenRA.FileFormats.Primitives; + +namespace OpenRA.Irc +{ + public class IrcClientUser : User + { + public readonly ObservableDictionary Channels = new ObservableDictionary(StringComparer.OrdinalIgnoreCase); + public readonly IrcClient Client; + + public IrcClientUser(IrcClient client) + { + Client = client; + } + + public void OnNumeric(NumericLine line) + { + switch (line.Numeric) + { + case NumericCommand.RPL_WELCOME: + new User(line.Message.Substring(line.Message.LastIndexOf(' ') + 1)).CopyTo(this); + break; + case NumericCommand.RPL_NAMREPLY: + { + var channel = line.GetChannel(); + var nicknames = line.Message.Replace("~", "").Replace("&", "").Replace("@", "").Replace("%", "").Replace("+", "").Split(' '); + + foreach (var nickname in nicknames.Where(n => !channel.Users.ContainsKey(n))) + channel.Users.Add(nickname, new User { Nickname = nickname }); + } + break; + case NumericCommand.RPL_TOPIC: + line.GetChannel().Topic.Message = line.Message; + break; + case NumericCommand.RPL_TOPICWHOTIME: + { + var topic = line.GetChannel().Topic; + topic.Author = new User(line[4]); + topic.Time = IrcUtils.DateTimeFromUnixTime(int.Parse(line[5])); + } + break; + case NumericCommand.ERR_NICKNAMEINUSE: + if (line.Target == "*") // no nickname set yet + Client.SetNickname(Client.Nickname + new Random().Next(10000, 99999)); + break; + } + } + + public void OnJoin(Line line) + { + if (line.PrefixIsSelf()) + Channels.Add(line.Target, new Channel(line.Target)); + + line.GetChannel().Users.Add(line.Prefix.Nickname, new User(line.Prefix)); + } + + public void OnPart(Line line) + { + line.GetChannel().Users.Remove(line.Prefix.Nickname); + + if (line.PrefixIsSelf()) + Channels.Remove(line.Target); + } + + public void OnNicknameSet(NicknameSetLine line) + { + if (line.PrefixIsSelf()) + Nickname = line.NewNickname; + + foreach (var channel in Channels.Values.Where(c => c.Users.ContainsKey(line.Prefix.Nickname))) + { + var user = channel.Users[line.Prefix.Nickname]; + channel.Users.Remove(line.Prefix.Nickname); + user.Nickname = line.NewNickname; + channel.Users.Add(line.NewNickname, user); + } + } + + public void OnQuit(Line line) + { + foreach (var channel in Channels) + channel.Value.Users.Remove(line.Prefix.Nickname); + } + + public void OnKick(KickLine line) + { + line.GetChannel().Users.Remove(line.KickeeNickname); + + if (line.KickeeNickname.EqualsIC(Nickname)) + Channels.Remove(line.Target); + } + + public void OnTopicSet(Line line) + { + var topic = line.GetChannel().Topic; + topic.Message = line.Message; + topic.Author = line.Prefix; + topic.Time = DateTime.UtcNow; + } + } +} diff --git a/OpenRA.Irc/IrcConnection.cs b/OpenRA.Irc/IrcConnection.cs new file mode 100644 index 0000000000..ae9679f83b --- /dev/null +++ b/OpenRA.Irc/IrcConnection.cs @@ -0,0 +1,83 @@ +#region Copyright & License Information +/* + * Copyright 2007-2013 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.IO; +using System.Net.Sockets; + +namespace OpenRA.Irc +{ + public class IrcConnection : IDisposable + { + TcpClient socket; + Stream stream; + StreamWriter writer; + StreamReader reader; + bool disposed; + + public void Connect(string hostname, int port, int connectionTimeout) + { + CheckDisposed(); + if (socket != null && socket.Connected) + throw new InvalidOperationException("Socket already connected"); + + socket = new TcpClient(hostname, port); + socket.ReceiveTimeout = socket.SendTimeout = connectionTimeout; + stream = socket.GetStream(); + writer = new StreamWriter(stream) { AutoFlush = true }; + reader = new StreamReader(stream); + } + + public void WriteLine(string format, params object[] args) + { + CheckDisposed(); + writer.WriteLine(format, args); + } + + public string ReadLine() + { + CheckDisposed(); + return reader.ReadLine(); + } + + public void Close() + { + CloseImpl(); + GC.SuppressFinalize(this); + } + + void CloseImpl() + { + if (disposed) return; + + disposed = true; + if (socket != null) socket.Close(); + if (stream != null) stream.Close(); + if (writer != null) writer.Close(); + if (reader != null) reader.Close(); + } + + void IDisposable.Dispose() + { + Close(); + } + + ~IrcConnection() + { + CloseImpl(); + } + + void CheckDisposed() + { + if (disposed) + throw new ObjectDisposedException(GetType().FullName); + } + } +} diff --git a/OpenRA.Irc/IrcUtils.cs b/OpenRA.Irc/IrcUtils.cs new file mode 100644 index 0000000000..d896496ac3 --- /dev/null +++ b/OpenRA.Irc/IrcUtils.cs @@ -0,0 +1,67 @@ +#region Copyright & License Information +/* + * Copyright 2007-2013 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.Linq; + +namespace OpenRA.Irc +{ + public static class IrcUtils + { + public static bool IsChannel(string s) + { + return !string.IsNullOrEmpty(s) && s[0] == '#'; + } + + public static bool IsNickname(string s) + { + return !string.IsNullOrEmpty(s) && (char.IsLetter(s[0]) || NicknameSpecialChars.Contains(s[0])) + && s.Substring(1).All(c => char.IsLetterOrDigit(c) || NicknameSpecialChars.Contains(c) || c == '-'); + } + + const string NicknameSpecialChars = @"[]\`_^{|}"; + + public static DateTime DateTimeFromUnixTime(int seconds) + { + return new DateTime(1970, 1, 1).AddSeconds(seconds); + } + + public static bool EqualsIC(this string a, string b) + { + return a.Equals(b, StringComparison.OrdinalIgnoreCase); + } + + public static string FromCtcp(string message) + { + if (message.Length < 2 || !message.StartsWith("\x0001") || !message.EndsWith("\x0001")) + return null; + + return message.Substring(1, message.Length - 2); + } + + public static string ToCtcp(string message) + { + return "\x0001{0}\x0001".F(message); + } + + public static string FromAction(string message) + { + if (!message.StartsWith("\x0001ACTION ") || !message.EndsWith("\x0001")) + return null; + + return message.Substring(8, message.Length - 8 - 1); + } + + public static string ToAction(string message) + { + return "\x0001ACTION {0}\x0001".F(message); + } + } +} diff --git a/OpenRA.Irc/Line.cs b/OpenRA.Irc/Line.cs new file mode 100644 index 0000000000..c21faaf3b6 --- /dev/null +++ b/OpenRA.Irc/Line.cs @@ -0,0 +1,155 @@ +#region Copyright & License Information +/* + * Copyright 2007-2013 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.Linq; + +namespace OpenRA.Irc +{ + public class Line + { + public readonly IrcClient Client; + public readonly string RawString; + public readonly string[] RawStringParts; + public readonly User Prefix; + public readonly string Command; + public string Target { get; protected set; } + public string Message { get; protected set; } + + public Line(Line line) + { + Client = line.Client; + RawString = line.RawString; + RawStringParts = line.RawStringParts; + Prefix = line.Prefix; + Command = line.Command; + Target = line.Target; + Message = line.Message; + } + + public Line(IrcClient client, string line) + { + RawString = line; + RawStringParts = line.Split(' '); + Client = client; + + if (line[0] == ':') + { + line = line.Substring(1); + var prefixDelim = line.Split(new[] { ' ' }, 2); + Prefix = new User(prefixDelim[0]); + + if (prefixDelim.Length > 1) + { + var messageDelim = prefixDelim[1].Split(new[] { ':' }, 2); + + var args = messageDelim[0].Trim().Split(' '); + + Command = args[0]; + if (args.Length > 1) + Target = args[1]; + + if (messageDelim.Length > 1) + Message = messageDelim[1]; + } + } + else + { + var messageDelim = line.Split(new[] { ':' }, 2); + + var args = messageDelim[0].Trim().Split(' '); + + Command = args[0]; + if (args.Length > 1) + Target = args[1]; + + if (messageDelim.Length > 1) + Message = messageDelim[1]; + } + } + + public virtual Channel GetChannel() + { + return Client.GetChannel(Target); + } + + public string this[int index] + { + get { return RawStringParts[index]; } + } + + public bool PrefixIsSelf() + { + return Client.LocalUser != null && Prefix.Nickname.EqualsIC(Client.LocalUser.Nickname); + } + + public bool TargetIsSelf() + { + return Target != null && Target.EqualsIC(Client.LocalUser.Nickname); + } + } + + public class NicknameSetLine : Line + { + public readonly string NewNickname; + + public NicknameSetLine(Line line) + : base(line) + { + NewNickname = Message; + } + } + + public class NumericLine : Line + { + public readonly NumericCommand Numeric; + public readonly string AltTarget; + + public override Channel GetChannel() + { + if (IrcUtils.IsChannel(AltTarget)) + return Client.GetChannel(AltTarget); + return Client.GetChannel(Target); + } + + public NumericLine(Line line, int numeric) + : base(line) + { + if (!IrcUtils.IsChannel(Target)) + { + var numericParts = line.RawStringParts.Skip(1).TakeWhile(p => !p.StartsWith(":")); + AltTarget = numericParts.LastOrDefault(IrcUtils.IsChannel); + if (AltTarget == null) + AltTarget = numericParts.LastOrDefault(); + } + Numeric = (NumericCommand)numeric; + } + } + + public class JoinLine : Line // for compatibility with certain IRCds + { + public JoinLine(Line line) + : base(line) + { + if (Message != null) // don't overwrite the target if it was already set properly by the IRCd + Target = Message; + } + } + + public class KickLine : Line + { + public readonly string KickeeNickname; + + public KickLine(Line line) + : base(line) + { + KickeeNickname = this[3]; + } + } +} diff --git a/OpenRA.Irc/NumericCommand.cs b/OpenRA.Irc/NumericCommand.cs new file mode 100644 index 0000000000..ac3b33dc56 --- /dev/null +++ b/OpenRA.Irc/NumericCommand.cs @@ -0,0 +1,25 @@ +#region Copyright & License Information +/* + * Copyright 2007-2013 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 + +namespace OpenRA.Irc +{ + public enum NumericCommand + { + Undefined = 0, + RPL_WELCOME = 001, + RPL_NOTOPIC = 331, + RPL_TOPIC = 332, + RPL_TOPICWHOTIME = 333, + RPL_NAMREPLY = 353, + RPL_ENDOFNAMES = 366, + ERR_ERRONEUSNICKNAME = 432, + ERR_NICKNAMEINUSE = 433 + } +} diff --git a/OpenRA.Irc/OpenRA.Irc.csproj b/OpenRA.Irc/OpenRA.Irc.csproj new file mode 100644 index 0000000000..afdc1fca9c --- /dev/null +++ b/OpenRA.Irc/OpenRA.Irc.csproj @@ -0,0 +1,70 @@ + + + + + Debug + AnyCPU + {85B48234-8B31-4BE6-AF9C-665CC6866841} + Library + Properties + OpenRA.Irc + OpenRA.Irc + v3.5 + 512 + + + + + true + full + false + .. + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + {bdaeab25-991e-46a7-af1e-4f0e03358daa} + OpenRA.FileFormats + + + {0dfb103f-2962-400f-8c6d-e2c28ccba633} + OpenRA.Game + + + + + + + + + \ No newline at end of file diff --git a/OpenRA.Irc/Topic.cs b/OpenRA.Irc/Topic.cs new file mode 100644 index 0000000000..b97cb8c4e8 --- /dev/null +++ b/OpenRA.Irc/Topic.cs @@ -0,0 +1,30 @@ +#region Copyright & License Information +/* + * Copyright 2007-2013 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; + +namespace OpenRA.Irc +{ + public class Topic + { + public string Message; + public User Author; + public DateTime Time; + + public Topic() { } + + public Topic(string message, User author, DateTime time) + { + Message = message; + Author = author; + Time = time; + } + } +} diff --git a/OpenRA.Irc/User.cs b/OpenRA.Irc/User.cs new file mode 100644 index 0000000000..d64011fafc --- /dev/null +++ b/OpenRA.Irc/User.cs @@ -0,0 +1,73 @@ +#region Copyright & License Information +/* + * Copyright 2007-2013 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; + +namespace OpenRA.Irc +{ + public class User + { + public string Nickname; + public string Username; + public string Hostname; + + public User() { } + + public User(User user) + { + Nickname = user.Nickname; + Username = user.Username; + Hostname = user.Hostname; + } + + public void CopyTo(User user) + { + user.Nickname = Nickname; + user.Username = Username; + user.Hostname = Hostname; + } + + public User(string prefix) + { + if (string.IsNullOrEmpty(prefix)) + throw new ArgumentException(); + + var ex = prefix.IndexOf('!'); + var at = prefix.IndexOf('@'); + + if (ex >= 0 && at >= 0 && at < ex) + throw new ArgumentException("Bogus input string: @ before !"); + + if (ex >= 0) + { + Nickname = prefix.Substring(0, ex); + if (at >= 0) + { + Username = prefix.Substring(ex + 1, at - ex - 1); + Hostname = prefix.Substring(at + 1); + } + else + Username = prefix.Substring(ex + 1); + } + else + Nickname = prefix; + } + + public override string ToString() + { + var ret = "" + Nickname; + if (Username != null) + ret += "!" + Username; + if (Hostname != null) + ret += "@" + Hostname; + return ret; + } + } +} diff --git a/OpenRA.sln b/OpenRA.sln index 1f7cfd7087..2f304547f8 100644 --- a/OpenRA.sln +++ b/OpenRA.sln @@ -37,6 +37,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenRA.Mods.TS", "OpenRA.Mo EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GeoIP", "GeoIP\GeoIP.csproj", "{021DDD6A-A608-424C-9A9A-252D8A9989E0}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenRA.Irc", "OpenRA.Irc\OpenRA.Irc.csproj", "{85B48234-8B31-4BE6-AF9C-665CC6866841}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -45,85 +47,6 @@ Global Release|Mixed Platforms = Release|Mixed Platforms EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {00038B75-405B-44F5-8691-BD2546DBE224}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {00038B75-405B-44F5-8691-BD2546DBE224}.Debug|Any CPU.Build.0 = Debug|Any CPU - {00038B75-405B-44F5-8691-BD2546DBE224}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {00038B75-405B-44F5-8691-BD2546DBE224}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {00038B75-405B-44F5-8691-BD2546DBE224}.Release|Any CPU.ActiveCfg = Release|Any CPU - {00038B75-405B-44F5-8691-BD2546DBE224}.Release|Any CPU.Build.0 = Release|Any CPU - {00038B75-405B-44F5-8691-BD2546DBE224}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {00038B75-405B-44F5-8691-BD2546DBE224}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {021DDD6A-A608-424C-9A9A-252D8A9989E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {021DDD6A-A608-424C-9A9A-252D8A9989E0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {021DDD6A-A608-424C-9A9A-252D8A9989E0}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {021DDD6A-A608-424C-9A9A-252D8A9989E0}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {021DDD6A-A608-424C-9A9A-252D8A9989E0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {021DDD6A-A608-424C-9A9A-252D8A9989E0}.Release|Any CPU.Build.0 = Release|Any CPU - {021DDD6A-A608-424C-9A9A-252D8A9989E0}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {021DDD6A-A608-424C-9A9A-252D8A9989E0}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {0C4AEC1A-E7D5-4114-8CCD-3EEC82872981}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0C4AEC1A-E7D5-4114-8CCD-3EEC82872981}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0C4AEC1A-E7D5-4114-8CCD-3EEC82872981}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {0C4AEC1A-E7D5-4114-8CCD-3EEC82872981}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {0C4AEC1A-E7D5-4114-8CCD-3EEC82872981}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0C4AEC1A-E7D5-4114-8CCD-3EEC82872981}.Release|Any CPU.Build.0 = Release|Any CPU - {0C4AEC1A-E7D5-4114-8CCD-3EEC82872981}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {0C4AEC1A-E7D5-4114-8CCD-3EEC82872981}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {0DFB103F-2962-400F-8C6D-E2C28CCBA633}.Debug|Any CPU.ActiveCfg = Debug|x86 - {0DFB103F-2962-400F-8C6D-E2C28CCBA633}.Debug|Any CPU.Build.0 = Debug|x86 - {0DFB103F-2962-400F-8C6D-E2C28CCBA633}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 - {0DFB103F-2962-400F-8C6D-E2C28CCBA633}.Debug|Mixed Platforms.Build.0 = Debug|x86 - {0DFB103F-2962-400F-8C6D-E2C28CCBA633}.Release|Any CPU.ActiveCfg = Release|x86 - {0DFB103F-2962-400F-8C6D-E2C28CCBA633}.Release|Mixed Platforms.ActiveCfg = Release|x86 - {0DFB103F-2962-400F-8C6D-E2C28CCBA633}.Release|Mixed Platforms.Build.0 = Release|x86 - {1A8E50CC-EE32-4E57-8842-0C39C8EA7541}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1A8E50CC-EE32-4E57-8842-0C39C8EA7541}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1A8E50CC-EE32-4E57-8842-0C39C8EA7541}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {1A8E50CC-EE32-4E57-8842-0C39C8EA7541}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {1A8E50CC-EE32-4E57-8842-0C39C8EA7541}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1A8E50CC-EE32-4E57-8842-0C39C8EA7541}.Release|Any CPU.Build.0 = Release|Any CPU - {1A8E50CC-EE32-4E57-8842-0C39C8EA7541}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {1A8E50CC-EE32-4E57-8842-0C39C8EA7541}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {2881135D-4D62-493E-8F83-5EEE92CCC6BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2881135D-4D62-493E-8F83-5EEE92CCC6BE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2881135D-4D62-493E-8F83-5EEE92CCC6BE}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {2881135D-4D62-493E-8F83-5EEE92CCC6BE}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {2881135D-4D62-493E-8F83-5EEE92CCC6BE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2881135D-4D62-493E-8F83-5EEE92CCC6BE}.Release|Any CPU.Build.0 = Release|Any CPU - {2881135D-4D62-493E-8F83-5EEE92CCC6BE}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {2881135D-4D62-493E-8F83-5EEE92CCC6BE}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {4A8A43B5-A9EF-4ED0-99DD-4BAB10A0DB6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4A8A43B5-A9EF-4ED0-99DD-4BAB10A0DB6E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4A8A43B5-A9EF-4ED0-99DD-4BAB10A0DB6E}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {4A8A43B5-A9EF-4ED0-99DD-4BAB10A0DB6E}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {4A8A43B5-A9EF-4ED0-99DD-4BAB10A0DB6E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4A8A43B5-A9EF-4ED0-99DD-4BAB10A0DB6E}.Release|Any CPU.Build.0 = Release|Any CPU - {4A8A43B5-A9EF-4ED0-99DD-4BAB10A0DB6E}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {4A8A43B5-A9EF-4ED0-99DD-4BAB10A0DB6E}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {52FD9F0B-B209-4ED7-8A32-AC8033363263}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {52FD9F0B-B209-4ED7-8A32-AC8033363263}.Debug|Any CPU.Build.0 = Debug|Any CPU - {52FD9F0B-B209-4ED7-8A32-AC8033363263}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {52FD9F0B-B209-4ED7-8A32-AC8033363263}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {52FD9F0B-B209-4ED7-8A32-AC8033363263}.Release|Any CPU.ActiveCfg = Release|Any CPU - {52FD9F0B-B209-4ED7-8A32-AC8033363263}.Release|Any CPU.Build.0 = Release|Any CPU - {52FD9F0B-B209-4ED7-8A32-AC8033363263}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {52FD9F0B-B209-4ED7-8A32-AC8033363263}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {5457CBF5-4CE4-421E-A8BF-9FD6C9732E1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5457CBF5-4CE4-421E-A8BF-9FD6C9732E1D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5457CBF5-4CE4-421E-A8BF-9FD6C9732E1D}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {5457CBF5-4CE4-421E-A8BF-9FD6C9732E1D}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {5457CBF5-4CE4-421E-A8BF-9FD6C9732E1D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5457CBF5-4CE4-421E-A8BF-9FD6C9732E1D}.Release|Any CPU.Build.0 = Release|Any CPU - {5457CBF5-4CE4-421E-A8BF-9FD6C9732E1D}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {5457CBF5-4CE4-421E-A8BF-9FD6C9732E1D}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {67CF1A10-C5F6-48FA-B1A7-FE83BE4CE2CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {67CF1A10-C5F6-48FA-B1A7-FE83BE4CE2CC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {67CF1A10-C5F6-48FA-B1A7-FE83BE4CE2CC}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {67CF1A10-C5F6-48FA-B1A7-FE83BE4CE2CC}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {67CF1A10-C5F6-48FA-B1A7-FE83BE4CE2CC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {67CF1A10-C5F6-48FA-B1A7-FE83BE4CE2CC}.Release|Any CPU.Build.0 = Release|Any CPU - {67CF1A10-C5F6-48FA-B1A7-FE83BE4CE2CC}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {67CF1A10-C5F6-48FA-B1A7-FE83BE4CE2CC}.Release|Mixed Platforms.Build.0 = Release|Any CPU {BDAEAB25-991E-46A7-AF1E-4F0E03358DAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {BDAEAB25-991E-46A7-AF1E-4F0E03358DAA}.Debug|Any CPU.Build.0 = Debug|Any CPU {BDAEAB25-991E-46A7-AF1E-4F0E03358DAA}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU @@ -132,30 +55,53 @@ Global {BDAEAB25-991E-46A7-AF1E-4F0E03358DAA}.Release|Any CPU.Build.0 = Release|Any CPU {BDAEAB25-991E-46A7-AF1E-4F0E03358DAA}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {BDAEAB25-991E-46A7-AF1E-4F0E03358DAA}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {C0B0465C-6BE2-409C-8770-3A9BF64C4344}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C0B0465C-6BE2-409C-8770-3A9BF64C4344}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C0B0465C-6BE2-409C-8770-3A9BF64C4344}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {C0B0465C-6BE2-409C-8770-3A9BF64C4344}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {C0B0465C-6BE2-409C-8770-3A9BF64C4344}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C0B0465C-6BE2-409C-8770-3A9BF64C4344}.Release|Any CPU.Build.0 = Release|Any CPU - {C0B0465C-6BE2-409C-8770-3A9BF64C4344}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {C0B0465C-6BE2-409C-8770-3A9BF64C4344}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {E9C01A96-C3E2-4772-825B-A740AC513D31}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E9C01A96-C3E2-4772-825B-A740AC513D31}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E9C01A96-C3E2-4772-825B-A740AC513D31}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {E9C01A96-C3E2-4772-825B-A740AC513D31}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {E9C01A96-C3E2-4772-825B-A740AC513D31}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E9C01A96-C3E2-4772-825B-A740AC513D31}.Release|Any CPU.Build.0 = Release|Any CPU - {E9C01A96-C3E2-4772-825B-A740AC513D31}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {E9C01A96-C3E2-4772-825B-A740AC513D31}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {F33337BE-CB69-4B24-850F-07D23E408DDF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F33337BE-CB69-4B24-850F-07D23E408DDF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F33337BE-CB69-4B24-850F-07D23E408DDF}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {F33337BE-CB69-4B24-850F-07D23E408DDF}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {F33337BE-CB69-4B24-850F-07D23E408DDF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F33337BE-CB69-4B24-850F-07D23E408DDF}.Release|Any CPU.Build.0 = Release|Any CPU - {F33337BE-CB69-4B24-850F-07D23E408DDF}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {F33337BE-CB69-4B24-850F-07D23E408DDF}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {0DFB103F-2962-400F-8C6D-E2C28CCBA633}.Debug|Any CPU.ActiveCfg = Debug|x86 + {0DFB103F-2962-400F-8C6D-E2C28CCBA633}.Debug|Any CPU.Build.0 = Debug|x86 + {0DFB103F-2962-400F-8C6D-E2C28CCBA633}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {0DFB103F-2962-400F-8C6D-E2C28CCBA633}.Debug|Mixed Platforms.Build.0 = Debug|x86 + {0DFB103F-2962-400F-8C6D-E2C28CCBA633}.Release|Any CPU.ActiveCfg = Release|x86 + {0DFB103F-2962-400F-8C6D-E2C28CCBA633}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {0DFB103F-2962-400F-8C6D-E2C28CCBA633}.Release|Mixed Platforms.Build.0 = Release|x86 + {4A8A43B5-A9EF-4ED0-99DD-4BAB10A0DB6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4A8A43B5-A9EF-4ED0-99DD-4BAB10A0DB6E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4A8A43B5-A9EF-4ED0-99DD-4BAB10A0DB6E}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {4A8A43B5-A9EF-4ED0-99DD-4BAB10A0DB6E}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {4A8A43B5-A9EF-4ED0-99DD-4BAB10A0DB6E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4A8A43B5-A9EF-4ED0-99DD-4BAB10A0DB6E}.Release|Any CPU.Build.0 = Release|Any CPU + {4A8A43B5-A9EF-4ED0-99DD-4BAB10A0DB6E}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {4A8A43B5-A9EF-4ED0-99DD-4BAB10A0DB6E}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {2881135D-4D62-493E-8F83-5EEE92CCC6BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2881135D-4D62-493E-8F83-5EEE92CCC6BE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2881135D-4D62-493E-8F83-5EEE92CCC6BE}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {2881135D-4D62-493E-8F83-5EEE92CCC6BE}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {2881135D-4D62-493E-8F83-5EEE92CCC6BE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2881135D-4D62-493E-8F83-5EEE92CCC6BE}.Release|Any CPU.Build.0 = Release|Any CPU + {2881135D-4D62-493E-8F83-5EEE92CCC6BE}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {2881135D-4D62-493E-8F83-5EEE92CCC6BE}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {67CF1A10-C5F6-48FA-B1A7-FE83BE4CE2CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {67CF1A10-C5F6-48FA-B1A7-FE83BE4CE2CC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {67CF1A10-C5F6-48FA-B1A7-FE83BE4CE2CC}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {67CF1A10-C5F6-48FA-B1A7-FE83BE4CE2CC}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {67CF1A10-C5F6-48FA-B1A7-FE83BE4CE2CC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {67CF1A10-C5F6-48FA-B1A7-FE83BE4CE2CC}.Release|Any CPU.Build.0 = Release|Any CPU + {67CF1A10-C5F6-48FA-B1A7-FE83BE4CE2CC}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {67CF1A10-C5F6-48FA-B1A7-FE83BE4CE2CC}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {00038B75-405B-44F5-8691-BD2546DBE224}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {00038B75-405B-44F5-8691-BD2546DBE224}.Debug|Any CPU.Build.0 = Debug|Any CPU + {00038B75-405B-44F5-8691-BD2546DBE224}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {00038B75-405B-44F5-8691-BD2546DBE224}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {00038B75-405B-44F5-8691-BD2546DBE224}.Release|Any CPU.ActiveCfg = Release|Any CPU + {00038B75-405B-44F5-8691-BD2546DBE224}.Release|Any CPU.Build.0 = Release|Any CPU + {00038B75-405B-44F5-8691-BD2546DBE224}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {00038B75-405B-44F5-8691-BD2546DBE224}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {1A8E50CC-EE32-4E57-8842-0C39C8EA7541}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1A8E50CC-EE32-4E57-8842-0C39C8EA7541}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1A8E50CC-EE32-4E57-8842-0C39C8EA7541}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {1A8E50CC-EE32-4E57-8842-0C39C8EA7541}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {1A8E50CC-EE32-4E57-8842-0C39C8EA7541}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1A8E50CC-EE32-4E57-8842-0C39C8EA7541}.Release|Any CPU.Build.0 = Release|Any CPU + {1A8E50CC-EE32-4E57-8842-0C39C8EA7541}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {1A8E50CC-EE32-4E57-8842-0C39C8EA7541}.Release|Mixed Platforms.Build.0 = Release|Any CPU {F9FA4D9F-2302-470A-8A07-6E37F488C124}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F9FA4D9F-2302-470A-8A07-6E37F488C124}.Debug|Any CPU.Build.0 = Debug|Any CPU {F9FA4D9F-2302-470A-8A07-6E37F488C124}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU @@ -164,6 +110,70 @@ Global {F9FA4D9F-2302-470A-8A07-6E37F488C124}.Release|Any CPU.Build.0 = Release|Any CPU {F9FA4D9F-2302-470A-8A07-6E37F488C124}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {F9FA4D9F-2302-470A-8A07-6E37F488C124}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {F33337BE-CB69-4B24-850F-07D23E408DDF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F33337BE-CB69-4B24-850F-07D23E408DDF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F33337BE-CB69-4B24-850F-07D23E408DDF}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {F33337BE-CB69-4B24-850F-07D23E408DDF}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {F33337BE-CB69-4B24-850F-07D23E408DDF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F33337BE-CB69-4B24-850F-07D23E408DDF}.Release|Any CPU.Build.0 = Release|Any CPU + {F33337BE-CB69-4B24-850F-07D23E408DDF}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {F33337BE-CB69-4B24-850F-07D23E408DDF}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {0C4AEC1A-E7D5-4114-8CCD-3EEC82872981}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0C4AEC1A-E7D5-4114-8CCD-3EEC82872981}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0C4AEC1A-E7D5-4114-8CCD-3EEC82872981}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {0C4AEC1A-E7D5-4114-8CCD-3EEC82872981}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {0C4AEC1A-E7D5-4114-8CCD-3EEC82872981}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0C4AEC1A-E7D5-4114-8CCD-3EEC82872981}.Release|Any CPU.Build.0 = Release|Any CPU + {0C4AEC1A-E7D5-4114-8CCD-3EEC82872981}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {0C4AEC1A-E7D5-4114-8CCD-3EEC82872981}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {E9C01A96-C3E2-4772-825B-A740AC513D31}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E9C01A96-C3E2-4772-825B-A740AC513D31}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E9C01A96-C3E2-4772-825B-A740AC513D31}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {E9C01A96-C3E2-4772-825B-A740AC513D31}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {E9C01A96-C3E2-4772-825B-A740AC513D31}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E9C01A96-C3E2-4772-825B-A740AC513D31}.Release|Any CPU.Build.0 = Release|Any CPU + {E9C01A96-C3E2-4772-825B-A740AC513D31}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {E9C01A96-C3E2-4772-825B-A740AC513D31}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {52FD9F0B-B209-4ED7-8A32-AC8033363263}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {52FD9F0B-B209-4ED7-8A32-AC8033363263}.Debug|Any CPU.Build.0 = Debug|Any CPU + {52FD9F0B-B209-4ED7-8A32-AC8033363263}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {52FD9F0B-B209-4ED7-8A32-AC8033363263}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {52FD9F0B-B209-4ED7-8A32-AC8033363263}.Release|Any CPU.ActiveCfg = Release|Any CPU + {52FD9F0B-B209-4ED7-8A32-AC8033363263}.Release|Any CPU.Build.0 = Release|Any CPU + {52FD9F0B-B209-4ED7-8A32-AC8033363263}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {52FD9F0B-B209-4ED7-8A32-AC8033363263}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {C0B0465C-6BE2-409C-8770-3A9BF64C4344}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C0B0465C-6BE2-409C-8770-3A9BF64C4344}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C0B0465C-6BE2-409C-8770-3A9BF64C4344}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {C0B0465C-6BE2-409C-8770-3A9BF64C4344}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {C0B0465C-6BE2-409C-8770-3A9BF64C4344}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C0B0465C-6BE2-409C-8770-3A9BF64C4344}.Release|Any CPU.Build.0 = Release|Any CPU + {C0B0465C-6BE2-409C-8770-3A9BF64C4344}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {C0B0465C-6BE2-409C-8770-3A9BF64C4344}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {5457CBF5-4CE4-421E-A8BF-9FD6C9732E1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5457CBF5-4CE4-421E-A8BF-9FD6C9732E1D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5457CBF5-4CE4-421E-A8BF-9FD6C9732E1D}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {5457CBF5-4CE4-421E-A8BF-9FD6C9732E1D}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {5457CBF5-4CE4-421E-A8BF-9FD6C9732E1D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5457CBF5-4CE4-421E-A8BF-9FD6C9732E1D}.Release|Any CPU.Build.0 = Release|Any CPU + {5457CBF5-4CE4-421E-A8BF-9FD6C9732E1D}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {5457CBF5-4CE4-421E-A8BF-9FD6C9732E1D}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {021DDD6A-A608-424C-9A9A-252D8A9989E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {021DDD6A-A608-424C-9A9A-252D8A9989E0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {021DDD6A-A608-424C-9A9A-252D8A9989E0}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {021DDD6A-A608-424C-9A9A-252D8A9989E0}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {021DDD6A-A608-424C-9A9A-252D8A9989E0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {021DDD6A-A608-424C-9A9A-252D8A9989E0}.Release|Any CPU.Build.0 = Release|Any CPU + {021DDD6A-A608-424C-9A9A-252D8A9989E0}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {021DDD6A-A608-424C-9A9A-252D8A9989E0}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {85B48234-8B31-4BE6-AF9C-665CC6866841}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {85B48234-8B31-4BE6-AF9C-665CC6866841}.Debug|Any CPU.Build.0 = Debug|Any CPU + {85B48234-8B31-4BE6-AF9C-665CC6866841}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {85B48234-8B31-4BE6-AF9C-665CC6866841}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {85B48234-8B31-4BE6-AF9C-665CC6866841}.Release|Any CPU.ActiveCfg = Release|Any CPU + {85B48234-8B31-4BE6-AF9C-665CC6866841}.Release|Any CPU.Build.0 = Release|Any CPU + {85B48234-8B31-4BE6-AF9C-665CC6866841}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {85B48234-8B31-4BE6-AF9C-665CC6866841}.Release|Mixed Platforms.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(MonoDevelopProperties) = preSolution StartupItem = OpenRA.Game\OpenRA.Game.csproj From c6dc0e8c8b20ea688460f3cbc8255137eafd131c Mon Sep 17 00:00:00 2001 From: ScottNZ Date: Sun, 22 Sep 2013 17:59:38 +1200 Subject: [PATCH 5/6] Add IRC interface to ra/cnc server browsers --- OpenRA.Game/Widgets/ScrollPanelWidget.cs | 1 - OpenRA.Game/Widgets/TextFieldWidget.cs | 2 +- OpenRA.Mods.RA/OpenRA.Mods.RA.csproj | 5 + OpenRA.Mods.RA/Widgets/Logic/IrcLogic.cs | 252 ++++++++++++++++++ .../Widgets/Logic/ServerBrowserLogic.cs | 2 + mods/cnc/chrome/irc.yaml | 68 +++++ mods/cnc/chrome/serverbrowser.yaml | 38 +-- mods/cnc/mod.yaml | 1 + mods/d2k/mod.yaml | 1 + mods/ra/chrome/irc.yaml | 68 +++++ mods/ra/chrome/serverbrowser.yaml | 23 +- mods/ra/mod.yaml | 1 + mods/ts/mod.yaml | 1 + 13 files changed, 434 insertions(+), 29 deletions(-) create mode 100644 OpenRA.Mods.RA/Widgets/Logic/IrcLogic.cs create mode 100644 mods/cnc/chrome/irc.yaml create mode 100644 mods/ra/chrome/irc.yaml diff --git a/OpenRA.Game/Widgets/ScrollPanelWidget.cs b/OpenRA.Game/Widgets/ScrollPanelWidget.cs index b3dbd1d74c..e4308bf4e5 100644 --- a/OpenRA.Game/Widgets/ScrollPanelWidget.cs +++ b/OpenRA.Game/Widgets/ScrollPanelWidget.cs @@ -46,7 +46,6 @@ namespace OpenRA.Widgets base.RemoveChildren(); } - public override void AddChild(Widget child) { // Initial setup of margins/height diff --git a/OpenRA.Game/Widgets/TextFieldWidget.cs b/OpenRA.Game/Widgets/TextFieldWidget.cs index a4cfe4086f..cb386340a3 100644 --- a/OpenRA.Game/Widgets/TextFieldWidget.cs +++ b/OpenRA.Game/Widgets/TextFieldWidget.cs @@ -33,7 +33,7 @@ namespace OpenRA.Widgets public Func OnTabKey = () => false; public Func OnEscKey = () => false; public Action OnLoseFocus = () => { }; - public int CursorPosition { get; protected set; } + public int CursorPosition { get; set; } public Func IsDisabled = () => false; public Color TextColor = Color.White; diff --git a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj index 804b7179f2..eb31b519d4 100644 --- a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj +++ b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj @@ -380,6 +380,7 @@ + @@ -482,6 +483,10 @@ OpenRA.Game False + + {85b48234-8b31-4be6-af9c-665cc6866841} + OpenRA.Irc + {F33337BE-CB69-4B24-850F-07D23E408DDF} OpenRA.Utility diff --git a/OpenRA.Mods.RA/Widgets/Logic/IrcLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/IrcLogic.cs new file mode 100644 index 0000000000..86f7a0e0da --- /dev/null +++ b/OpenRA.Mods.RA/Widgets/Logic/IrcLogic.cs @@ -0,0 +1,252 @@ +#region Copyright & License Information +/* + * Copyright 2007-2013 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.Generic; +using System.Linq; +using OpenRA.Irc; +using OpenRA.Widgets; + +namespace OpenRA.Mods.RA.Widgets.Logic +{ + class IrcLogic + { + TextFieldWidget inputBox; + TextFieldWidget nicknameBox; + Widget connectBG; + Widget ircContainer; + + [ObjectCreator.UseCtor] + public IrcLogic(Widget widget) + { + var historyPanel = widget.Get("HISTORY_PANEL"); + var historyTemplate = widget.Get("HISTORY_TEMPLATE"); + var nicknamePanel = widget.Get("NICKNAME_PANEL"); + var nicknameTemplate = widget.Get("NICKNAME_TEMPLATE"); + + inputBox = widget.Get("INPUT_BOX"); + inputBox.OnEnterKey = EnterPressed; + inputBox.OnTabKey = TabPressed; + inputBox.IsDisabled = () => IrcClient.Instance.GetChannel(IrcClient.MainChannel) == null; + + nicknameBox = widget.Get("NICKNAME_BOX"); + nicknameBox.Text = ChooseNickname(Game.Settings.Irc.Nickname); + + connectBG = widget.Get("IRC_CONNECT_BG"); + ircContainer = widget.Get("IRC_CONTAINER"); + + widget.Get("DISCONNECT_BUTTON").OnClick = IrcClient.Instance.Disconnect; + + MaybeShowConnectPanel(); + + historyPanel.Bind(IrcClient.Instance.History, item => MakeLabelWidget(historyTemplate, item), LabelItemEquals, true); + + var mainChannel = IrcClient.Instance.GetChannel(IrcClient.MainChannel); + if (mainChannel != null) + nicknamePanel.Bind(mainChannel.Users, item => MakeLabelWidget(nicknameTemplate, item), LabelItemEquals, false); + + IrcClient.Instance.OnSync += l => + { + var channel = l.GetChannel(); + if (channel.Name.EqualsIC(IrcClient.MainChannel)) + nicknamePanel.Bind(channel.Users, item => MakeLabelWidget(nicknameTemplate, item), LabelItemEquals, false); + }; + IrcClient.Instance.OnKick += l => + { + if (l.KickeeNickname.EqualsIC(IrcClient.Instance.LocalUser.Nickname) && l.Target.EqualsIC(IrcClient.MainChannel)) + nicknamePanel.Unbind(); + }; + IrcClient.Instance.OnPart += l => + { + if (l.PrefixIsSelf() && l.Target.EqualsIC(IrcClient.MainChannel)) + nicknamePanel.Unbind(); + }; + IrcClient.Instance.OnDisconnect += () => + { + nicknamePanel.Unbind(); + MaybeShowConnectPanel(); + }; + + commands.Add("me", args => + { + IrcClient.Instance.Act(IrcClient.MainChannel, args); + IrcClient.AddAction(IrcClient.Instance.LocalUser.Nickname, args); + }); + commands.Add("slap", args => + { + IrcClient.Instance.Act(IrcClient.MainChannel, "slaps {0} around a bit with a large trout".F(args)); + IrcClient.AddAction(IrcClient.Instance.LocalUser.Nickname, "slaps {0} around a bit with a large trout".F(args)); + }); + commands.Add("notice", args => + { + var split = args.Split(new[] { ' ' }, 2); + if (split.Length < 2) + { + IrcClient.AddHistory("/notice: Not enough arguments"); + return; + } + IrcClient.Instance.Notice(split[0], split[1]); + IrcClient.AddSelfNotice(split[0], split[1]); + }); + commands.Add("disconnect", args => + { + Game.Settings.Irc.ConnectAutomatically = false; + Game.Settings.Save(); + IrcClient.Instance.Disconnect(); + }); + commands.Add("quit", args => + { + Game.Settings.Irc.ConnectAutomatically = false; + Game.Settings.Save(); + if (IrcClient.Instance.IsConnected) + IrcClient.Instance.Quit(args); + else + IrcClient.Instance.Disconnect(); + }); + commands.Add("nick", args => IrcClient.Instance.SetNickname(args)); + commands.Add("topic", args => IrcClient.Instance.GetTopic(IrcClient.MainChannel)); + } + + void MaybeShowConnectPanel() + { + if (IrcClient.Instance.IsConnected || IrcClient.Instance.IsReconnecting) + { + ircContainer.Visible = true; + connectBG.Visible = false; + return; + } + + if (Game.Settings.Irc.ConnectAutomatically) + { + ircContainer.Visible = true; + connectBG.Visible = false; + Connect(); + return; + } + + ircContainer.Visible = false; + connectBG.Visible = true; + + var connectAutomaticallyCheckBox = connectBG.Get("CONNECT_AUTOMATICALLY_CHECKBOX"); + var connectAutomaticallyChecked = false; + connectAutomaticallyCheckBox.IsChecked = () => connectAutomaticallyChecked; + connectAutomaticallyCheckBox.OnClick = () => connectAutomaticallyChecked ^= true; + + var connectButton = connectBG.Get("CONNECT_BUTTON"); + + connectButton.OnClick = () => + { + ircContainer.Visible = true; + connectBG.Visible = false; + + Game.Settings.Irc.ConnectAutomatically = connectAutomaticallyCheckBox.IsChecked(); + Game.Settings.Save(); + Connect(); + }; + } + + string ChooseNickname(string nickname) + { + if (!IrcUtils.IsNickname(nickname)) + { + nickname = Game.Settings.Player.Name; + if (!IrcUtils.IsNickname(nickname)) + nickname = Game.Settings.Irc.DefaultNickname; + } + return nickname; + } + + void Connect() + { + var nickname = ChooseNickname(nicknameBox.Text); + var s = Game.Settings.Irc; + s.Nickname = nickname; + Game.Settings.Save(); + IrcClient.Instance.Connect(s.Hostname, s.Port, s.ConnectionTimeout, nickname, s.Username ?? nickname, s.Realname ?? nickname); + } + + Widget MakeLabelWidget(LabelWidget template, object item) + { + var itemString = item.ToString(); + var widget = (LabelWidget)template.Clone(); + var font = Game.Renderer.Fonts[widget.Font]; + itemString = WidgetUtils.WrapText(itemString, widget.Bounds.Width, font); + widget.Bounds.Height = font.Measure(itemString).Y; + widget.GetText = () => itemString; + return widget; + } + + bool LabelItemEquals(Widget widget, object item) + { + return item != null && ((LabelWidget)widget).GetText() == item.ToString(); + } + + bool EnterPressed() + { + if (!inputBox.Text.Any()) + return true; + + var text = inputBox.Text; + inputBox.Text = ""; + + if (text[0] == '/') + { + var parts = text.Split(new[] { ' ' }, 2); + var name = parts[0].Substring(1); + var args = parts.Length > 1 ? parts[1] : null; + + Action command; + if (!commands.TryGetValue(name, out command)) + { + IrcClient.AddHistory("{0}: Unknown command".F(name)); + return true; + } + command(args); + } + else + { + IrcClient.Instance.Message(IrcClient.MainChannel, text); + IrcClient.AddMessage(IrcClient.Instance.LocalUser.Nickname, text); + } + return true; + } + + Dictionary> commands = new Dictionary>(StringComparer.OrdinalIgnoreCase); + + List tabMatches = new List(); + int tabMatchesIndex = -1; + + bool TabPressed() + { + if (!inputBox.Text.Any()) + return true; + + var channel = IrcClient.Instance.GetChannel(IrcClient.MainChannel); + + if (channel == null) + return true; + + var spaceIndex = inputBox.Text.TrimEnd().LastIndexOf(' '); + var tabMatchtext = inputBox.Text.Substring(spaceIndex + 1); + + if (tabMatchesIndex < 0 || !tabMatches.Any() || tabMatchtext != tabMatches[tabMatchesIndex]) + tabMatches = channel.Users.Keys.Where(u => u.StartsWith(tabMatchtext, StringComparison.OrdinalIgnoreCase)).ToList(); + + if (!tabMatches.Any()) + return true; + + tabMatchesIndex = (tabMatchesIndex + 1) % tabMatches.Count; + inputBox.Text = inputBox.Text.Remove(spaceIndex + 1) + tabMatches[tabMatchesIndex]; + inputBox.CursorPosition = inputBox.Text.Length; + + return true; + } + } +} diff --git a/OpenRA.Mods.RA/Widgets/Logic/ServerBrowserLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/ServerBrowserLogic.cs index dfc43f105d..44d1b6118e 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/ServerBrowserLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/ServerBrowserLogic.cs @@ -101,6 +101,8 @@ namespace OpenRA.Mods.RA.Widgets.Logic showIncompatibleCheckbox.OnClick = () => { showIncompatible ^= true; ServerList.Query(games => RefreshServerList(panel, games)); }; } + Game.LoadWidget(null, "SERVERBROWSER_IRC", panel.Get("IRC_ROOT"), new WidgetArgs()); + ServerList.Query(games => RefreshServerList(panel, games)); } diff --git a/mods/cnc/chrome/irc.yaml b/mods/cnc/chrome/irc.yaml new file mode 100644 index 0000000000..136f2ec52d --- /dev/null +++ b/mods/cnc/chrome/irc.yaml @@ -0,0 +1,68 @@ +Container@SERVERBROWSER_IRC: + Logic:IrcLogic + Width:700 + Height:250 + Children: + Container@IRC_CONTAINER: + Width:PARENT_RIGHT + Height:PARENT_BOTTOM + Children: + ScrollPanel@HISTORY_PANEL: + Width:565 + Height:PARENT_BOTTOM - 30 + ItemSpacing:5 + Label@HISTORY_TEMPLATE: + X:5 + Width:530 + Height:25 + WordWrap:True + TextField@INPUT_BOX: + Y:PARENT_BOTTOM - 25 + Width:565 + Height:25 + ScrollPanel@NICKNAME_PANEL: + X:570 + Width:130 + Height:PARENT_BOTTOM - 30 + Label@NICKNAME_TEMPLATE: + X:5 + Button@DISCONNECT_BUTTON: + X:570 + Y:PARENT_BOTTOM - 25 + Width:130 + Height:25 + Text:Disconnect + Font:Bold + Background@IRC_CONNECT_BG: + Width:PARENT_RIGHT + Height:PARENT_BOTTOM + Background:scrollpanel-bg + Children: + Label@GLOBAL_CHAT_LABEL: + Y:PARENT_BOTTOM / 4 + Width:PARENT_RIGHT + Align:Center + Text:Global Chat + Font:Bold + Label@NICKNAME_LABEL: + X:200 + Y:PARENT_BOTTOM / 4 + 35 + Text:Nickname: + TextField@NICKNAME_BOX: + X:270 + Y:PARENT_BOTTOM / 4 + 25 + Width:150 + Height:25 + Checkbox@CONNECT_AUTOMATICALLY_CHECKBOX: + X:270 + Y:PARENT_BOTTOM / 4 + 75 + Height:20 + Width:180 + Text:Connect Automatically + Button@CONNECT_BUTTON: + X:430 + Y:PARENT_BOTTOM / 4 + 25 + Width:100 + Height:25 + Text:Connect + Font:Bold \ No newline at end of file diff --git a/mods/cnc/chrome/serverbrowser.yaml b/mods/cnc/chrome/serverbrowser.yaml index 88655167e0..647212a2b2 100644 --- a/mods/cnc/chrome/serverbrowser.yaml +++ b/mods/cnc/chrome/serverbrowser.yaml @@ -1,27 +1,31 @@ Container@SERVERBROWSER_PANEL: Logic:ServerBrowserLogic X:(WINDOW_RIGHT - WIDTH)/2 - Y:(WINDOW_BOTTOM - 500)/2 - Width:540 - Height:535 + Y:(WINDOW_BOTTOM - HEIGHT)/2 + Width:730 + Height:645 Children: Label@TITLE: Text:Find Server - Width:540 - Y:0-25 + Width:740 + Y:0-10 Font:BigBold Contrast:true Align:Center Background@bg: - Width:540 - Height:500 + Width:730 + Height:600 Background:panel-black + Y:15 Children: + Container@IRC_ROOT: + X:15 + Y:15 ScrollPanel@SERVER_LIST: X:15 - Y:30 - Width:510 - Height:450 + Y:280 + Width:700 + Height:300 Children: ScrollItem@SERVER_TEMPLATE: Width:PARENT_RIGHT-27 @@ -76,7 +80,7 @@ Container@SERVERBROWSER_PANEL: Height:25 Label@PROGRESS_LABEL: X:(PARENT_RIGHT - WIDTH) / 2 - Y:PARENT_BOTTOM / 2 - HEIGHT + Y:PARENT_BOTTOM / 2 - HEIGHT + (280 / 2) Width:710 Height:25 Font:Bold @@ -85,20 +89,20 @@ Container@SERVERBROWSER_PANEL: Button@BACK_BUTTON: Key:escape X:0 - Y:499 + Y:614 Width:140 Height:35 Text:Back Button@REFRESH_BUTTON: - X:250 - Y:499 + X:PARENT_RIGHT - 140 - 10 - 140 + Y:614 Width:140 Height:35 Text:Refresh Button@JOIN_BUTTON: Key:return - X:400 - Y:499 + X:PARENT_RIGHT - 140 + Y:614 Width:140 Height:35 - Text:Join + Text:Join \ No newline at end of file diff --git a/mods/cnc/mod.yaml b/mods/cnc/mod.yaml index fff00e2b3f..a3976513fa 100644 --- a/mods/cnc/mod.yaml +++ b/mods/cnc/mod.yaml @@ -85,6 +85,7 @@ ChromeLayout: mods/cnc/chrome/dialogs.yaml mods/cnc/chrome/objectives.yaml mods/cnc/chrome/tooltips.yaml + mods/cnc/chrome/irc.yaml Weapons: mods/cnc/weapons.yaml diff --git a/mods/d2k/mod.yaml b/mods/d2k/mod.yaml index 7961437974..ac0b1ff9e7 100644 --- a/mods/d2k/mod.yaml +++ b/mods/d2k/mod.yaml @@ -72,6 +72,7 @@ ChromeLayout: mods/d2k/chrome/tooltips.yaml mods/d2k/chrome/assetbrowser.yaml mods/ra/chrome/convertassets.yaml + mods/ra/chrome/irc.yaml Weapons: mods/d2k/weapons.yaml diff --git a/mods/ra/chrome/irc.yaml b/mods/ra/chrome/irc.yaml new file mode 100644 index 0000000000..136f2ec52d --- /dev/null +++ b/mods/ra/chrome/irc.yaml @@ -0,0 +1,68 @@ +Container@SERVERBROWSER_IRC: + Logic:IrcLogic + Width:700 + Height:250 + Children: + Container@IRC_CONTAINER: + Width:PARENT_RIGHT + Height:PARENT_BOTTOM + Children: + ScrollPanel@HISTORY_PANEL: + Width:565 + Height:PARENT_BOTTOM - 30 + ItemSpacing:5 + Label@HISTORY_TEMPLATE: + X:5 + Width:530 + Height:25 + WordWrap:True + TextField@INPUT_BOX: + Y:PARENT_BOTTOM - 25 + Width:565 + Height:25 + ScrollPanel@NICKNAME_PANEL: + X:570 + Width:130 + Height:PARENT_BOTTOM - 30 + Label@NICKNAME_TEMPLATE: + X:5 + Button@DISCONNECT_BUTTON: + X:570 + Y:PARENT_BOTTOM - 25 + Width:130 + Height:25 + Text:Disconnect + Font:Bold + Background@IRC_CONNECT_BG: + Width:PARENT_RIGHT + Height:PARENT_BOTTOM + Background:scrollpanel-bg + Children: + Label@GLOBAL_CHAT_LABEL: + Y:PARENT_BOTTOM / 4 + Width:PARENT_RIGHT + Align:Center + Text:Global Chat + Font:Bold + Label@NICKNAME_LABEL: + X:200 + Y:PARENT_BOTTOM / 4 + 35 + Text:Nickname: + TextField@NICKNAME_BOX: + X:270 + Y:PARENT_BOTTOM / 4 + 25 + Width:150 + Height:25 + Checkbox@CONNECT_AUTOMATICALLY_CHECKBOX: + X:270 + Y:PARENT_BOTTOM / 4 + 75 + Height:20 + Width:180 + Text:Connect Automatically + Button@CONNECT_BUTTON: + X:430 + Y:PARENT_BOTTOM / 4 + 25 + Width:100 + Height:25 + Text:Connect + Font:Bold \ No newline at end of file diff --git a/mods/ra/chrome/serverbrowser.yaml b/mods/ra/chrome/serverbrowser.yaml index 76e114a86c..9347e24afc 100644 --- a/mods/ra/chrome/serverbrowser.yaml +++ b/mods/ra/chrome/serverbrowser.yaml @@ -2,8 +2,8 @@ Background@JOINSERVER_BG: Logic:ServerBrowserLogic X:(WINDOW_RIGHT - WIDTH)/2 Y:(WINDOW_BOTTOM - HEIGHT)/2 - Width:540 - Height:505 + Width:740 + Height:700 Children: Label@JOINSERVER_LABEL_TITLE: X:0 @@ -47,8 +47,8 @@ Background@JOINSERVER_BG: ScrollPanel@SERVER_LIST: X:20 Y:80 - Width:500 - Height:355 + Width:700 + Height:305 Children: ScrollItem@SERVER_TEMPLATE: Width:PARENT_RIGHT-27 @@ -103,34 +103,37 @@ Background@JOINSERVER_BG: Height:25 Label@PROGRESS_LABEL: X:(PARENT_RIGHT - WIDTH) / 2 - Y:PARENT_BOTTOM / 2 - HEIGHT + Y:505 / 2 - HEIGHT Width:150 Height:30 Text:Fetching games... Align:Center Button@REFRESH_BUTTON: X:20 - Y:PARENT_BOTTOM - 45 + Y:395 Width:100 Height:25 Text:Refresh Font:Bold Button@JOIN_BUTTON: - X:PARENT_RIGHT - 140 - 130 - Y:PARENT_BOTTOM - 45 + X:PARENT_RIGHT - 120 - 120 + Y:395 Width:100 Height:25 Text:Join Font:Bold Key:return Button@BACK_BUTTON: - X:PARENT_RIGHT - 140 - Y:PARENT_BOTTOM - 45 + X:PARENT_RIGHT - 120 + Y:395 Width:100 Height:25 Text:Cancel Font:Bold Key:escape + Container@IRC_ROOT: + X:20 + Y:430 Background@DIRECTCONNECT_BG: Logic:DirectConnectLogic X:(WINDOW_RIGHT - WIDTH)/2 diff --git a/mods/ra/mod.yaml b/mods/ra/mod.yaml index 7c7dd31244..00237cfaf8 100644 --- a/mods/ra/mod.yaml +++ b/mods/ra/mod.yaml @@ -85,6 +85,7 @@ ChromeLayout: mods/ra/chrome/tooltips.yaml mods/ra/chrome/assetbrowser.yaml mods/ra/chrome/convertassets.yaml + mods/ra/chrome/irc.yaml Weapons: mods/ra/weapons.yaml diff --git a/mods/ts/mod.yaml b/mods/ts/mod.yaml index 50605b71a6..b4c112630a 100644 --- a/mods/ts/mod.yaml +++ b/mods/ts/mod.yaml @@ -115,6 +115,7 @@ ChromeLayout: mods/ra/chrome/tooltips.yaml mods/ra/chrome/assetbrowser.yaml mods/ra/chrome/convertassets.yaml + mods/ra/chrome/irc.yaml Weapons: mods/ts/weapons.yaml From faee82654fb5d31909bcdfa328501534a2b07a6b Mon Sep 17 00:00:00 2001 From: ScottNZ Date: Sun, 22 Sep 2013 19:51:37 +1200 Subject: [PATCH 6/6] Add OpenRA.Irc to packaging scripts --- packaging/package-all.sh | 2 +- packaging/windows/OpenRA.nsi | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packaging/package-all.sh b/packaging/package-all.sh index 20a29f66bc..c4bc9d72bc 100755 --- a/packaging/package-all.sh +++ b/packaging/package-all.sh @@ -29,7 +29,7 @@ markdown DOCUMENTATION.md > DOCUMENTATION.html # Note that the Tao dlls are shipped on all platforms except osx and that # they are now installed to the game directory instead of placed in the gac FILES=('OpenRA.Game.exe' 'OpenRA.Editor.exe' 'OpenRA.Utility.exe' \ -'OpenRA.FileFormats.dll' 'OpenRA.Renderer.SdlCommon.dll' 'OpenRA.Renderer.Cg.dll' 'OpenRA.Renderer.Gl.dll' 'OpenRA.Renderer.Null.dll' \ +'OpenRA.FileFormats.dll' 'OpenRA.Renderer.SdlCommon.dll' 'OpenRA.Renderer.Cg.dll' 'OpenRA.Renderer.Gl.dll' 'OpenRA.Renderer.Null.dll' 'OpenRA.Irc.dll' \ 'FreeSans.ttf' 'FreeSansBold.ttf' 'titles.ttf' 'Dune2k.ttf' \ 'cg' 'glsl' 'mods/ra' 'mods/cnc' 'mods/d2k' \ 'AUTHORS' 'COPYING' 'HACKING' 'INSTALL' 'CHANGELOG' \ diff --git a/packaging/windows/OpenRA.nsi b/packaging/windows/OpenRA.nsi index 78acb060da..54352889ad 100644 --- a/packaging/windows/OpenRA.nsi +++ b/packaging/windows/OpenRA.nsi @@ -64,6 +64,7 @@ Section "Client" CLIENT File "${SRCDIR}\OpenRA.Renderer.Gl.dll" File "${SRCDIR}\OpenRA.Renderer.Cg.dll" File "${SRCDIR}\OpenRA.Renderer.Null.dll" + File "${SRCDIR}\OpenRA.Irc.dll" File "${SRCDIR}\ICSharpCode.SharpZipLib.dll" File "${SRCDIR}\FuzzyLogicLibrary.dll" File "${SRCDIR}\Mono.Nat.dll" @@ -216,6 +217,7 @@ Function ${UN}Clean Delete $INSTDIR\OpenRA.Renderer.Cg.dll Delete $INSTDIR\OpenRA.Renderer.Null.dll Delete $INSTDIR\OpenRA.Renderer.SdlCommon.dll + Delete $INSTDIR\OpenRA.Irc.dll Delete $INSTDIR\ICSharpCode.SharpZipLib.dll Delete $INSTDIR\FuzzyLogicLibrary.dll Delete $INSTDIR\Mono.Nat.dll