From 0219475ab2c65847800295fdcc5727d81f65fe65 Mon Sep 17 00:00:00 2001 From: Joyce Date: Sun, 17 May 2020 08:57:01 +0200 Subject: [PATCH 01/45] Return the max party size to 4 (#222) --- Intersect.Server/Entities/Player.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Intersect.Server/Entities/Player.cs b/Intersect.Server/Entities/Player.cs index f5271cab14..140a685c50 100644 --- a/Intersect.Server/Entities/Player.cs +++ b/Intersect.Server/Entities/Player.cs @@ -3831,7 +3831,7 @@ public void AddParty(Player target) } } - if (Party.Count < Options.Party.MaximumMembers) + if (Party.Count < 4) { target.LeaveParty(); Party.Add(target); From dc2e369c9106b82277bb7fbe6303e0da36650aa1 Mon Sep 17 00:00:00 2001 From: Robert Lodico Date: Sat, 25 Jul 2020 11:44:42 -0400 Subject: [PATCH 02/45] Changed strings, finished tests for class --- .../Extensions/RandomExtensions.cs | 14 +++++----- .../Extensions/RandomExtensionsTests.cs | 26 +++++++++++++++++-- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/Intersect (Core)/Extensions/RandomExtensions.cs b/Intersect (Core)/Extensions/RandomExtensions.cs index 05fef03d5b..cdaa1720f7 100644 --- a/Intersect (Core)/Extensions/RandomExtensions.cs +++ b/Intersect (Core)/Extensions/RandomExtensions.cs @@ -61,7 +61,7 @@ public static ulong NextULong([NotNull] this Random random, ulong minimum, ulong { if (minimum >= maximum) { - throw new ArgumentOutOfRangeException(nameof(maximum), @"'maximum' must be greater than 'minimum'."); + throw new ArgumentOutOfRangeException(nameof(maximum), $@"'{nameof(maximum)}' must be greater than '{nameof(minimum)}'."); } return NextULong(random, maximum - minimum) + minimum; @@ -71,7 +71,7 @@ public static decimal NextDecimal([NotNull] this Random random, decimal maximum) { if (maximum <= 0) { - throw new ArgumentOutOfRangeException(nameof(maximum), @"'maximum' must be greater than 0."); + throw new ArgumentOutOfRangeException(nameof(maximum), $@"'{nameof(maximum)}' must be greater than 0."); } return random.NextDecimal() * maximum; @@ -81,7 +81,7 @@ public static decimal NextDecimal([NotNull] this Random random, decimal minimum, { if (minimum >= maximum) { - throw new ArgumentOutOfRangeException(nameof(maximum), @"'maximum' must be greater than 'minimum'."); + throw new ArgumentOutOfRangeException(nameof(maximum), $@"'{nameof(maximum)}' must be greater than '{nameof(minimum)}'."); } var midpoint = minimum / 2M + maximum / 2M; @@ -94,7 +94,7 @@ public static double NextDouble([NotNull] this Random random, double maximum) { if (maximum <= 0) { - throw new ArgumentOutOfRangeException(nameof(maximum), @"'maximum' must be greater than 0."); + throw new ArgumentOutOfRangeException(nameof(maximum), $@"'{nameof(maximum)}' must be greater than 0."); } return random.NextDouble() * maximum; @@ -104,7 +104,7 @@ public static double NextDouble([NotNull] this Random random, double minimum, do { if (minimum >= maximum) { - throw new ArgumentOutOfRangeException(nameof(maximum), @"'maximum' must be greater than 'minimum'."); + throw new ArgumentOutOfRangeException(nameof(maximum), $@"'{nameof(maximum)}' must be greater than '{nameof(minimum)}'."); } var midpoint = minimum / 2D + maximum / 2D; @@ -122,7 +122,7 @@ public static float NextFloat([NotNull] this Random random, float maximum) { if (maximum <= 0) { - throw new ArgumentOutOfRangeException(nameof(maximum), @"'maximum' must be greater than 0."); + throw new ArgumentOutOfRangeException(nameof(maximum), $@"'{nameof(maximum)}' must be greater than 0."); } return (float) NextDouble(random, maximum); @@ -132,7 +132,7 @@ public static float NextFloat([NotNull] this Random random, float minimum, float { if (minimum >= maximum) { - throw new ArgumentOutOfRangeException(nameof(maximum), @"'maximum' must be greater than 'minimum'."); + throw new ArgumentOutOfRangeException(nameof(maximum), $@"'{nameof(maximum)}' must be greater than '{nameof(minimum)}'."); } return (float) NextDouble(random, minimum, maximum); diff --git a/Intersect.Tests/Extensions/RandomExtensionsTests.cs b/Intersect.Tests/Extensions/RandomExtensionsTests.cs index 5afe99cbf9..f27c05a6f6 100644 --- a/Intersect.Tests/Extensions/RandomExtensionsTests.cs +++ b/Intersect.Tests/Extensions/RandomExtensionsTests.cs @@ -54,19 +54,41 @@ public void NextDoubleTest() } [Test] - public void NextFloatMaximumTest() + public void NextFloatMaximumThrowsExceptionWhenNegativeTest() { var random = new MockRandom(); Assert.ThrowsException(() => random.NextFloat(-1)); } [Test] - public void NextFloatMinimumMaximumTest() + public void NextFloatMaximumTest() + { + var random = new MockRandom + { + MockNextDouble = float.NegativeInfinity + }; + var nextFloat = random.NextFloat(1); + Assert.IsTrue(float.IsNegativeInfinity(nextFloat), $@"Expected negative infinity, got {nextFloat}."); + } + + [Test] + public void NextFloatMinimumThrowsExceptionWhenMaximumLessThanMinimumTest() { var random = new MockRandom(); Assert.ThrowsException(() => random.NextFloat(1, 0)); } + [Test] + public void NextFloatMinimumMaximumTest() + { + var random = new MockRandom + { + MockNextDouble = float.NegativeInfinity + }; + var nextFloat = random.NextFloat(0, 1); + Assert.IsTrue(float.IsNegativeInfinity(nextFloat), $@"Expected negative infinity, got {nextFloat}."); + } + [Test] public void NextFloatTest() { From 8ce6d489a23a361e09edf674334f99ee7768480a Mon Sep 17 00:00:00 2001 From: Robert Lodico Date: Sat, 25 Jul 2020 14:12:39 -0400 Subject: [PATCH 03/45] Manifests and tests --- Intersect (Core)/Intersect (Core).csproj | 44 +- .../Plugins/Interfaces/IManifestHelper.cs | 43 ++ .../Plugins/Loaders/ManifestLoader.cs | 174 ++++++ .../Plugins/Manifests/JsonManifest.cs | 39 ++ .../Plugins/Manifests/Types/Authors.cs | 2 +- Intersect (Core)/packages.config | 11 +- .../Extensions/DateTimeExtensionsTests.cs | 6 +- Intersect.Tests/Intersect.Tests.csproj | 52 +- .../Plugins/Loaders/ManifestLoaderTest.cs | 497 ++++++++++++++++++ .../Plugins/Manifests/JsonManifestTests.cs | 94 ++++ .../Plugins/VirtualTestManifest.cs | 34 ++ .../Plugins/manifest.lowercase.json | 20 + .../Plugins/manifest.well-formed.json | 20 + Intersect.Tests/app.config | 2 +- Intersect.Tests/packages.config | 13 +- NuGet.Config | 1 + 16 files changed, 1040 insertions(+), 12 deletions(-) create mode 100644 Intersect (Core)/Plugins/Interfaces/IManifestHelper.cs create mode 100644 Intersect (Core)/Plugins/Loaders/ManifestLoader.cs create mode 100644 Intersect (Core)/Plugins/Manifests/JsonManifest.cs create mode 100644 Intersect.Tests/Plugins/Loaders/ManifestLoaderTest.cs create mode 100644 Intersect.Tests/Plugins/Manifests/JsonManifestTests.cs create mode 100644 Intersect.Tests/Plugins/VirtualTestManifest.cs create mode 100644 Intersect.Tests/Plugins/manifest.lowercase.json create mode 100644 Intersect.Tests/Plugins/manifest.well-formed.json diff --git a/Intersect (Core)/Intersect (Core).csproj b/Intersect (Core)/Intersect (Core).csproj index 3ed29e63df..936c51568f 100644 --- a/Intersect (Core)/Intersect (Core).csproj +++ b/Intersect (Core)/Intersect (Core).csproj @@ -1,5 +1,10 @@ - + + + + + + Debug @@ -12,6 +17,8 @@ v4.6.1 512 + + true @@ -23,6 +30,8 @@ 4 false ..\build\debug\core\Intersect Core.xml + true + MinimumRecommendedRules.ruleset pdbonly @@ -32,6 +41,8 @@ prompt 4 false + true + MinimumRecommendedRules.ruleset ..\build\tespia\core\ @@ -42,6 +53,7 @@ prompt MinimumRecommendedRules.ruleset false + true true @@ -52,6 +64,7 @@ prompt MinimumRecommendedRules.ruleset false + true @@ -119,6 +132,9 @@ ..\packages\Remotion.Linq.2.2.0\lib\net45\Remotion.Linq.dll + + ..\packages\Semver.2.0.6\lib\net452\Semver.dll + ..\packages\System.Buffers.4.5.0\lib\netstandard2.0\System.Buffers.dll @@ -479,6 +495,10 @@ + + + + @@ -602,8 +622,30 @@ + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + + - \ No newline at end of file + From cd58edda5a6d2a19e4fc98cf5d45024da3af055f Mon Sep 17 00:00:00 2001 From: Robert Lodico Date: Sat, 25 Jul 2020 15:34:56 -0400 Subject: [PATCH 06/45] Minor additions and cleanup --- Intersect (Core)/Core/IApplicationContext.cs | 55 ++++++++++++- .../Network/AbstractConnection.cs | 14 ++-- Intersect (Core)/Network/AbstractNetwork.cs | 73 ++++++----------- Intersect (Core)/Network/NetworkThread.cs | 29 ++++--- Intersect (Core)/Utilities/ValueUtils.cs | 79 ++++++++++++++++++- 5 files changed, 179 insertions(+), 71 deletions(-) diff --git a/Intersect (Core)/Core/IApplicationContext.cs b/Intersect (Core)/Core/IApplicationContext.cs index 9c66638c43..b838e39555 100644 --- a/Intersect (Core)/Core/IApplicationContext.cs +++ b/Intersect (Core)/Core/IApplicationContext.cs @@ -1,26 +1,75 @@ using System; +using System.Collections.Generic; +using Intersect.Logging; using Intersect.Threading; using JetBrains.Annotations; namespace Intersect.Core { - + /// + /// Declares the API surface for applications. + /// public interface IApplicationContext : IDisposable { + /// + /// If the application has encountered unhandled/unobserved exceptions during its lifespan. + /// + bool HasErrors { get; } + /// + /// If the application has been disposed. + /// bool IsDisposed { get; } + /// + /// If the application has been started. + /// bool IsStarted { get; } + /// + /// If the application is running. + /// bool IsRunning { get; } + /// + /// The options the application was started with. + /// + [NotNull] + ICommandLineOptions StartupOptions { get; } + + /// + /// The application-specific logger. + /// + [NotNull] + Logger Logger { get; } + + /// + /// The s currently registered. + /// + [NotNull] + List Services { get; } + + /// + /// Gets a service of type if one has been registered. + /// + /// the service type to look for + /// an instance of if found, otherwise default() + [CanBeNull] + TApplicationService GetService() where TApplicationService : IApplicationService; + + /// + /// Start the application, optionally locking the current thread until shutdown (default true). + /// + /// if the current thread should be locked until shutdown void Start(bool lockUntilShutdown = true); + /// + /// Start the application with a . + /// + /// the instance being used [NotNull] LockingActionQueue StartWithActionQueue(); - } - } diff --git a/Intersect (Core)/Network/AbstractConnection.cs b/Intersect (Core)/Network/AbstractConnection.cs index 3ae96a6b86..e13fc9c13e 100644 --- a/Intersect (Core)/Network/AbstractConnection.cs +++ b/Intersect (Core)/Network/AbstractConnection.cs @@ -2,29 +2,29 @@ using Intersect.Logging; +using JetBrains.Annotations; + namespace Intersect.Network { public abstract class AbstractConnection : IConnection { + [NotNull] private readonly object mDisposeLock; private bool mDisposed; protected AbstractConnection(Guid? guid = null) { - if (!guid.HasValue) - { - guid = Guid.NewGuid(); - } + mDisposeLock = new object(); - Guid = guid.Value; + Guid = guid ?? Guid.NewGuid(); } public Ceras Ceras { get; } = new Ceras(true); public virtual void Dispose() { - lock (this) + lock (mDisposeLock) { if (mDisposed) { @@ -61,7 +61,7 @@ public virtual void HandleDisconnected() { IsConnected = false; - Log.Debug($"Connectioned terminated to remote [{Guid}/{Ip}:{Port}]."); + Log.Debug($"Connection terminated to remote [{Guid}/{Ip}:{Port}]."); } } diff --git a/Intersect (Core)/Network/AbstractNetwork.cs b/Intersect (Core)/Network/AbstractNetwork.cs index d51ad6320d..e730a25392 100644 --- a/Intersect (Core)/Network/AbstractNetwork.cs +++ b/Intersect (Core)/Network/AbstractNetwork.cs @@ -11,17 +11,18 @@ namespace Intersect.Network { - public abstract class AbstractNetwork : INetwork { + private bool mDisposed; - private readonly List mNetworkLayerInterfaces; + [NotNull] private readonly object mDisposeLock; - private bool mDisposed; + private readonly List mNetworkLayerInterfaces; protected AbstractNetwork([NotNull] NetworkConfiguration configuration) { mDisposed = false; + mDisposeLock = new object(); mNetworkLayerInterfaces = new List(); @@ -35,14 +36,11 @@ protected AbstractNetwork([NotNull] NetworkConfiguration configuration) Configuration = configuration; } - [NotNull] - public ICollection Connections => ConnectionLookup.Values; + [NotNull] public ICollection Connections => ConnectionLookup.Values; - [NotNull] - public IDictionary ConnectionLookup { get; } + [NotNull] public IDictionary ConnectionLookup { get; } - [NotNull] - public HandlePacket Handler { get; set; } + [NotNull] public HandlePacket Handler { get; set; } public ShouldProcessPacket PreProcessHandler { get; set; } @@ -64,14 +62,12 @@ public bool AddConnection([NotNull] IConnection connection) return true; } - public bool RemoveConnection([NotNull] IConnection connection) - { - return !connection.IsConnected && ConnectionLookup.Remove(connection.Guid); - } + public bool RemoveConnection([NotNull] IConnection connection) => + !connection.IsConnected && ConnectionLookup.Remove(connection.Guid); public void Dispose() { - lock (this) + lock (mDisposeLock) { if (mDisposed) { @@ -91,25 +87,17 @@ public void Dispose() ConnectionLookup.Clear(); } - public bool Disconnect(string message = "") - { - return Disconnect(Connections, message); - } + public bool Disconnect(string message = "") => Disconnect(Connections, message); - public bool Disconnect(Guid guid, string message = "") - { - return Disconnect(FindConnection(guid), message); - } + public bool Disconnect(Guid guid, string message = "") => Disconnect(FindConnection(guid), message); public bool Disconnect(IConnection connection, string message = "") { return Disconnect(new[] {connection}, message); } - public bool Disconnect(ICollection guids, string message = "") - { - return Disconnect(FindConnections(guids), message); - } + public bool Disconnect(ICollection guids, string message = "") => + Disconnect(FindConnections(guids), message); public bool Disconnect(ICollection connections, string message = "") { @@ -131,10 +119,7 @@ public bool Send(Guid guid, IPacket packet) public abstract bool Send(IConnection connection, IPacket packet); - public bool Send(ICollection guids, IPacket packet) - { - return Send(FindConnections(guids), packet); - } + public bool Send(ICollection guids, IPacket packet) => Send(FindConnections(guids), packet); public abstract bool Send(ICollection connections, IPacket packet); @@ -150,16 +135,12 @@ public IConnection FindConnection(Guid guid) return null; } - public TConnection FindConnection(Guid guid) where TConnection : class, IConnection - { - return FindConnection(guid) as TConnection; - } + public TConnection FindConnection(Guid guid) where TConnection : class, IConnection => + FindConnection(guid) as TConnection; public TConnection FindConnection([NotNull] Func selector) - where TConnection : class, IConnection - { - return FindConnections().FirstOrDefault(selector); - } + where TConnection : class, IConnection => + FindConnections().FirstOrDefault(selector); public ICollection FindConnections([NotNull] ICollection guids) { @@ -167,10 +148,8 @@ public ICollection FindConnections([NotNull] ICollection guid } [NotNull] - public ICollection FindConnections() where TConnection : class, IConnection - { - return Connections.OfType().ToList(); - } + public ICollection FindConnections() where TConnection : class, IConnection => + Connections.OfType().ToList(); protected void AddNetworkLayerInterface(INetworkLayerInterface networkLayerInterface) { @@ -241,10 +220,10 @@ private void HandleInboundData(IBuffer buffer, IConnection connection) } } - //Incorperate Ceras + // Incorporate Ceras var data = buffer.ToBytes(); - //Get Packet From Data using Ceras + // Get Packet From Data using Ceras var sw = new Stopwatch(); sw.Start(); var packet = (IPacket) connection.Ceras.Deserialize(data); @@ -255,9 +234,9 @@ private void HandleInboundData(IBuffer buffer, IConnection connection) ); } - //Handle any packet identification errors + // Handle any packet identification errors - //Pass packet to handler. + // Pass packet to handler. Handler.Invoke(connection, packet); } @@ -294,7 +273,5 @@ protected void StopInterfaces(string reason = "stopping") { mNetworkLayerInterfaces?.ForEach(networkLayerInterface => networkLayerInterface?.Stop(reason)); } - } - } diff --git a/Intersect (Core)/Network/NetworkThread.cs b/Intersect (Core)/Network/NetworkThread.cs index 8ff581ce93..7fc9ef395d 100644 --- a/Intersect (Core)/Network/NetworkThread.cs +++ b/Intersect (Core)/Network/NetworkThread.cs @@ -4,38 +4,43 @@ using Intersect.Logging; +using JetBrains.Annotations; + namespace Intersect.Network { public sealed class NetworkThread { + [NotNull] private readonly object mLifecycleLock; - private readonly PacketDispatcher mDispatcher; + [NotNull] private readonly PacketDispatcher mDispatcher; private bool mStarted; - public NetworkThread(PacketDispatcher dispatcher, string name = null) + public NetworkThread([NotNull] PacketDispatcher dispatcher, string name = null) { + mLifecycleLock = new object(); + mDispatcher = dispatcher; + Name = name ?? "Network Worker Thread"; CurrentThread = new Thread(Loop); Queue = new PacketQueue(); - mDispatcher = dispatcher; Connections = new List(); } - public string Name { get; } + [NotNull] public string Name { get; } - public Thread CurrentThread { get; } + [NotNull] public Thread CurrentThread { get; } - public PacketQueue Queue { get; } + [NotNull] public PacketQueue Queue { get; } - public IList Connections { get; } + [NotNull] public IList Connections { get; } public bool IsRunning { get; private set; } public void Start() { - lock (this) + lock (mLifecycleLock) { if (mStarted) { @@ -51,12 +56,12 @@ public void Start() public void Stop() { - lock (this) + lock (mLifecycleLock) { IsRunning = false; } - Queue?.Interrupt(); + Queue.Interrupt(); } private void Loop() @@ -75,7 +80,7 @@ private void Loop() } //Log.Debug($"Dispatching packet '{packet.GetType().Name}' (size={(packet as BinaryPacket)?.Buffer?.Length() ?? -1})."); - if (!(mDispatcher?.Dispatch(packet) ?? false)) + if (!mDispatcher.Dispatch(packet)) { Log.Warn($"Failed to dispatch packet '{packet}'."); } @@ -88,7 +93,7 @@ private void Loop() } #endif - packet.Dispose(); + packet?.Dispose(); Thread.Yield(); } diff --git a/Intersect (Core)/Utilities/ValueUtils.cs b/Intersect (Core)/Utilities/ValueUtils.cs index faef0c64e7..f4f8715b98 100644 --- a/Intersect (Core)/Utilities/ValueUtils.cs +++ b/Intersect (Core)/Utilities/ValueUtils.cs @@ -1,4 +1,10 @@ -namespace Intersect.Utilities +using System; +using System.Collections.Generic; +using System.Linq; + +using JetBrains.Annotations; + +namespace Intersect.Utilities { public static class ValueUtils @@ -18,6 +24,77 @@ public static bool SetDefault(bool condition, out T value) return condition; } + /// + /// Computes the aggregate hash code for . + /// + /// the set of values + /// the aggregate hash code + public static int ComputeHashCode(params object[] values) => ComputeHashCode(values); + + /// + /// Computes the aggregate hash code for . + /// + /// the value type + /// the enumerable set of values + /// the aggregate hash code + public static int ComputeHashCode([CanBeNull] IEnumerable values) => + values?.Aggregate( + 0, (current, value) => unchecked(current * (int) 0xA5555529 + (value?.GetHashCode() ?? 0)) + ) ?? + 0; + + /// + /// Compares two if . + /// + /// + /// + /// the mode to use + /// the comparison between the two enumerables + public static int Compare( + [CanBeNull] IEnumerable a, + [CanBeNull] IEnumerable b, + StringComparison stringComparison = StringComparison.CurrentCulture + ) + { + if (a == null) + { + return b == null ? 0 : -1; + } + + if (b == null) + { + return 1; + } + + int comparison; + + using (var enumeratorA = a.GetEnumerator()) + { + using (var enumeratorB = b.GetEnumerator()) + { + do + { + var aHas = enumeratorA.MoveNext(); + var bHas = enumeratorB.MoveNext(); + + if (!aHas) + { + return bHas ? -1 : 0; + } + + if (!bHas) + { + return 1; + } + + comparison = string.Compare(enumeratorA.Current, enumeratorB.Current, stringComparison); + } while (comparison == 0); + } + } + + return comparison; + } + } } From c3e0a9feb37c364cd0b0f6527a7b6d9a628b4a9e Mon Sep 17 00:00:00 2001 From: Robert Lodico Date: Sat, 25 Jul 2020 15:51:43 -0400 Subject: [PATCH 07/45] Strings, utilities --- Intersect (Core)/GlobalSuppressions.cs | 4 + Intersect (Core)/Intersect (Core).csproj | 18 ++- .../Properties/DeveloperStrings.Designer.cs | 81 +++++++++++ .../Properties/DeveloperStrings.resx | 126 ++++++++++++++++++ .../Reflection/AssemblyExtensions.cs | 99 ++++++++++++++ Intersect (Core)/Reflection/TypeExtensions.cs | 64 +++++++++ Intersect (Core)/SuppressionJustifications.cs | 14 ++ .../Threading/ConcurrentInstance.cs | 2 + Intersect (Core)/Threading/Threaded.cs | 12 +- Intersect (Core)/Threading/Threaded`1.cs | 35 +++++ 10 files changed, 447 insertions(+), 8 deletions(-) create mode 100644 Intersect (Core)/GlobalSuppressions.cs create mode 100644 Intersect (Core)/Properties/DeveloperStrings.Designer.cs create mode 100644 Intersect (Core)/Properties/DeveloperStrings.resx create mode 100644 Intersect (Core)/Reflection/AssemblyExtensions.cs create mode 100644 Intersect (Core)/Reflection/TypeExtensions.cs create mode 100644 Intersect (Core)/SuppressionJustifications.cs create mode 100644 Intersect (Core)/Threading/Threaded`1.cs diff --git a/Intersect (Core)/GlobalSuppressions.cs b/Intersect (Core)/GlobalSuppressions.cs new file mode 100644 index 0000000000..282d4492c7 --- /dev/null +++ b/Intersect (Core)/GlobalSuppressions.cs @@ -0,0 +1,4 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. diff --git a/Intersect (Core)/Intersect (Core).csproj b/Intersect (Core)/Intersect (Core).csproj index 864b72e455..0f9977d3eb 100644 --- a/Intersect (Core)/Intersect (Core).csproj +++ b/Intersect (Core)/Intersect (Core).csproj @@ -257,6 +257,7 @@ + @@ -499,14 +500,23 @@ + + True + True + DeveloperStrings.resx + + + + + @@ -610,15 +620,15 @@ + + ResXFileCodeGenerator + DeveloperStrings.Designer.cs + ResXFileCodeGenerator Resources.Designer.cs - - - - diff --git a/Intersect (Core)/Properties/DeveloperStrings.Designer.cs b/Intersect (Core)/Properties/DeveloperStrings.Designer.cs new file mode 100644 index 0000000000..0e7a95c3c8 --- /dev/null +++ b/Intersect (Core)/Properties/DeveloperStrings.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Intersect.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class DeveloperStrings { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal DeveloperStrings() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Intersect.Properties.DeveloperStrings", typeof(DeveloperStrings).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Application context exited.. + /// + internal static string ApplicationContextExited { + get { + return ResourceManager.GetString("ApplicationContextExited", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to UnknownService. + /// + internal static string ServiceLifecycleFailureExceptionUnknknownServiceName { + get { + return ResourceManager.GetString("ServiceLifecycleFailureExceptionUnknknownServiceName", resourceCulture); + } + } + } +} diff --git a/Intersect (Core)/Properties/DeveloperStrings.resx b/Intersect (Core)/Properties/DeveloperStrings.resx new file mode 100644 index 0000000000..55aba770e2 --- /dev/null +++ b/Intersect (Core)/Properties/DeveloperStrings.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Application context exited. + + + UnknownService + + \ No newline at end of file diff --git a/Intersect (Core)/Reflection/AssemblyExtensions.cs b/Intersect (Core)/Reflection/AssemblyExtensions.cs new file mode 100644 index 0000000000..2fcc356f78 --- /dev/null +++ b/Intersect (Core)/Reflection/AssemblyExtensions.cs @@ -0,0 +1,99 @@ +using JetBrains.Annotations; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Intersect.Reflection +{ + /// + /// Extension methods for . + /// + [UsedImplicitly] + public static class AssemblyExtensions + { + /// + [NotNull] + [Pure] + public static TParentType CreateInstanceOf([NotNull] this Assembly assembly, params object[] args) => + assembly.CreateInstanceOf(_ => true, args); + + /// + /// Creates an instance of given . + /// + /// the type to search for subtypes and create an instance of + /// the to search for subtypes in + /// a function to filter subtypes with + /// the arguments to create the instance with + /// an instance of + /// if no matching subtypes are found, or instance creation fails + [NotNull] + [Pure] + public static TParentType CreateInstanceOf([NotNull] this Assembly assembly, [NotNull] Func predicate, params object[] args) + { + var validTypes = assembly.FindDefinedSubtypesOf(); + var type = validTypes.FirstOrDefault(predicate); + + if (type == default) + { + throw new InvalidOperationException($"Found no matching subtype of {typeof(TParentType).FullName} that can be created."); + } + + if (Activator.CreateInstance(type, args) is TParentType instance) + { + return instance; + } + + throw new InvalidOperationException($"Failed to create instance of {typeof(TParentType).FullName}."); + } + + [NotNull] + public static IEnumerable FindAbstractSubtypesOf([NotNull] this Assembly assembly, [NotNull] Type type) => + FindSubtypesOf(assembly, type).Where(subtype => subtype?.IsAbstract ?? false); + + [NotNull] + public static IEnumerable FindAbstractSubtypesOf([NotNull] this Assembly assembly) => + FindAbstractSubtypesOf(assembly, typeof(TParentType)); + + [NotNull] + public static IEnumerable FindDefinedSubtypesOf([NotNull] this Assembly assembly, [NotNull] Type type) => + FindSubtypesOf(assembly, type).Where(subtype => !(subtype == null || subtype.IsAbstract || subtype.IsGenericType || subtype.IsInterface)); + + [NotNull] + public static IEnumerable FindDefinedSubtypesOf([NotNull] this Assembly assembly) => + FindDefinedSubtypesOf(assembly, typeof(TParentType)); + + [NotNull] + public static IEnumerable FindGenericSubtypesOf([NotNull] this Assembly assembly, [NotNull] Type type) => + FindSubtypesOf(assembly, type).Where(subtype => subtype?.IsGenericType ?? false); + + [NotNull] + public static IEnumerable FindGenericSubtypesOf([NotNull] this Assembly assembly) => + FindGenericSubtypesOf(assembly, typeof(TParentType)); + + [NotNull] + public static IEnumerable FindInterfaceSubtypesOf([NotNull] this Assembly assembly, [NotNull] Type type) => + FindSubtypesOf(assembly, type).Where(subtype => subtype?.IsInterface ?? false); + + [NotNull] + public static IEnumerable FindInterfaceSubtypesOf([NotNull] this Assembly assembly) => + FindInterfaceSubtypesOf(assembly, typeof(Type)); + + [NotNull] + public static IEnumerable FindSubtypesOf([NotNull] this Assembly assembly, [NotNull] Type type) => + assembly.GetTypes().Where(type.IsAssignableFrom); + + [NotNull] + public static IEnumerable FindSubtypesOf([NotNull] this Assembly assembly) => + FindGenericSubtypesOf(assembly, typeof(TParentType)); + + [NotNull] + public static IEnumerable FindValueSubtypesOf([NotNull] this Assembly assembly, [NotNull] Type type) => + FindSubtypesOf(assembly, type).Where(subtype => subtype?.IsValueType ?? false); + + [NotNull] + public static IEnumerable FindValueSubtypesOf([NotNull] this Assembly assembly) => + FindValueSubtypesOf(assembly, typeof(Type)); + } +} diff --git a/Intersect (Core)/Reflection/TypeExtensions.cs b/Intersect (Core)/Reflection/TypeExtensions.cs new file mode 100644 index 0000000000..fca121d919 --- /dev/null +++ b/Intersect (Core)/Reflection/TypeExtensions.cs @@ -0,0 +1,64 @@ +using JetBrains.Annotations; + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; + +namespace Intersect.Reflection +{ + + public static class TypeExtensions + { + + [NotNull] + public static IEnumerable FindConstructors( + [NotNull] this Type type, + params object[] parameters + ) => type.GetConstructors() + .Where( + constructor => + { + var constructorParameters = constructor.GetParameters(); + if (constructorParameters.Length < parameters.Length) + { + return false; + } + + for (var index = 0; index < constructorParameters.Length; ++index) + { + var constructorParameter = constructorParameters[index]; + Debug.Assert(constructorParameter != null, nameof(constructorParameter) + " != null"); + + if (index >= parameters.Length) + { + return constructorParameter.IsOptional; + } + + var parameter = parameters[index]; + + if (parameter == null) + { + if (constructorParameter.ParameterType.IsValueType) + { + return false; + } + + continue; + } + + var parameterType = parameter.GetType(); + if (!constructorParameter.ParameterType.IsAssignableFrom(parameterType)) + { + return false; + } + } + + return true; + } + ); + + } + +} diff --git a/Intersect (Core)/SuppressionJustifications.cs b/Intersect (Core)/SuppressionJustifications.cs new file mode 100644 index 0000000000..39b629465e --- /dev/null +++ b/Intersect (Core)/SuppressionJustifications.cs @@ -0,0 +1,14 @@ +namespace Intersect +{ + /// + /// Collection of commonly used justifications for suppressing code analysis warnings. + /// + public static class SuppressionJustifications + { + /// + /// Analyzer doesn't respect JetBrains NotNullAttribute which already asserts non-nullability. + /// + public const string NotNullJetBrains = + "Analyzer doesn't respect JetBrains NotNullAttribute which already asserts non-nullability."; + } +} diff --git a/Intersect (Core)/Threading/ConcurrentInstance.cs b/Intersect (Core)/Threading/ConcurrentInstance.cs index 0e76cb29ae..bafaaff741 100644 --- a/Intersect (Core)/Threading/ConcurrentInstance.cs +++ b/Intersect (Core)/Threading/ConcurrentInstance.cs @@ -21,6 +21,8 @@ public ConcurrentInstance() mLock = new object(); } + public bool HasInstance => mInstance != null; + [NotNull] public TInstance Instance => mInstance ?? throw new InvalidOperationException(); diff --git a/Intersect (Core)/Threading/Threaded.cs b/Intersect (Core)/Threading/Threaded.cs index d3b10230a0..29536c023f 100644 --- a/Intersect (Core)/Threading/Threaded.cs +++ b/Intersect (Core)/Threading/Threaded.cs @@ -15,7 +15,7 @@ public abstract class Threaded : IDisposable protected Threaded(string name = null) { - mThread = new Thread(ThreadStart); + mThread = new Thread(ThreadStartWrapper); if (!string.IsNullOrEmpty(name)) { mThread.Name = name; @@ -29,17 +29,21 @@ public void Dispose() return; } + mThread.Abort(); + mDisposed = true; } - public Thread Start() + public Thread Start(params object[] args) { - mThread.Start(); + mThread.Start(args); return mThread; } - protected abstract void ThreadStart(); + private void ThreadStartWrapper(object args) => ThreadStart(args as object[]); + + protected abstract void ThreadStart(params object[] args); } diff --git a/Intersect (Core)/Threading/Threaded`1.cs b/Intersect (Core)/Threading/Threaded`1.cs new file mode 100644 index 0000000000..2d96aefc3b --- /dev/null +++ b/Intersect (Core)/Threading/Threaded`1.cs @@ -0,0 +1,35 @@ +using System; + +using JetBrains.Annotations; + +namespace Intersect.Threading +{ + + /// + public abstract class Threaded : Threaded + { + + /// + protected Threaded(string name = null) : base(name) + { + } + + /// + protected override void ThreadStart(params object[] args) + { + // TODO: Generic utility that checks arg types against expected types + if (args == null || args.Length < 1) + { + throw new ArgumentOutOfRangeException( + nameof(args), $@"Expected one argument of type {typeof(TArgument).FullName}." + ); + } + + ThreadStart((TArgument) args[0]); + } + + protected abstract void ThreadStart([CanBeNull] TArgument argument); + + } + +} From 7c87bdbb56faddbaefcb2f0c2dd5a7bd7f519ca8 Mon Sep 17 00:00:00 2001 From: Robert Lodico Date: Sat, 25 Jul 2020 15:59:19 -0400 Subject: [PATCH 08/45] Analyzers --- .../Intersect.Client.Framework.csproj | 32 +++++++++++++++- Intersect.Client.Framework/packages.config | 6 ++- Intersect.Client/Intersect.Client.csproj | 29 +++++++++++++-- Intersect.Client/packages.config | 7 +++- Intersect.Editor/Intersect.Editor.csproj | 23 +++++++++++- Intersect.Editor/packages.config | 6 ++- Intersect.Network/Intersect.Network.csproj | 34 ++++++++++++++++- Intersect.Network/Lidgren/LidgrenBuffer.cs | 2 +- Intersect.Network/packages.config | 6 ++- Intersect.Server/Intersect.Server.csproj | 37 ++++++++++++++++++- Intersect.Server/packages.config | 7 +++- .../Intersect.Tests.Client.Framework.csproj | 23 ++++++++++++ .../packages.config | 6 ++- .../Intersect.Tests.Client.csproj | 23 ++++++++++++ Intersect.Tests.Client/packages.config | 6 ++- .../Intersect.Tests.Editor.csproj | 23 ++++++++++++ Intersect.Tests.Editor/packages.config | 6 ++- .../Intersect.Tests.Network.csproj | 23 ++++++++++++ Intersect.Tests.Network/packages.config | 6 ++- .../Intersect.Tests.Server.csproj | 23 ++++++++++++ Intersect.Tests.Server/packages.config | 6 ++- 21 files changed, 316 insertions(+), 18 deletions(-) diff --git a/Intersect.Client.Framework/Intersect.Client.Framework.csproj b/Intersect.Client.Framework/Intersect.Client.Framework.csproj index 7a62cfc584..ba10f40a33 100644 --- a/Intersect.Client.Framework/Intersect.Client.Framework.csproj +++ b/Intersect.Client.Framework/Intersect.Client.Framework.csproj @@ -1,5 +1,10 @@ - + + + + + + Debug @@ -12,6 +17,8 @@ v4.6.1 512 + + true @@ -22,6 +29,7 @@ prompt 4 false + true pdbonly @@ -31,6 +39,7 @@ prompt 4 false + true @@ -201,11 +210,32 @@ Intersect %28Core%29 + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + + + + + + + + $(INTERSECT_REPO_PATH) + $(INTERSECT_PATH) + $(IntersectRepoPath) + + build\$(Configuration.ToLower()) + <_IntersectRootPath>$(IntersectPath)\$(IntersectIntermediatePath) + <_IntersectRootPath Condition="'$(IntersectRepoPath)' == ''">$(IntersectPath) + + $(_IntersectRootPath)\client + $(_IntersectRootPath) + $(INTERSECT_CLIENT_EXECUTABLE_NAME) + Intersect Client + $(IntersectClientDirectoryPath)\$(IntersectClientExecutableName).exe + + $(_IntersectRootPath)\editor + $(_IntersectRootPath) + $(INTERSECT_EDITOR_EXECUTABLE_NAME) + Intersect Editor + $(IntersectEditorDirectoryPath)\$(IntersectEditorExecutableName).exe + + $(_IntersectRootPath)\server + $(_IntersectRootPath) + $(INTERSECT_SERVER_EXECUTABLE_NAME) + Intersect Server + $(IntersectServerDirectoryPath)\$(IntersectServerExecutableName).exe + + + + + + + diff --git a/scripts/Intersect.Building.KeyGeneration.msbuild b/targets/Intersect.Building.KeyGeneration.msbuild similarity index 100% rename from scripts/Intersect.Building.KeyGeneration.msbuild rename to targets/Intersect.Building.KeyGeneration.msbuild diff --git a/targets/Intersect.Client.Plugin.targets b/targets/Intersect.Client.Plugin.targets new file mode 100644 index 0000000000..fcec6de1f2 --- /dev/null +++ b/targets/Intersect.Client.Plugin.targets @@ -0,0 +1,38 @@ + + + + + + pdbonly + true + + + Program + $(IntersectClientExecutablePath) + $(IntersectClientDirectoryPath) + --plugin-development --plugin-directory $(TargetDir) + + + + ..\packages\CommandLineParser.2.7.82\lib\net461\CommandLine.dll + False + + + ..\packages\JetBrains.Annotations.2018.3.0\lib\net20\JetBrains.Annotations.dll + False + + + ..\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll + False + + + ..\packages\Semver.2.0.6\lib\net452\Semver.dll + False + + + + + + + + diff --git a/targets/Intersect.Server.Plugin.targets b/targets/Intersect.Server.Plugin.targets new file mode 100644 index 0000000000..71e0da8dab --- /dev/null +++ b/targets/Intersect.Server.Plugin.targets @@ -0,0 +1,38 @@ + + + + + + pdbonly + true + + + Program + $(IntersectServerExecutablePath) + $(IntersectServerDirectoryPath) + --plugin-development --plugin-directory $(TargetDir) + + + + ..\packages\CommandLineParser.2.7.82\lib\net461\CommandLine.dll + False + + + ..\packages\JetBrains.Annotations.2018.3.0\lib\net20\JetBrains.Annotations.dll + False + + + ..\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll + False + + + ..\packages\Semver.2.0.6\lib\net452\Semver.dll + False + + + + + + + + diff --git a/scripts/Intersect.targets b/targets/Intersect.targets similarity index 100% rename from scripts/Intersect.targets rename to targets/Intersect.targets From 6562c20f711a832171678c373979196924750f14 Mon Sep 17 00:00:00 2001 From: Robert Lodico Date: Sun, 26 Jul 2020 22:42:02 -0400 Subject: [PATCH 17/45] Intersect.Client.Framework nuget package --- .../Intersect.Client.Framework.nuspec | 47 ++++++++++++++++++ assets/Intersect.png | Bin 0 -> 16993 bytes 2 files changed, 47 insertions(+) create mode 100644 Intersect.Client.Framework/Intersect.Client.Framework.nuspec create mode 100644 assets/Intersect.png diff --git a/Intersect.Client.Framework/Intersect.Client.Framework.nuspec b/Intersect.Client.Framework/Intersect.Client.Framework.nuspec new file mode 100644 index 0000000000..31f6a46fc2 --- /dev/null +++ b/Intersect.Client.Framework/Intersect.Client.Framework.nuspec @@ -0,0 +1,47 @@ + + + + AscensionGameDev.Intersect.Client.Framework + 0.7.0.0-beta + Intersect Client Framework + Library for the public Intersect Client API + Ascension Game Dev + Ascension Game Dev + Copyright © 2020 Ascension Game Dev + intersect agd client plugin framework + LICENSE.md + false + + https://github.com/AscensionGameDev/Intersect-Engine + + assets\Intersect.png + + Nuget release of Intersect.Client.Framework for client plugin development. + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/Intersect.png b/assets/Intersect.png new file mode 100644 index 0000000000000000000000000000000000000000..b8d2f176d1a40e3e0c1e33af55d90003234edb7a GIT binary patch literal 16993 zcmXt918^LFyx%xY(niq@$g952?NR-?HZ3|oaP$#dfaS&t3#fyF0TSD z8(ludS!hS1ik6|IJ^J_rRDp$TU#htj0z8J!|h(& zUayZb<=upxv5tf7`&sqihXZRQEg=eg{P)V~ERF}C`DFiH(+L0&(f>OjfV2!O@JTpl zNm((tJveMk46Hwu)nWiZ1W1YsskpD4`?$E{E#5!y)O65RHpHp@U|Nn-G~;pD8F0lj zvn>e-&6OpVO@Eh?q<(_Iu}E3}#L`#3$^EkXwE1sU{N?vKQe1FdpQ!zh& zmdSGIgS_T61rus3vvf%(zQmB?^>$0*Z*&~gx+uUn*>HfPPPATdIZV1t zb&?~H0C;ilHP6rK7bXeZZyBdqPA4+0$Ux2>=BS@!ipOBSZj^9k5Pt<-dsX>mOYu?+7>HRzn~mr68ol-)y$@ z{B>@u^LyY1uWlogH*WAg`(wBh-1INhd2#qNI9^jjP8qKZe*EBFYgfPggVY_WcoJs; zz<-FmxqW0&cjlBKe!PeD+@e4S#Pt@`d+eT0eQA+HxynyJ_dLzQ1_FT`$;r3)e|E}~ zUROE6{#~IHi^wgS-+GW+mb;LB-H`py_f~0dFgKsduk>)4DFnpd85TOc>~>;2DQ@t* z7n;=gk6wS?x$m3*GmQD;1TRo$uItR=U%JOTyz%I(OLBuJ8UP1S)s-`@6Z|p*Lg7h+ zA#)-1sMGa03Vy=1J7)l+S zuyl9Um<-`AdomA>2cCar9DH6j{Qg6|wyr~G)>+R)QJY_HnpGS2 zLg`1ISowJR339O1h`VDuZ7w&0^!d5DDh@5@)%oVXzPoF` zI~jlRSHtO1ip>LUclch7NnnbjNV`3G1ojw%1Ec`}_}*syd~hZYA=Ils`v;$Sh{6Ft zQ>U|&EmF=gB+o@s4HkKWjd0qvgH)B>&;y*M{YkR`oB<`K z9-WV#%l4*r0b_}#i@sHG`kMxegYV+z2-^wuA>Whd5=`Wu&5rhsw!?y&?rq`$|NkbMnjF5vW| zRUH0>GnP_8hC%0w)87*Hj9E4qwJ&Z84)B`|JpCe8vp*ybR}=g?Q9z004=|auL9_s2 zK^roOzu(&{PH6oEGP2z1T-Y*sHZu`>`24FuX?u6uH{IOScFe~P1H-IxTF&h-Abiyn zS?hN;5i=iUt^NZV@c;15Q)tCR0~(}P@ywqKnv)@X3*%sOBN12{Pl1YMPE=RaiA zoVLsNDt|Ud)tNT?4VT?FLw=7Dau8GH59;Co=HsXS-QU7G8P06a%xTq2)FJlcRC8cX zq`3@|Mndo+07)yKHy3?x&(;A~BuCkCb_H(#M5ijBhs_|}j^ox-1c*80o5M1$MgDfD zFl+F=Xp*%Pbr^&EVMg%Hi99Kq{)dk)Z8@w9duikmic?4SF z1eM)~kzgXLp2=EsJPqkCT}~r8GQVu?VqLorR%G)&8__-T-OcYJudY7)L-Wsu=rNPi zeRi}u-q}YH6XgL1#qBe|58qx4+2aj-8dytgPW!c%8Gl=T_%LsWa0PMs z2m;>jqzN5{ZbVo0+ux8#sJXgNoDOwvN=xl8gdHav0X=x0pOE&0;ETd(xUxIzT{egP zsKbvY#kiZgU0)#q!Vn)rrIAv%bGgxSJ*rHO`#e8>xQG|clr%Jmj*hp+Rr$yl-N0$P zbaCdGf`04Ev!&+)-D6b$EWZ7Ja9%3m*dE)9Dp?=XyDNJcaA6Xpmutm5n;o>-he3S` zn;uJSY2Fw5BweF?<-RTi{*M5>VYTVtp3?`~*MAvPmI7BJ*~NfGwEsx94F4nJ$NP2p znMIlQn+3Lm<}-xE<3=_AQvHd#B%l`JGMdqR^b+*d{Ox-8%9;c}N;qA^VS!K^(7`SW z3)!PhMlNGtn1x%^DkSK5RmM8f-b7ZVRL_l5bc9J`QVWTnBTzP{6}d~+(zHNf_0dzR!Gm9!vC)m=KA&Acb>lIv-NXry%xwGEg&n_=m)sRe53Sv zDP*K|rd}4wTxxx5gbTDC^9$o59fJDT->Qd96sz#-d%+B5WFog(z9y=v3hhw62o69g z*S;lx56Fd0GvV~4CYpb^GWx@qpnNX>JnUo!k&sR+=UH=;vH2c9j}_Ok;99=8@-DXI zF}f3P=g!Y$KH6}WYOZT+w_1+S@`3}gWHhKgP8w^bdN|ef^1(;<(Ldy;-wx_IfnU^N zek`8q#BjZk59^;nuXLJO;zsQWudnua!H+Q0RCJ}~KYD~(;!F8U8tQWnuFPIcTTNQc z-M&+UGgN`t5i(=y@Bf1O1{c?)6z^gn(^vJn&JXi?{8;`d^Y}Dh4z`EPjrqiVVl0e(!*Uorb$0F|YY zz69Tc&&}c~$#?49t$|a*PhPWys87{C!?F`LTgF2e$ zR%OPQLqy<)Bx+!4Evcvti!3CCY`)X(-j4K$#_2iru^Z#qTdk0XF;K6O;N@mJ(l~hz zi)?^vdr`RfLf!jx9h_F5Xv!(k5zQr&1K6Q(fqP#c@|6VVH-}`%Y z+4ei&^s&yk6;TYb>UOH(Cw?JMhXWhumFrjc28$3Va6Uf#UJ~Wyr&|IAtpb`X`}A!W z^A^>_g(la>z+{*p>chi%dDS78pM8oTro!ud{}_aGXgX`fihbjPiUR7`RTby!R5fJE z*WQ$h&BymQ=}>5HQNy0v$Ii_RXahTFyhBq~D zBe%uhRD_t8IAilZ7Kjs6vQ-yBtLsTGp*ihSBToFZqP6MYngjEe>BLKCO7BX=MZ^bH zY^1hL#(FRL@dk+Qpxo=W;vT9i?MLTJ}EYoe(fAT#2vlN57)E_ z>$PUKigUW061ZMa1DxXQGdjg0%vQxMViGEq&)Y8nY~lvs=ej<+r(U(=UL+&Vkeu&E2KMa-uslBj{Hm z+8kh3B;UP9Ubvz2ZFaK3#`i(JWApySfcP}_CIPjFnhVj+BQq~01umf8XV`q9?b27) zNOMg?G>jV1j8bxwJUFu^#Bu!dn(6ajohDnr^5pGXp!270Yq&{|k4)C%=z)Lap~u($ zaDo%IoKqADH%66K2fmZ2!HUgA;`=+1WrnL4hyJyg^mmm=Ty&;Y7~li--!8mZ|QmV0~93gLItu)B8Pc#Mdb{#&{PFUopha?pXnH8WAa%=h5uV+}U&`qfVjWswsZ!jenCLsHcBXHIEK`pJVz7S3Q|qS zE<1EHQ?i(7vMG*k?Yy3wtRcL`GOA*)K0)OOma=8>obUX6=hh;qYra_a*ou@OfKmuj z(303RXb}9I=Yc>Kp~re2*ZmQsdVF`CD}k!5-?pz7d&u7hABROk)XB@_H7YMH*LuI; zj@nXQ+-18dfxH1{fG!-))7zV&k|y{sp%jzv$@ihcX{s1IggHsGRO45gYWFMsp_8v5 zGU$>JHK6e3u-7hs&3@^C|}81W-`O|fZb~~iz0-g zRv|qj9=+OEnRTovF|(z^dMcUcQ+FHoY;D9%;^TXyDsI$u!dz}OfzSA|!xlO4%H-@D zU$8I7pzScFt;sU=E|A@+eD3D_i0WoRMGOZxuWGwpDq|}}unvu{x!-4-fe;i}Pfxh- zpnyoSioT3x%yqP>T(_e!iT4BWUBzOB1W;LwLev<&6vlMqP2(t2r>T1y2X}vP}%Y~-E$IsFx>k@D<`#ml`zToLr zld=wDN0F510AoeEFIOzY!S|aI$!YonRIJC7zuw@&h`B(0fq*BT4V#xJe@clw<1fTx zC*S11juz)Mz@embm9u-ykgLJ=tN%m)2yAVUF_P<(Zqf$6 z-I@!8==C`kbRncduHh((Xx>j}f>v>rqB@ zaJHGF!!4;18-8{QRq=IR@Vr6C-CbhsbmcSqdwpfMMZQ}ORl z`jI9qSVrym*biiR*&U`oW}r%)U_IZCaEW{(;;7t782f1x9~s#4n(0;T_Bh8Uo1vl) ziZ+M-MrpU*7*hmd)M5Wor;3Xv8*)2UhxgBxk=VyALAnFbdc|S)*|pfTmSjk&cV8OH zShPJa-)un;Tn&qz*KRg+nP~6k#qQyOuS`c2-!9ex%+)iVf6Cuk4*r#RNx}{h7ua9x z?&_s-Q3t5{d%g+=efm+nxbX0F;pXemH8r!&RdER-C8BLSD@bX`?SX^z+HAknGCzE$ z47)uJ52WDpKOR~;b^~QOuR?)jS+1v*sL3Pc6_1t8xK|!oi5s5!I0}q$BIsD}<|{we z7V7dzTMaJaz+W|G+06dq{SvUjLR*-wH2qs1(`e)M$^dMl+U^(&{;iu$eC$k2;d7De z=S*{obgb;7wAEox9-iyV;)rmB$a<&D3S`zJ@sF#c-ZwHY^X zE9i|OOk=qPCBNKw`PVJ}|{b_bv z_$`+JiJCuWf^;Ai|JUJHJd99P!uY-&s0;qd!>AGVNK>pYDQ)sJI^LE>a`%=J_5Qp` zD>mzFmjaTI5Yb@{y-(jAQIU&5)D0u`f6FG@GgYzUDC_J+`Hz~cX+Anba})6nMy+WvvJ2bI(V^JM+o!*RP*qDJ0S3iSY$XxtY8i zqy~?hXDXz47>-cgtEye^Z!fBR2#r&gnIu-I63?p1euE^Hmn}Iglp_v@u=!p+t<>Hk zbBc)xj#A+y+{c6qiF)TI|pftklwfgvdA zxN#RLCEt1~Frw~M2wf8n&`;&+$ zGz;4ij@3wMs`Tkj6UvNuO&zaVt5*C^xBcEd?LjGUe8GzYwVtcS#NJ;W+7LAhUCow9 z+wJCy4-S83NGtv_GmT=5gcWUs-@ZCd+pDMm!^kJ?sa}`U(=MM2&D2`U_z*pcz3KQf zDs+D|pdL}a<>3O1zdQ6wWu*`VTCJxR%g@if1Sre+apaludMGL7(>wx&<2iHU0wxpE z|GW;M!qG5=6{=kCO^#81kY&IDc(bjewFh0yxC44hcUd=H*2m)wG|2r z&;Sj?mL&83RK|5ahp1u#lb55Z*78o_!-LO7&Z5u=G)4w|U|hzb44`~(2}4UW^?shM z6mc+~d(^9r_mY{k@sOF_6{ZkTz$h*>qi3B?<8_c~{+V41yHMPiLEswN*mk~SD8^`Z+=Yh6sqC`%-CbH^cf~W=fL-5{0mXv(DXd1Y%WErtsdig z+TV>KR)MkNl$M1C5ajhE`*do_ib_HlOqG1i=5JU633yJAZIaadU?hy>zV-8R1O#iE zr680sW;ibRARj3sMugsmG$pOKd!Dj)hD;;58gMcY0)38RJF#gth+J<+(#WdAR1U zU^Zdj7zYTBQL)7%9S@TC3kNGzN@jQ5k@;x#&TX{GVi+AdKpXa1K$Bl9&93*CV zp4#0U=(;X{Up$2F{30>7;Fe8{3OvHw5(5VKnQ%uHmbB_o0|si81*!E#*$x!>b0$lx z4=sI9(pXhF7R=)a5aT0hzC-t>v==X}6ij?RtZDf^!xERRZKwQVRPAO3THk>jmV)F- zHEbm%@fA!TEuq@hoxOcMZJtXfXGyIYfhBtr*ynNR9A>8SoqyEk>yO0)6%o{+62(@Y zJR*3wF84isz}4vK6lOmfuBXK--GG5-dqDA7BTCqiCWUWNMWJ&*sl8Sj#3|rV3^@ZW zM%p0d<0*Il>?V`uc03R29~zuEuH5VxTxodAME&hfCK{eGRVqoX+YlArix*xtH18=Q zJ|FS5>Vms{(=rzCzQ-Ui@B338v)iPYTcLV}H}}rcgWpZFUpQnl3@P0lijamB@fI77 zQ(4hqc?@-k(;*M{gEr(v7eVTjJC@K-k1v9Tj_(z>e@EqYs7ZW3GS*#Oyr-KnXPmwp z+du&Ld_NZ|4}$ppcwe=eb}qau)!2}88wDfmg_z*Ub7W~XywB;c_1+gcuJ6@R(%|if zkM|?V=)7wVx7wVcY~y}(&0Dr#+q$0^i9yZdDx9zLH+ZPXP#rmT1f zU%-Z5d&5@(1D=FhN7>TnbZIt5S5BNy(Q{x zEQ(fk;&sYy_sgxDr8v`HcA6 z$}WiBHAU5iw?*!cn>r3RrNQm0tiq{IMG7wWPeL3=E}S-%?Te_aVovigJ$8!+|l*hr=H^U{zets4YlgUMYtE5&LlD>XcioQf1KA+YZF0!b#0 z1`6Ta=zK`0BowmeiF8mu39+L}okl!$OREFt#f)BFZiV=qi;^lE}pyd5h_tTvx#Rs&J?cJ6rv zTn%^bV88f2O#MV%5f}?Og6W*4POD@+kjcb9)&q&LEZ0wVM`8JAGNmj;XziwsXS^BbnG-1*z=Xy{lEs-ChFoMZwD<@Nl61f3!zNVRcfxghKIOWn zz@z5D<)i!bKTh#SD3DKEph8+YxOw#MoZ3b2Z;U+GV@^lq#c7PpD13#U9(vYEe>vgs zMQ8jz2%|VT8RVs_`%^8x^~2)8A5G9!Sp0cJO2o>uVw1_TcYZN;+L`X!lfz#G9Mqnn zi0+-&Vsr2#a$AtSFPB;GJgMKp22~|t6_R)!>#Tp?@_`8mlMo6A90!{vtcj3ymvX{a zC!G=Rf}V=kmvA^#nr};Ap#rRdy<+vMmfuFIW!t{$z2O||l4ycx#@{dlfi$0ARcw54 zi{9K30hGckRYnD2C*`X(E7sQ9vUvJ_NYGg@IW&#VBdMg%{Dll$6Dd5qtig2`b4)1h zxIMkUG70%{?X}u=f=Ud3>2{CJu0Qmb6{D2lfyRDILn>%`pYV0#el|CmXxl0V%D!)i zREXFg222rj$fLMB=x|Aa+_$&Pk>6l*ZR+zAKmER8lvaws)Ge4TE{F+bp;B;u-#PUY z`cba&(}2g{=JO{1vjH9_&4b@(>nsoW9`?9sv?#+r`^-}3I!^nRm_z-Q(?xs9{O0mq!*W zGe&?^okw-Mp}u8aeMKkLx2gVk_;}+#a^ealE?%O)%^6-G4uk@3{Rs0Fsh=I=Z@}!Q4`{H)IGo}+w)rSjKb}aRu4~KCvAOw& zU;X2BT1-B1L}EIez^M7l_Ih#WLJ~6S=teX~SUlX`96}|NZj@)+_b!bDR{~sj2r)$v z6%A`jUIG3LD^$dSFvw_qN|OwokZt|7%Ue8Ge&kz0@&uMu%AG!i{NGaT*RFh=K-T*J{$@M_TjMOLtkcCJZVxP2GUO-RN+Br_1moANV$La=&i4BH7XK0y!f#rGDiX zrxFWsxmaX@_G3x_%(8R5-S0U;HIPT+pIflt7=i3wk~1s?Wss5i%4(8QS{Sa-m)pM* z6-rek2WaCg@;mAG#;v{=PE(}!AAI&m>Im@6u5@9nRG?fi8Q!}ECUQVJwZ=ja!pi57|alNUlOo;MVJ=$cWx+stTl}AC}XENPQ zaOJ0pF}!;1+JP@N-~vX6Los7rpUrH;f`U1JSqVwrf5I|)u4nd=Soxy%k{O$ct4Ao$ zfFfiTbj@tDC-Fg3nlrA3zC-_KMh%D~$Ra-o>A=V`;j%W(KGk#)E)z~SF`54qULj?d z^A(4Ff~FBvsW6?ZiUtZd3(M#zT{mND8{X6A%cS+?SFzq-?<8de zcZ)-}`Q74r0F-(_gfTVMC-R#ee{Qr4X);JA|2!p)L}D@H(J5t0lRcU{YOW}yT5k^zu=4sM=!Z^sf*`N&2|Z#+7H|&v-l8c?m}mt? zPwkSQLKJC^jU(|S0^JBrkoT+tpc5WD#R$lXP zTXS{SN4J=%?VxC*_#h?|!-%1>bYb;?M9lWRWgYbrX^8U`a+!*@j%@PRv~g5b<&sQhtU z`-8AnD<6cElP^a4bB}WJvfvKAAZcBL)p|Yc@NIJP5=E-_K!St{)j#HTrOhV#l%pJt z89^g@#=8QZ>gdA8di5q7&buio1|@C${+HTmDQcPlo$86wL7{4v6D^p?#IJ-z&_q=){ac_^BfUni8*_OCKLQ(kXIwITh&& zQL65`K9#mJZe6YAJcs%kn+dk{_K#Qi9Sufzx?M#yze0e@Bw^vJls`JsHyj9 z?PgOD%)dN}`+A)QWTib4V2T-13bW}I=++KiyN6v{Il7gk)4bGPPWfE+3x-E%G5KI!Q3s0`7@$J`s zYfMAVUNB?!@87|qx!rBfC~xR6@1(>z;Y201z6_RaGSpt51V84IzU-@S z-mFS~C?|p>{CxIzB2pb9)ycc4=!}nUZc=i&?PCX4$*T z08xCS0iyBd3h3*p-|sAF04J%Lwc~>ubon_jjQ?Aq1Oq^*>sJ@r?w%CM+EtTaq_VDZ zXc^SP%OVy-{8=oNNz3AR+=&FEy>W9e3@AryxA!~~=^LIfxo2x~H^LIi z@Vc@omT|)I-|~Mv<2N5G-NHs&A!G<_o!D9&t-MaSEgWRF!!S$>2`)%4jR(DFTEx&` z7{`#X$GQHJA)+DwEU4w!TVZKg7>Pk{l8_`Bg;Lq-m7WeBBilc{N#b7HR~0sp=}(^? zovz)^hk~)ST%M-sfa+J)awQ6rq{ROYNRoeFq~-12k|x&Sx;BsM-PllIPl?NvMwdPr zWHftosijC|c~z7DNFb|4EnhG#8=Aj(z1bC-gP zIA1U7R!~;dLav&V+HOeFy^wH^|^kO*AAvkR(MO_ z9iQ*v$qCYdf?6vdIjie`CkZ>xw`@aqbP;C5Kit}L7B4I`UT>tq5xYsmYB%$XbD~e8 zwmJ?qErEts%kizTsPBa9+GzdH)Uh)s5Axah(%U`<7@1pUwYYU?M&XdMD~;}m$7xAR zkJB;A(`&X+F}3Ge(Jfj0z7ndjdHs3WeED+57krSe{fCO+bU$iV#}mh2I89YGiN1Rm zHmJ44oi|Gx1ifKxV_iRIvofvSY-!zNY#mpd;>`z-X@`-&GcL}JjhjlHHu62Y!&%Sq zeSCQ(LDFN+{SYBMZ^_C2JT0`5u`^J?G>d}S(FcoAl?X+{`69VCCDC8Th{?Bsj(zq{EIfKIOAEnYrINat%6PO?8jX3Z z?7!-rmV0Qi+5Fgp8N*M{GTt7373^>ln2>HLS^{fe3FZ{yZx8fcsmo_AV-j>k0}lBX znie)zKA*NdRxguX33+VIN4yWJlOkwFnKY*TDrr;!Qb7|YVC7|8idMJddvaVlM^e+w z71Gke!dv$H?x)GijgV<)8OmQWo66i)BA+wlwEzC9 zT!&lmzHDN}C#j1|kwMTL=x{NNO5Y=JeN~1}JSb_ zJw2(?Cp-iWE&K~9%!C;HZzP=u{ZwPR2asf;&63z}~H8>?c}aeS+kj4zrnQswR!MMuEAa>rfhdWxvu?GeWo!Iu=o zf54vZj==l%;Nx>L&;SCvX%U5b%I=)AF_^LDRyE#L>z0SeUAbFX= zr%Otx=s@_7V5`zH&~ZHs@x}$&(++E|^WH*i$kMS@~@Mrl2^O%8km}i#Ph8b>ht8lMTZVXhv#eh^(qa z(=$4Ao)WY#*uReJi856{qBn#?PPu`_F^B(TXfm4WM1=>@vS6~Q**$3fKU^!1F4(h? zk)RdPUD3o!rDPK+>a1>mMJqJC^VbS!%6|si@DzbU9-L?yItrZ?SHY*YYR9scDLt|v zm|jQwa}IiK^-ft}KJaPpdvqZV66DrXPK=W_4*e%=)BOsi^7InsBC52R;;#uuO1HN* zva38|iYT8-_67}M{iPriV%@1skCl@$<5_Okq_IP^g2JPzW|Z2u+l!*ByK!Kt2q|nM z{`g~}-El)!RkdXeRE)%9hnNYEY1@34mH$J>=;C^IxdpWWyhI-0!D5is=aMC=C#bph zSz@b>O%?9e_+4SKBv{zI7>ew+CF_y-TZvmpk?8s7!E)``7=IF~COv9wT>XqqUWl%H z0mep>#^d9T&KH-UK;P1e#DrQJDVMr}Li%_#W~oCnIe}Z@5DH>c^p52;*B@2-xBn2C z1tn)34HE?899z-8WA%uRL-H9q<6Nim#GmxQLO{PUL=Ib@)_}WGA`97@M;?3PMuJp5 zO=72yJnqYFJ0!pcy=w7U6GrK6Fw;-_WI-RtCR>dNxC4O|YWe57iRp zf`Vs!zH@%BVvxoI05A3Rqrf>q)W*^F+P@N?!l0hrc6GMpy>O8z0uCi@s(t`MLCFS# z#h=$jcxCRN*y37=^>4#@D`Pp;!b*=}ir_kC6<}?a1QIG-($su{^EE0Qsbr(=vvoRq z#|w?91EH{KY=J0`Zvv7FXRTavS~rp3_Pl^HWzk`9_IMe391?b+YSl%X-sIu$Gx%rj zt{LVeLc!_Fqb4uU^fqTpG4d!S7R63ssXeZ$X4f~fyW&OnLa9wyBaQ-d75ePoA^xI@ zD4y1fet+wZCz4S{Ie!ED_~bbtXa!9`>h2&9#^biSL(UY7H>!`Okz2%5vUGKJ3H@$` zk6FZ}K}b!p2muP*+V@YYPQQWYih6XAhWGSoEuK#Mmp5uQz3|e4Lb+q?L|O{G5^!N` zfAGF{SVg|8}l=0lWf`sxJxQgDT(_YrXmYzPkRZ)=fCv zQ^d=V{5gk=$$In?*aO!ds(NaiHD!{Z)k$36(YLO=IQg70E4Ax>c249G;tN0!AKijY zx(y}hvn&ZO2@Rj%%Nn6on*QXJ=`=Y*x-2bs95*IcM6Y&<{wvb!Q&t?63At4>QP;QU zDxX8*84g#qatUq7W0u!6m=n-0#SX+$ExN%JS`+vT52M~yF) zxHu{Y)aXzu{Mr)Q|FLrW7nT|^3OM$-xr;N9Qe`nIh&im4{|Z=G%H()rR455kF$-po z(V0h!p73;k`H$@+!mvqLcSy&&5dw^&Gv;>7s`4{*m2CSM0eMHH$TZFQ97rlZpd+8c zphlN#{nY;ssq61S5tO23dGC*X=lS#dNr~BC5{O_aQ_D)$^6Ya2>l!os&BF0187!xL z<=V}i<&F)PhBE@%Oe@~*dI#&UfIGLV&)GE>_bQlx_P{QmC0xGChj%olL@N>v*rfwA zax1l`yB{P8ZL=gSeHDwr^LI@EJjRoWPDHNM7U?WaM)HdTh6obq}}t7H!SWOP?L9yp)| ztbs%Fs8U-$e~ug%Ja0=%j24|#>IyWxpvM5J;Nt#1-S!-<{*c&`SYFd{mc_Skye z^XKAvi{+NulJ4s{Kd{B^qT=~1v+E^VKhB_NZ#`OVU6LRx0YNuMnm!|) z?=;Xlp>1#O+Fd*%Z+R{bT~6HsEl!KQ`9Qe~(69iW6fv4uOoXkuxqbWQ-xk||i3mwPzn1KZ86laE`#>a3D4yH)vE!TNwyMX7!M%$^E5l>^jz`Mm7kZpRV(vw^{ZRaxGVRPP4GW?_il2b}FZ_2#Uoc^v^@I+KZM1it&q97vu0(Q@DxH(L4V`wyt$)gDav#q zFQ1ivVmKXKnga@eM#$vng9;)nl?T^DcA1C_XjsPYG|<- z&o>Hi8dgTn2&!^(D_jdz7h69LSG%6MUxJCRm56E48Wm)#hFe#qOh@YVX5sXVc8l3S z>&uz+$nug;-!R#Kkm_arPGBwdBrcy#*I9?NVUE#(T(F1?zhc%NgXr0=Wo6Y$Z<;Il zY-uT#Il}9yV1tdD4yN3DH|mUS%qNB~BfPJOnEKko)j3vRGSPUBQOh0^B$y&*$Bz&7 z(tb0w;()}$cNBFNv+|Y;{&5wb{FOF$y?B3OQ$Kva4_;pV6L}LIw@&V|B#}Xad+FxJ zZs%R9n5?Cdsps)hDCHyd@&^fzsZEv*t?1^Coxo!G?gsrKiI6{-YVJ=Tv~3i>rEJz~ z0!$8XtHHZwt5bE0a%KwGC-fy(oTi^83fAcLWd|0r9{U?zX0(KNQp;bizrV--BY$YvnWX7_Tu$36 zKc};&7FTc%r|{fDE3ke&o$%cY8`vX#U$S-7{^DIfy3%q?i!J*|Jfy^uUxU~I_7uN@ z-ChmM2(lgn5+E@l5De3uGDtM`P%Pk3lphPk*)6{X2@!zo2uTPJpCLeW)0;zel|h97 zp|}N=*YheCfDDNOzM3e4MD*IeR4yV9TY>d+M4n}uVxSqAzNVs1Q&eEh&%rK?GfSk2 zP^@9iSDCJm604W`I#GiF-VE@+1{|1(JnCs@vZDPU{^rFMB~pYARzavRzg%`nast?7 zDto@ncK#oYc>gZSqsmwq7Ye`1V@t{iIhv9ODWLr938J#ksB{=pU-uHw7u%twho3NQ zo$0oeUlSn~E$IZFz>U&M=T(MYtRA~!<+l`z6g{T>HOUl=m?C@xyGoACg7hW|Ig?DDU(}U6dLvFcNwAs6riX}%=ZL4_S&{PAtC_}yQk{G4=uOwOj z!11z+=VI3^$ZSFKn-6D2gVlp4LHL(1#0~EC(ikExQ}XSnCv%s zo+CD|967cP;)2xRadm2$>FJ@OdHrbcg0}%fgkY^hiIbo{)H39iFGQMBgwh#W=LUBCje&9N&#=k zPznV?#+IAwBYcMb3Z>Y}W=(mShv44Q4Nr=TLFDhF1q5+P%%c9OujkEFOty9RHvRRSen%xHokHNMs3MV^o%xXlKVMiE&a7F|0*w%1NI7aIWV|e@xFr@4PcZ-mdH9GRl%0;; za-K0|_^HLKJ?LUXkN?O4^>>@7qCF|A`jHTo0X>{K(HXq?XbW^>siIyv5x_(Qnv^FK zaGA}WD=erxfkrQv`);Tb3YKk&RdZ$KOjSe4B2>%3HruFIe*a+x1h6&+t+)jVd3X!m z*^~vA5C@;}Ix`kG#-8zfgsSKJ;CN>rtiJ>wJXgT)3rq5tMO(et6y(IsdPc)n){aU3 zNo^FhrpXwlYAlVC${wd;T`?X&aDsmf zDQ!7CK?kOe(#j`W#cak?xm$vqJ7hVLqYV=gqGu|mu_nL;J%9+B+RiSB@QrOI2n&W& z9L7BW8QP5#YkqfH5}%)Dh85wrOp zovlV)O4+V5^m3eOxP(_re0*@tE!>Yku6THmi4!cSZLDd@m!s24cd(UyU*ZwlEM&qG zse%oJSt)!@V18a%h<0vex7w{^DQp*#LAt))coD~K@%=onrxBg#9#!0NOhs?aac?M4 zXfIqB2RpJ5@_V5LJ1LXfgRqU1VxzG2DEFk1gBfY}-;+l6OXsG`C;qLoGBGOSMd3c! zFGgXnriM@sbilVFIoKCW_!5)|U2GE~7HegMa_55x7%O8}m^;nAM`OH_LEroT8#@@iA?3`)V)%3^ui>V=}jN!5~QOHPF3+L(Boy(=wT zSMU$6_zzB@v!9UvFp>DEW3a7QvLFed!HFdQgY>QvU0S1*t>}?QL)@8Q_Tgs3q`3E- zGd8<-vC%y=1 z^@`KGclT_zZf=*WTlI}+@gA1wfziJ(6vS?*3MDQ13~NpDX#TQ-_m7AS&7rG)!X_UU z)sX=O`>)QPU2HUOtT+0l_dILC|E5Eajs7LBAa;^JV6@$&fm6=Bhf`;sl>>GvkCY)5 z<-VslBG$?D`7Rp3|5_(PP1w~%_ub#qtH+^tE4E30YynwiUd*QgCZaF z34FjKa()-*cUU;Dt=(sDt@GR48i*RR5hk(|5Xsr-m@gQ!oP+=X07gkfK~x#=zcfW6 zx5bn)bt&yquEM~QK=OqVhlvP3Uc?}!=6cazS3t@i=Z{r3Iu5Gnp^HeXY6g=}`0}er fC5Zl~lgs`;_aNC1zY&Vw00000NkvXXu0mjfFw`Qx literal 0 HcmV?d00001 From 9172b37fe8dfb09bffbabc7912326127fa438729 Mon Sep 17 00:00:00 2001 From: Robert Lodico Date: Sun, 26 Jul 2020 22:45:40 -0400 Subject: [PATCH 18/45] Move GameStates to Client Framework --- .../Enums.cs => Intersect.Client.Framework/General/GameStates.cs | 0 Intersect.Client.Framework/Intersect.Client.Framework.csproj | 1 + Intersect.Client/Intersect.Client.csproj | 1 - 3 files changed, 1 insertion(+), 1 deletion(-) rename Intersect.Client/General/Enums.cs => Intersect.Client.Framework/General/GameStates.cs (100%) diff --git a/Intersect.Client/General/Enums.cs b/Intersect.Client.Framework/General/GameStates.cs similarity index 100% rename from Intersect.Client/General/Enums.cs rename to Intersect.Client.Framework/General/GameStates.cs diff --git a/Intersect.Client.Framework/Intersect.Client.Framework.csproj b/Intersect.Client.Framework/Intersect.Client.Framework.csproj index 4a66f0aaa2..94f6337355 100644 --- a/Intersect.Client.Framework/Intersect.Client.Framework.csproj +++ b/Intersect.Client.Framework/Intersect.Client.Framework.csproj @@ -60,6 +60,7 @@ + diff --git a/Intersect.Client/Intersect.Client.csproj b/Intersect.Client/Intersect.Client.csproj index 3192b79609..ae026f2e52 100644 --- a/Intersect.Client/Intersect.Client.csproj +++ b/Intersect.Client/Intersect.Client.csproj @@ -106,7 +106,6 @@ - From a4a3cbb9c05a480c9d3c33da6a8c098b2502acd1 Mon Sep 17 00:00:00 2001 From: Robert Lodico Date: Sun, 26 Jul 2020 23:11:44 -0400 Subject: [PATCH 19/45] Moved interfaces to the client framework --- Intersect.Client.Framework/Content/IAsset.cs | 9 +++++++ .../Content/IContentManager.cs | 27 +++++++++++++++++++ .../Interface/IMutableInterface.cs | 0 .../Intersect.Client.Framework.csproj | 8 +++++- .../Plugins/ClientPluginEntry.cs | 9 +++++++ .../Plugins/IClientPluginContext.cs | 0 .../Interfaces/IClientLifecycleHelper.cs | 0 7 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 Intersect.Client.Framework/Content/IAsset.cs create mode 100644 Intersect.Client.Framework/Content/IContentManager.cs rename {Intersect.Client => Intersect.Client.Framework}/Interface/IMutableInterface.cs (100%) create mode 100644 Intersect.Client.Framework/Plugins/ClientPluginEntry.cs rename {Intersect.Client => Intersect.Client.Framework}/Plugins/IClientPluginContext.cs (100%) rename {Intersect.Client => Intersect.Client.Framework}/Plugins/Interfaces/IClientLifecycleHelper.cs (100%) diff --git a/Intersect.Client.Framework/Content/IAsset.cs b/Intersect.Client.Framework/Content/IAsset.cs new file mode 100644 index 0000000000..d9d47b7081 --- /dev/null +++ b/Intersect.Client.Framework/Content/IAsset.cs @@ -0,0 +1,9 @@ +using JetBrains.Annotations; + +namespace Intersect.Client.Framework.Content +{ + public interface IAsset + { + [NotNull] string Name { get; } + } +} diff --git a/Intersect.Client.Framework/Content/IContentManager.cs b/Intersect.Client.Framework/Content/IContentManager.cs new file mode 100644 index 0000000000..27e95a2605 --- /dev/null +++ b/Intersect.Client.Framework/Content/IContentManager.cs @@ -0,0 +1,27 @@ +using JetBrains.Annotations; + +using System; +using System.IO; + +using Intersect.Plugins; + +namespace Intersect.Client.Framework.Content +{ + + public interface IContentManager + { + + TAsset Load(ContentTypes contentType, [NotNull] string assetPath) where TAsset : class, IAsset; + + TAsset Load(ContentTypes contentType, [NotNull] string assetName, [NotNull] Func createStream) + where TAsset : class, IAsset; + + TAsset LoadEmbedded( + [NotNull] IPluginContext context, + ContentTypes contentType, + [NotNull] string assetName + ) where TAsset : class, IAsset; + + } + +} diff --git a/Intersect.Client/Interface/IMutableInterface.cs b/Intersect.Client.Framework/Interface/IMutableInterface.cs similarity index 100% rename from Intersect.Client/Interface/IMutableInterface.cs rename to Intersect.Client.Framework/Interface/IMutableInterface.cs diff --git a/Intersect.Client.Framework/Intersect.Client.Framework.csproj b/Intersect.Client.Framework/Intersect.Client.Framework.csproj index 94f6337355..c9ae5aadbf 100644 --- a/Intersect.Client.Framework/Intersect.Client.Framework.csproj +++ b/Intersect.Client.Framework/Intersect.Client.Framework.csproj @@ -58,6 +58,8 @@ + + @@ -71,7 +73,11 @@ + + + + @@ -242,4 +248,4 @@ Other similar extension points exist, see Microsoft.Common.targets. --> - \ No newline at end of file + diff --git a/Intersect.Client.Framework/Plugins/ClientPluginEntry.cs b/Intersect.Client.Framework/Plugins/ClientPluginEntry.cs new file mode 100644 index 0000000000..77c7a7e98b --- /dev/null +++ b/Intersect.Client.Framework/Plugins/ClientPluginEntry.cs @@ -0,0 +1,9 @@ +using Intersect.Plugins; + +namespace Intersect.Client.Plugins +{ + public abstract class ClientPluginEntry : PluginEntry + { + + } +} diff --git a/Intersect.Client/Plugins/IClientPluginContext.cs b/Intersect.Client.Framework/Plugins/IClientPluginContext.cs similarity index 100% rename from Intersect.Client/Plugins/IClientPluginContext.cs rename to Intersect.Client.Framework/Plugins/IClientPluginContext.cs diff --git a/Intersect.Client/Plugins/Interfaces/IClientLifecycleHelper.cs b/Intersect.Client.Framework/Plugins/Interfaces/IClientLifecycleHelper.cs similarity index 100% rename from Intersect.Client/Plugins/Interfaces/IClientLifecycleHelper.cs rename to Intersect.Client.Framework/Plugins/Interfaces/IClientLifecycleHelper.cs From bb718411d14577ad466814ac1806cf079078b87d Mon Sep 17 00:00:00 2001 From: Robert Lodico Date: Mon, 27 Jul 2020 00:13:34 -0400 Subject: [PATCH 20/45] Cleanup, interface changes/additions --- .../Collections/ReadOnlyDictionary.cs | 100 ------------------ Intersect (Core)/Intersect (Core).csproj | 1 - .../File Management/GameContentManager.cs | 25 ++++- .../Graphics/GameTexture.cs | 6 +- .../Gwen/Control/Base.cs | 76 +++++++++---- .../Interface/Game/GameInterface.cs | 6 +- Intersect.Client/Interface/Interface.cs | 16 +-- Intersect.Client/Interface/Menu/MainMenu.cs | 4 +- .../Interface/Menu/MenuGuiBase.cs | 39 ++++--- .../Interface/Shared/OptionsWindow.cs | 2 +- Intersect.Client/Intersect.Client.csproj | 2 +- Intersect.Client/Localization/Strings.cs | 2 + .../MonoGame/Graphics/MonoTexture.cs | 40 ++++--- Intersect.Client/MonoGame/IntersectGame.cs | 89 +++++++++++----- .../Arguments/ArgumentValues.cs | 2 +- .../CommandParsing/Arguments/EnumArgument.cs | 29 +++-- .../Core/CommandParsing/CommandParser.cs | 2 +- .../Core/CommandParsing/Commands/Command.cs | 2 +- .../Core/CommandParsing/ParserResult.cs | 6 +- Intersect.Server/Database/DbInterface.cs | 4 +- Intersect.Server/Entities/Entity.cs | 2 +- Intersect.Server/Entities/Trading.cs | 2 +- Intersect.Server/Maps/MapInstance.cs | 4 +- .../Networking/Lidgren/ServerNetwork.cs | 2 +- .../ConfigurableAuthorizeAttribute.cs | 2 +- Intersect.Server/Web/RestApi/RestApi.cs | 7 +- .../Web/RestApi/Routes/V1/DocController.cs | 4 +- 27 files changed, 239 insertions(+), 237 deletions(-) delete mode 100644 Intersect (Core)/Collections/ReadOnlyDictionary.cs diff --git a/Intersect (Core)/Collections/ReadOnlyDictionary.cs b/Intersect (Core)/Collections/ReadOnlyDictionary.cs deleted file mode 100644 index c2117bc82f..0000000000 --- a/Intersect (Core)/Collections/ReadOnlyDictionary.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; - -namespace Intersect.Collections -{ - - [SuppressMessage("ReSharper", "JoinNullCheckWithUsage")] - [SuppressMessage("ReSharper", "ArrangeAccessorOwnerBody")] - public class ReadOnlyDictionary : IDictionary - { - - private readonly IDictionary mInternalDictionary; - - public ReadOnlyDictionary(IDictionary internalDictionary) - { - mInternalDictionary = internalDictionary ?? throw new ArgumentNullException(); - } - - public int Count => mInternalDictionary?.Count ?? 0; - - public bool IsReadOnly => true; - - public ICollection Keys => mInternalDictionary?.Keys ?? new TKey[0]; - - public ICollection Values => mInternalDictionary?.Values ?? new TValue[0]; - - public IEnumerator> GetEnumerator() - { - return mInternalDictionary?.GetEnumerator() ?? new Dictionary().GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - public TValue this[TKey key] - { - get - { - Debug.Assert(mInternalDictionary != null, "mInternalDictionary != null"); - - return mInternalDictionary[key]; - } - set { throw new NotSupportedException(); } - } - - public bool TryGetValue(TKey key, out TValue value) - { - Debug.Assert(mInternalDictionary != null, "mInternalDictionary != null"); - - return mInternalDictionary.TryGetValue(key, out value); - } - - public bool Contains(KeyValuePair item) - { - return mInternalDictionary?.Contains(item) ?? false; - } - - public bool ContainsKey(TKey key) - { - return mInternalDictionary?.ContainsKey(key) ?? false; - } - - public void CopyTo(KeyValuePair[] array, int arrayIndex) - { - mInternalDictionary?.CopyTo(array, arrayIndex); - } - - public void Add(TKey key, TValue value) - { - throw new NotSupportedException(); - } - - public void Add(KeyValuePair item) - { - throw new NotSupportedException(); - } - - public bool Remove(TKey key) - { - throw new NotSupportedException(); - } - - public bool Remove(KeyValuePair item) - { - throw new NotSupportedException(); - } - - public void Clear() - { - throw new NotSupportedException(); - } - - } - -} diff --git a/Intersect (Core)/Intersect (Core).csproj b/Intersect (Core)/Intersect (Core).csproj index acc19ed667..3e9db0438b 100644 --- a/Intersect (Core)/Intersect (Core).csproj +++ b/Intersect (Core)/Intersect (Core).csproj @@ -224,7 +224,6 @@ - diff --git a/Intersect.Client.Framework/File Management/GameContentManager.cs b/Intersect.Client.Framework/File Management/GameContentManager.cs index ffdc0f9886..d0b2b9cdd4 100644 --- a/Intersect.Client.Framework/File Management/GameContentManager.cs +++ b/Intersect.Client.Framework/File Management/GameContentManager.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -172,26 +172,37 @@ public string[] GetTextureNames(TextureType type) { case TextureType.Tileset: return mTilesetDict.Keys.ToArray(); + case TextureType.Item: return mItemDict.Keys.ToArray(); + case TextureType.Entity: return mEntityDict.Keys.ToArray(); + case TextureType.Spell: return mSpellDict.Keys.ToArray(); + case TextureType.Animation: return mAnimationDict.Keys.ToArray(); + case TextureType.Face: return mFaceDict.Keys.ToArray(); + case TextureType.Image: return mImageDict.Keys.ToArray(); + case TextureType.Fog: return mFogDict.Keys.ToArray(); + case TextureType.Resource: return mResourceDict.Keys.ToArray(); + case TextureType.Paperdoll: return mPaperdollDict.Keys.ToArray(); + case TextureType.Gui: return mGuiDict.Keys.ToArray(); + case TextureType.Misc: return mMiscDict.Keys.ToArray(); } @@ -214,50 +225,62 @@ public virtual GameTexture GetTexture(TextureType type, string name) textureDict = mTilesetDict; break; + case TextureType.Item: textureDict = mItemDict; break; + case TextureType.Entity: textureDict = mEntityDict; break; + case TextureType.Spell: textureDict = mSpellDict; break; + case TextureType.Animation: textureDict = mAnimationDict; break; + case TextureType.Face: textureDict = mFaceDict; break; + case TextureType.Image: textureDict = mImageDict; break; + case TextureType.Fog: textureDict = mFogDict; break; + case TextureType.Resource: textureDict = mResourceDict; break; + case TextureType.Paperdoll: textureDict = mPaperdollDict; break; + case TextureType.Gui: textureDict = mGuiDict; break; + case TextureType.Misc: textureDict = mMiscDict; break; + default: return null; } diff --git a/Intersect.Client.Framework/Graphics/GameTexture.cs b/Intersect.Client.Framework/Graphics/GameTexture.cs index cba0ebcc6c..d0e76ab7e3 100644 --- a/Intersect.Client.Framework/Graphics/GameTexture.cs +++ b/Intersect.Client.Framework/Graphics/GameTexture.cs @@ -1,9 +1,13 @@ -namespace Intersect.Client.Framework.Graphics +using System; + +namespace Intersect.Client.Framework.Graphics { public abstract class GameTexture { + public string Name => GetName() ?? throw new ArgumentNullException(nameof(GetName)); + public abstract string GetName(); public abstract int GetWidth(); diff --git a/Intersect.Client.Framework/Gwen/Control/Base.cs b/Intersect.Client.Framework/Gwen/Control/Base.cs index 53ed8199b0..67e2cfb67f 100644 --- a/Intersect.Client.Framework/Gwen/Control/Base.cs +++ b/Intersect.Client.Framework/Gwen/Control/Base.cs @@ -11,10 +11,11 @@ using Intersect.Client.Framework.Gwen.ControlInternal; using Intersect.Client.Framework.Gwen.DragDrop; using Intersect.Client.Framework.Gwen.Input; + +using JetBrains.Annotations; #if DEBUG || DIAGNOSTIC using Intersect.Logging; #endif - using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -860,26 +861,32 @@ public virtual void LoadJson(JToken obj) AddAlignment(Alignments.Top); break; + case "bottom": AddAlignment(Alignments.Bottom); break; + case "left": AddAlignment(Alignments.Left); break; + case "right": AddAlignment(Alignments.Right); break; + case "center": AddAlignment(Alignments.Center); break; + case "centerh": AddAlignment(Alignments.CenterH); break; + case "centerv": AddAlignment(Alignments.CenterV); @@ -1369,34 +1376,54 @@ public virtual void BringNextToControl(Base child, bool behind) } /// - /// Finds a child by name. + /// Finds the first child that matches the predicate. /// - /// Child name. - /// Determines whether the search should be recursive. - /// Found control or null. - public virtual Base FindChildByName(string name, bool recursive = false) + /// The delegate that defines the conditions of the element to search for. + /// Whether or not the search will recurse through the element tree. + /// The first element that matches the conditions defined by the specified predicate, if found; otherwise, the default value for type . + public virtual Base Find([NotNull] Predicate predicate, bool recurse = false) { - var b = mChildren.Find(x => x.mName == name); - if (b != null) + var child = mChildren.Find(predicate); + if (child != null) { - return b; + return child; } - if (recursive) + return recurse + ? mChildren.Select(selectChild => selectChild?.Find(predicate, true)).FirstOrDefault() + : default; + } + + /// + /// Finds all children that match the predicate. + /// + /// The delegate that defines the conditions of the element to search for. + /// Whether or not the search will recurse through the element tree. + /// All elements that matches the conditions defined by the specified predicate. + [NotNull] + public virtual IEnumerable FindAll([NotNull] Predicate predicate, bool recurse = false) + { + var children = new List(); + + children.AddRange(mChildren.FindAll(predicate)); + + if (recurse) { - foreach (var child in mChildren) - { - b = child.FindChildByName(name, true); - if (b != null) - { - return b; - } - } + children.AddRange(mChildren.SelectMany(selectChild => selectChild?.FindAll(predicate, true))); } - return null; + return children; } + /// + /// Finds a child by name. + /// + /// Child name. + /// Determines whether the search should be recursive. + /// Found control or null. + public virtual Base FindChildByName(string name, bool recursive = false) => + Find(child => string.Equals(child?.Name, name)); + /// /// Attaches specified control as a child of this one. /// @@ -2715,46 +2742,57 @@ protected virtual bool OnKeyPressed(Key key, bool down = true) handled = OnKeyTab(down); break; + case Key.Space: handled = OnKeySpace(down); break; + case Key.Home: handled = OnKeyHome(down); break; + case Key.End: handled = OnKeyEnd(down); break; + case Key.Return: handled = OnKeyReturn(down); break; + case Key.Backspace: handled = OnKeyBackspace(down); break; + case Key.Delete: handled = OnKeyDelete(down); break; + case Key.Right: handled = OnKeyRight(down); break; + case Key.Left: handled = OnKeyLeft(down); break; + case Key.Up: handled = OnKeyUp(down); break; + case Key.Down: handled = OnKeyDown(down); break; + case Key.Escape: handled = OnKeyEscape(down); diff --git a/Intersect.Client/Interface/Game/GameInterface.cs b/Intersect.Client/Interface/Game/GameInterface.cs index 86b7b89c15..32280c528d 100644 --- a/Intersect.Client/Interface/Game/GameInterface.cs +++ b/Intersect.Client/Interface/Game/GameInterface.cs @@ -17,7 +17,7 @@ namespace Intersect.Client.Interface.Game { - public class GameInterface + public class GameInterface : MutableInterface { public bool FocusChat; @@ -77,9 +77,9 @@ public class GameInterface public EntityBox PlayerBox; - public GameInterface([NotNull] Canvas myCanvas) + public GameInterface([NotNull] Canvas canvas) : base(canvas) { - GameCanvas = myCanvas; + GameCanvas = canvas; EscapeMenu = new EscapeMenu(GameCanvas) {IsHidden = true}; InitGameGui(); diff --git a/Intersect.Client/Interface/Interface.cs b/Intersect.Client/Interface/Interface.cs index 068e8df296..a213e04bef 100644 --- a/Intersect.Client/Interface/Interface.cs +++ b/Intersect.Client/Interface/Interface.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using Intersect.Client.Core; @@ -43,9 +43,9 @@ public static class Interface public static bool SetupHandlers { get; set; } - public static GameInterface GameUi { get; set; } + public static GameInterface GameUi { get; private set; } - public static MenuGuiBase MenuUi { get; set; } + public static MenuGuiBase MenuUi { get; private set; } public static TexturedBase Skin { get; set; } @@ -71,15 +71,9 @@ public static void InitGwen() }; } - if (MenuUi != null) - { - MenuUi.Dispose(); - } + MenuUi?.Dispose(); - if (GameUi != null) - { - GameUi.Dispose(); - } + GameUi?.Dispose(); // Create a Canvas (it's root, on which all other GWEN controls are created) sMenuCanvas = new Canvas(Skin, "MainMenu") diff --git a/Intersect.Client/Interface/Menu/MainMenu.cs b/Intersect.Client/Interface/Menu/MainMenu.cs index e42d21f744..b90f0bc411 100644 --- a/Intersect.Client/Interface/Menu/MainMenu.cs +++ b/Intersect.Client/Interface/Menu/MainMenu.cs @@ -17,7 +17,7 @@ namespace Intersect.Client.Interface.Menu { - public class MainMenu + public class MainMenu : MutableInterface { public delegate void NetworkStatusHandler(); @@ -69,7 +69,7 @@ public class MainMenu private bool mShouldOpenCharacterSelection; //Init - public MainMenu(Canvas menuCanvas) + public MainMenu(Canvas menuCanvas) : base(menuCanvas) { mMenuCanvas = menuCanvas; diff --git a/Intersect.Client/Interface/Menu/MenuGuiBase.cs b/Intersect.Client/Interface/Menu/MenuGuiBase.cs index 3ab3542132..194064c4ab 100644 --- a/Intersect.Client/Interface/Menu/MenuGuiBase.cs +++ b/Intersect.Client/Interface/Menu/MenuGuiBase.cs @@ -1,4 +1,6 @@ -using Intersect.Client.Core; +using System.Collections.Generic; + +using Intersect.Client.Core; using Intersect.Client.Framework.File_Management; using Intersect.Client.Framework.Gwen.Control; using Intersect.Client.Localization; @@ -9,7 +11,7 @@ namespace Intersect.Client.Interface.Menu { - public class MenuGuiBase + public class MenuGuiBase : IMutableInterface { private static MainMenu.NetworkStatusHandler sNetworkStatusChanged; @@ -20,14 +22,14 @@ public class MenuGuiBase [NotNull] private readonly Label mServerStatusLabel; - public MainMenu MainMenu; + [NotNull] public MainMenu MainMenu { get; } private bool mShouldReset; public MenuGuiBase(Canvas myCanvas) { mMenuCanvas = myCanvas; - InitMenuGui(); + MainMenu = new MainMenu(mMenuCanvas); mServerStatusArea = new ImagePanel(mMenuCanvas, "ServerStatusArea"); mServerStatusLabel = new Label(mServerStatusArea, "ServerStatusLabel") { @@ -44,11 +46,6 @@ public MenuGuiBase(Canvas myCanvas) MainMenu.NetworkStatusChanged -= HandleNetworkStatusChanged; } - private void InitMenuGui() - { - MainMenu = new MainMenu(mMenuCanvas); - } - private void HandleNetworkStatusChanged() { mServerStatusLabel.Text = @@ -75,12 +72,28 @@ public void Reset() //Dispose public void Dispose() { - if (mMenuCanvas != null) - { - mMenuCanvas.Dispose(); - } + mMenuCanvas?.Dispose(); } + /// + public List Children => MainMenu.Children; + + /// + public TElement Create(params object[] parameters) where TElement : Base => + MainMenu.Create(parameters); + + /// + public TElement Find(string name = null, bool recurse = false) where TElement : Base => + MainMenu.Find(name, recurse); + + /// + public IEnumerable FindAll(bool recurse = false) where TElement : Base => + MainMenu.FindAll(recurse); + + /// + public void Remove(TElement element, bool dispose = false) where TElement : Base => + MainMenu.Remove(element, dispose); + } } diff --git a/Intersect.Client/Interface/Shared/OptionsWindow.cs b/Intersect.Client/Interface/Shared/OptionsWindow.cs index 2bfd5ba177..0d0bb3aac3 100644 --- a/Intersect.Client/Interface/Shared/OptionsWindow.cs +++ b/Intersect.Client/Interface/Shared/OptionsWindow.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using Intersect.Client.Core; diff --git a/Intersect.Client/Intersect.Client.csproj b/Intersect.Client/Intersect.Client.csproj index ae026f2e52..4864fcbc1f 100644 --- a/Intersect.Client/Intersect.Client.csproj +++ b/Intersect.Client/Intersect.Client.csproj @@ -107,7 +107,7 @@ - + diff --git a/Intersect.Client/Localization/Strings.cs b/Intersect.Client/Localization/Strings.cs index b556e8027b..76f560785e 100644 --- a/Intersect.Client/Localization/Strings.cs +++ b/Intersect.Client/Localization/Strings.cs @@ -1147,6 +1147,8 @@ public struct Options public static LocalizedString resolution = @"Resolution:"; + public static LocalizedString ResolutionCustom = @"Custom Resolution"; + public static LocalizedString restore = @"Restore Defaults"; public static LocalizedString soundvolume = @"Sound Volume: {00}%"; diff --git a/Intersect.Client/MonoGame/Graphics/MonoTexture.cs b/Intersect.Client/MonoGame/Graphics/MonoTexture.cs index a534b62c62..9efe187cf8 100644 --- a/Intersect.Client/MonoGame/Graphics/MonoTexture.cs +++ b/Intersect.Client/MonoGame/Graphics/MonoTexture.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using Intersect.Client.Framework.Graphics; @@ -8,15 +8,15 @@ using Intersect.IO.Files; using Intersect.Logging; +using JetBrains.Annotations; + using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; namespace Intersect.Client.MonoGame.Graphics { - public class MonoTexture : GameTexture { - private GraphicsDevice mGraphicsDevice; private int mHeight = -1; @@ -52,6 +52,19 @@ public MonoTexture(GraphicsDevice graphicsDevice, string filename, GameTexturePa mHeight = packFrame.SourceRect.Height; } + private void Load([NotNull] Stream stream) + { + mTexture = Texture2D.FromStream(mGraphicsDevice, stream); + if (mTexture == null) + { + throw new InvalidDataException("Failed to load texture, received no data."); + } + + mWidth = mTexture.Width; + mHeight = mTexture.Height; + mLoadError = false; + } + public void LoadTexture() { if (mTexture != null) @@ -66,7 +79,6 @@ public void LoadTexture() return; } - mLoadError = true; if (string.IsNullOrWhiteSpace(mPath)) { @@ -88,22 +100,7 @@ public void LoadTexture() { try { - mTexture = Texture2D.FromStream(mGraphicsDevice, fileStream); - if (mTexture == null) - { - Log.Error($"Failed to load texture due to unknown error: {relativePath}"); - ChatboxMsg.AddMessage( - new ChatboxMsg( - Strings.Errors.LoadFile.ToString(Strings.Words.lcase_sprite) + " [" + mName + "]", - new Color(0xBF, 0x0, 0x0) - ) - ); - return; - } - - mWidth = mTexture.Width; - mHeight = mTexture.Height; - mLoadError = false; + Load(fileStream); } catch (Exception exception) { @@ -111,6 +108,7 @@ public void LoadTexture() exception, $"Failed to load texture ({FileSystemHelper.FormatSize(fileStream.Length)}): {relativePath}" ); + ChatboxMsg.AddMessage( new ChatboxMsg( Strings.Errors.LoadFile.ToString(Strings.Words.lcase_sprite) + " [" + mName + "]", @@ -247,7 +245,5 @@ public void Update() mTexture.Dispose(); mTexture = null; } - } - } diff --git a/Intersect.Client/MonoGame/IntersectGame.cs b/Intersect.Client/MonoGame/IntersectGame.cs index bb46d1167b..94c8cf4e8e 100644 --- a/Intersect.Client/MonoGame/IntersectGame.cs +++ b/Intersect.Client/MonoGame/IntersectGame.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; using System.IO; using System.Linq; @@ -35,18 +35,27 @@ namespace Intersect.Client.MonoGame public class IntersectGame : Game { private bool mInitialized; + private double mLastUpdateTime = 0; private GraphicsDeviceManager mGraphics; #region "Autoupdate Variables" + private Updater.Updater mUpdater; + private Texture2D updaterBackground; + private SpriteFont updaterFont; + private SpriteFont updaterFontSmall; + private Texture2D updaterProgressBar; + private SpriteBatch updateBatch; + private bool updaterGraphicsReset; + #endregion public IntersectGame() @@ -116,7 +125,7 @@ public static void CurrentDomain_UnhandledException(object sender, UnhandledExce protected override void Initialize() { base.Initialize(); - + if (mUpdater != null) { LoadUpdaterContent(); @@ -131,11 +140,17 @@ private void IntersectInit() // TODO: Remove old netcode Networking.Network.Socket = new MonoSocket(); - Networking.Network.Socket.Connected += (sender, connectionEventArgs) => MainMenu.SetNetworkStatus(connectionEventArgs.NetworkStatus); - Networking.Network.Socket.ConnectionFailed += (sender, connectionEventArgs, denied) => MainMenu.SetNetworkStatus(connectionEventArgs.NetworkStatus); - Networking.Network.Socket.Disconnected += (sender, connectionEventArgs) => MainMenu.SetNetworkStatus(connectionEventArgs.NetworkStatus); + Networking.Network.Socket.Connected += (sender, connectionEventArgs) => + MainMenu.SetNetworkStatus(connectionEventArgs.NetworkStatus); + + Networking.Network.Socket.ConnectionFailed += (sender, connectionEventArgs, denied) => + MainMenu.SetNetworkStatus(connectionEventArgs.NetworkStatus); + + Networking.Network.Socket.Disconnected += (sender, connectionEventArgs) => + MainMenu.SetNetworkStatus(connectionEventArgs.NetworkStatus); Main.Start(); + mInitialized = true; } @@ -177,11 +192,11 @@ protected override void Update(GameTime gameTime) ? string.Join(" ", Environment.GetCommandLineArgs().Skip(1)) : null ); + Exit(); break; } } - } if (mUpdater == null) @@ -190,6 +205,7 @@ protected override void Update(GameTime gameTime) { IntersectInit(); } + if (Globals.IsRunning) { if (mLastUpdateTime < gameTime.TotalGameTime.TotalMilliseconds) @@ -198,6 +214,7 @@ protected override void Update(GameTime gameTime) { Main.Update(); } + ///mLastUpdateTime = gameTime.TotalGameTime.TotalMilliseconds + (1000/60f); } } @@ -270,10 +287,14 @@ protected override void OnExiting(object sender, EventArgs args) var exception = false; try { - var platform = GetType().GetField("Platform", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(this); - var field = platform.GetType().GetField("_isExiting", BindingFlags.NonPublic | BindingFlags.Instance); - field.SetValue(platform, 0); + var platform = GetType() + .GetField("Platform", BindingFlags.NonPublic | BindingFlags.Instance) + .GetValue(this); + + var field = platform.GetType() + .GetField("_isExiting", BindingFlags.NonPublic | BindingFlags.Instance); + field.SetValue(platform, 0); } catch { @@ -285,8 +306,8 @@ protected override void OnExiting(object sender, EventArgs args) { //Show Message Getting Exit Confirmation From Player to Leave in Combat var box = new InputBox( - Strings.Combat.warningtitle, Strings.Combat.warningcharacterselect, true, InputBox.InputType.YesNo, - ExitToDesktop, null, null + Strings.Combat.warningtitle, Strings.Combat.warningcharacterselect, true, + InputBox.InputType.YesNo, ExitToDesktop, null, null ); //Restart the MonoGame RunLoop @@ -299,14 +320,16 @@ protected override void OnExiting(object sender, EventArgs args) { mUpdater?.Stop(); } - catch { } + catch + { + } + //Just close if we don't need to show a combat warning base.OnExiting(sender, args); Networking.Network.Close("quitting"); - base.Dispose(); + Dispose(); } - private void DrawUpdater() { //Draw updating text and show progress bar... @@ -339,31 +362,37 @@ private void DrawUpdater() case UpdateStatus.Checking: status = Strings.Update.Checking; break; + case UpdateStatus.Updating: status = Strings.Update.Updating; progressPercent = mUpdater.Progress / 100f; - progress = Strings.Update.Percent.ToString((int)mUpdater.Progress); + progress = Strings.Update.Percent.ToString((int) mUpdater.Progress); filesRemaining = mUpdater.FilesRemaining + " Files Remaining"; sizeRemaining = mUpdater.GetHumanReadableFileSize(mUpdater.SizeRemaining) + " Left"; break; + case UpdateStatus.Restart: status = Strings.Update.Restart.ToString(Strings.Main.gamename); progressPercent = 100; progress = Strings.Update.Percent.ToString(100); break; + case UpdateStatus.Done: status = Strings.Update.Done; progressPercent = 100; progress = Strings.Update.Percent.ToString(100); break; + case UpdateStatus.Error: status = Strings.Update.Error; progress = mUpdater.Exception?.Message ?? ""; progressPercent = 100; break; + case UpdateStatus.None: //Nothing here! break; + default: throw new ArgumentOutOfRangeException(); } @@ -372,8 +401,7 @@ private void DrawUpdater() { var size = updaterFont.MeasureString(status); updateBatch.DrawString( - updaterFont, status, new Vector2(800 / 2 - size.X / 2, 360), - Microsoft.Xna.Framework.Color.White + updaterFont, status, new Vector2(800 / 2 - size.X / 2, 360), Microsoft.Xna.Framework.Color.White ); } @@ -381,9 +409,12 @@ private void DrawUpdater() if (updaterProgressBar != null) { updateBatch.Draw( - updaterProgressBar, new Rectangle(100, 400, (int)(600 * progressPercent), 32), - new Rectangle?(new Rectangle(0, 0, (int)(updaterProgressBar.Width * progressPercent), updaterProgressBar.Height)), - Microsoft.Xna.Framework.Color.White + updaterProgressBar, new Rectangle(100, 400, (int) (600 * progressPercent), 32), + new Rectangle?( + new Rectangle( + 0, 0, (int) (updaterProgressBar.Width * progressPercent), updaterProgressBar.Height + ) + ), Microsoft.Xna.Framework.Color.White ); } @@ -399,22 +430,19 @@ private void DrawUpdater() //Draw files remaining on bottom left updateBatch.DrawString( - updaterFontSmall, filesRemaining, new Vector2(100, 440), - Microsoft.Xna.Framework.Color.White + updaterFontSmall, filesRemaining, new Vector2(100, 440), Microsoft.Xna.Framework.Color.White ); //Draw total remaining on bottom right size = updaterFontSmall.MeasureString(sizeRemaining); updateBatch.DrawString( - updaterFontSmall, sizeRemaining, new Vector2(700 - size.X, 440), - Microsoft.Xna.Framework.Color.White + updaterFontSmall, sizeRemaining, new Vector2(700 - size.X, 440), Microsoft.Xna.Framework.Color.White ); } updateBatch.End(); } - private void LoadUpdaterContent() { if (File.Exists(Path.Combine("resources", "updater", "background.png"))) @@ -441,6 +469,15 @@ private void LoadUpdaterContent() updaterFontSmall = Content.Load(Path.Combine("resources", "updater", "fontsmall")); } } - } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (!disposing) + { + return; + } + } } diff --git a/Intersect.Server/Core/CommandParsing/Arguments/ArgumentValues.cs b/Intersect.Server/Core/CommandParsing/Arguments/ArgumentValues.cs index 9f07a787be..5b44c8a962 100644 --- a/Intersect.Server/Core/CommandParsing/Arguments/ArgumentValues.cs +++ b/Intersect.Server/Core/CommandParsing/Arguments/ArgumentValues.cs @@ -32,7 +32,7 @@ public sealed class ArgumentValues : IEnumerable ) { ArgumentName = argumentName; - mValues = new List(values ?? new object[0]); + mValues = new List(values ?? Array.Empty()); IsImplicit = isImplicit; } diff --git a/Intersect.Server/Core/CommandParsing/Arguments/EnumArgument.cs b/Intersect.Server/Core/CommandParsing/Arguments/EnumArgument.cs index 6d8447d633..28533ecb9d 100644 --- a/Intersect.Server/Core/CommandParsing/Arguments/EnumArgument.cs +++ b/Intersect.Server/Core/CommandParsing/Arguments/EnumArgument.cs @@ -1,15 +1,14 @@ -using System.Collections.Immutable; - -using Intersect.Localization; +using Intersect.Localization; using JetBrains.Annotations; +using System; +using System.Collections.Immutable; + namespace Intersect.Server.Core.CommandParsing.Arguments { - public class EnumArgument : CommandArgument { - public EnumArgument( [NotNull] LocaleArgument localization, bool required = false, @@ -17,7 +16,7 @@ public class EnumArgument : CommandArgument [CanBeNull] params TValue[] allowedValues ) : base(localization, required, positional) { - AllowedValues = (allowedValues ?? new TValue[0]).ToImmutableArray(); + AllowedValues = (allowedValues ?? Array.Empty()).ToImmutableArray(); } public EnumArgument( @@ -28,7 +27,7 @@ public class EnumArgument : CommandArgument [CanBeNull] params TValue[] allowedValues ) : base(localization, required, positional, allowsMultiple) { - AllowedValues = (allowedValues ?? new TValue[0]).ToImmutableArray(); + AllowedValues = (allowedValues ?? Array.Empty()).ToImmutableArray(); } public EnumArgument( @@ -40,7 +39,7 @@ public class EnumArgument : CommandArgument [CanBeNull] params TValue[] allowedValues ) : base(localization, required, positional, allowsMultiple, defaultValue) { - AllowedValues = (allowedValues ?? new TValue[0]).ToImmutableArray(); + AllowedValues = (allowedValues ?? Array.Empty()).ToImmutableArray(); } public EnumArgument( @@ -50,7 +49,7 @@ public class EnumArgument : CommandArgument [CanBeNull] params TValue[] allowedValues ) : base(localization, requiredPredicate, positional) { - AllowedValues = (allowedValues ?? new TValue[0]).ToImmutableArray(); + AllowedValues = (allowedValues ?? Array.Empty()).ToImmutableArray(); } public EnumArgument( @@ -61,7 +60,7 @@ public class EnumArgument : CommandArgument [CanBeNull] params TValue[] allowedValues ) : base(localization, requiredPredicate, positional, allowsMultiple) { - AllowedValues = (allowedValues ?? new TValue[0]).ToImmutableArray(); + AllowedValues = (allowedValues ?? Array.Empty()).ToImmutableArray(); } public EnumArgument( @@ -73,16 +72,12 @@ public class EnumArgument : CommandArgument [CanBeNull] params TValue[] allowedValues ) : base(localization, requiredPredicate, positional, allowsMultiple, defaultValue) { - AllowedValues = (allowedValues ?? new TValue[0]).ToImmutableArray(); + AllowedValues = (allowedValues ?? Array.Empty()).ToImmutableArray(); } public ImmutableArray AllowedValues { get; } - public override bool IsValueAllowed(object value) - { - return value is TValue castedValue && AllowedValues.Contains(castedValue); - } - + public override bool IsValueAllowed(object value) => + value is TValue castedValue && AllowedValues.Contains(castedValue); } - } diff --git a/Intersect.Server/Core/CommandParsing/CommandParser.cs b/Intersect.Server/Core/CommandParsing/CommandParser.cs index d9f5d4aaf0..cc3a576a21 100644 --- a/Intersect.Server/Core/CommandParsing/CommandParser.cs +++ b/Intersect.Server/Core/CommandParsing/CommandParser.cs @@ -58,7 +58,7 @@ public CommandParser([NotNull] ParserSettings settings) ); } - if (defaultConstructor.Invoke(args ?? new object[0]) is ICommand command) + if (defaultConstructor.Invoke(args ?? Array.Empty()) is ICommand command) { return Register(command); } diff --git a/Intersect.Server/Core/CommandParsing/Commands/Command.cs b/Intersect.Server/Core/CommandParsing/Commands/Command.cs index 0286b9e9dd..f8416bfff8 100644 --- a/Intersect.Server/Core/CommandParsing/Commands/Command.cs +++ b/Intersect.Server/Core/CommandParsing/Commands/Command.cs @@ -21,7 +21,7 @@ protected Command([NotNull] LocaleCommand localization, [CanBeNull] params IComm Localization = localization; var argumentList = new List( - (arguments ?? new ICommandArgument[0]).Where(argument => argument != null) + (arguments ?? Array.Empty()).Where(argument => argument != null) ); UnsortedArguments = argumentList.ToImmutableList() ?? throw new InvalidOperationException(); diff --git a/Intersect.Server/Core/CommandParsing/ParserResult.cs b/Intersect.Server/Core/CommandParsing/ParserResult.cs index a670d61034..cc20e15e07 100644 --- a/Intersect.Server/Core/CommandParsing/ParserResult.cs +++ b/Intersect.Server/Core/CommandParsing/ParserResult.cs @@ -34,9 +34,9 @@ public class ParserResult where TCommand : ICommand { Command = command; Parsed = parsed; - Errors = (errors ?? new ParserError[0]).ToImmutableList() ?? throw new InvalidOperationException(); - Missing = (missing ?? new ICommandArgument[0]).ToImmutableList() ?? throw new InvalidOperationException(); - Omitted = (omitted ?? new ICommandArgument[0]).ToImmutableList() ?? throw new InvalidOperationException(); + Errors = (errors ?? Array.Empty()).ToImmutableList() ?? throw new InvalidOperationException(); + Missing = (missing ?? Array.Empty()).ToImmutableList() ?? throw new InvalidOperationException(); + Omitted = (omitted ?? Array.Empty()).ToImmutableList() ?? throw new InvalidOperationException(); Unhandled = Errors.Where(error => error is UnhandledArgumentError) .Cast() .ToImmutableList() ?? diff --git a/Intersect.Server/Database/DbInterface.cs b/Intersect.Server/Database/DbInterface.cs index 3c871ee985..2a0a08d438 100644 --- a/Intersect.Server/Database/DbInterface.cs +++ b/Intersect.Server/Database/DbInterface.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; @@ -101,7 +101,6 @@ public static void InitializeDbLoggers() new LogConfiguration { Tag = "GAMEDB", - Pretty = false, LogLevel = Options.GameDb.LogLevel, Outputs = ImmutableList.Create( new FileOutput(Log.SuggestFilename(null, "gamedb"), LogLevel.Debug) @@ -116,7 +115,6 @@ public static void InitializeDbLoggers() new LogConfiguration { Tag = "PLAYERDB", - Pretty = false, LogLevel = Options.PlayerDb.LogLevel, Outputs = ImmutableList.Create( new FileOutput(Log.SuggestFilename(null, "playerdb"), LogLevel.Debug) diff --git a/Intersect.Server/Entities/Entity.cs b/Intersect.Server/Entities/Entity.cs index e4912032d3..d61d0086d5 100644 --- a/Intersect.Server/Entities/Entity.cs +++ b/Intersect.Server/Entities/Entity.cs @@ -833,7 +833,7 @@ public virtual void Move(int moveDir, Player forPlayer, bool doNotUpdate = false var projectiles = map.MapProjectiles.ToArray(); foreach (var projectile in projectiles) { - var spawns = projectile.Spawns?.ToArray() ?? new ProjectileSpawn[0]; + var spawns = projectile.Spawns?.ToArray() ?? Array.Empty(); foreach (var spawn in spawns) { // TODO: Filter in Spawns variable, there should be no nulls. See #78 for evidence it is null. diff --git a/Intersect.Server/Entities/Trading.cs b/Intersect.Server/Entities/Trading.cs index 0cb4daa258..64852a37af 100644 --- a/Intersect.Server/Entities/Trading.cs +++ b/Intersect.Server/Entities/Trading.cs @@ -38,7 +38,7 @@ public Trading([NotNull] Player player) public void Dispose() { - Offer = new Item[0]; + Offer = Array.Empty(); Requester = null; Requests.Clear(); } diff --git a/Intersect.Server/Maps/MapInstance.cs b/Intersect.Server/Maps/MapInstance.cs index 5c3cdec5c4..7eb1154e0c 100644 --- a/Intersect.Server/Maps/MapInstance.cs +++ b/Intersect.Server/Maps/MapInstance.cs @@ -54,9 +54,9 @@ public class MapInstance : MapBase //Traps [JsonIgnore] [NotMapped] public List MapTraps = new List(); - [NotMapped] private BytePoint[] mMapBlocks = new BytePoint[0]; + [NotMapped] private BytePoint[] mMapBlocks = Array.Empty(); - private BytePoint[] mNpcMapBlocks = new BytePoint[0]; + private BytePoint[] mNpcMapBlocks = Array.Empty(); private ConcurrentDictionary mPlayers = new ConcurrentDictionary(); diff --git a/Intersect.Server/Networking/Lidgren/ServerNetwork.cs b/Intersect.Server/Networking/Lidgren/ServerNetwork.cs index 0bc3e7d696..790ace1347 100644 --- a/Intersect.Server/Networking/Lidgren/ServerNetwork.cs +++ b/Intersect.Server/Networking/Lidgren/ServerNetwork.cs @@ -15,7 +15,7 @@ namespace Intersect.Server.Networking.Lidgren { - + // TODO: Migrate to a proper service public class ServerNetwork : AbstractNetwork, IServer { diff --git a/Intersect.Server/Web/RestApi/Attributes/ConfigurableAuthorizeAttribute.cs b/Intersect.Server/Web/RestApi/Attributes/ConfigurableAuthorizeAttribute.cs index 7b110a988a..027632a4d7 100644 --- a/Intersect.Server/Web/RestApi/Attributes/ConfigurableAuthorizeAttribute.cs +++ b/Intersect.Server/Web/RestApi/Attributes/ConfigurableAuthorizeAttribute.cs @@ -21,7 +21,7 @@ internal class ConfigurableAuthorizeAttribute : AuthorizeAttribute [NotNull] protected IEnumerable InternalRoles => Roles?.Split(',').Where(role => !string.IsNullOrWhiteSpace(role)).Select(role => role.Trim()) ?? - new string[0]; + Array.Empty(); protected override bool IsAuthorized(HttpActionContext actionContext) { diff --git a/Intersect.Server/Web/RestApi/RestApi.cs b/Intersect.Server/Web/RestApi/RestApi.cs index ebee3165ef..fff372c1fa 100644 --- a/Intersect.Server/Web/RestApi/RestApi.cs +++ b/Intersect.Server/Web/RestApi/RestApi.cs @@ -26,14 +26,17 @@ namespace Intersect.Server.Web.RestApi { - + // TODO: Migrate to a proper service internal sealed class RestApi : IAppConfigurationProvider, IConfigurable, IDisposable { + [NotNull] private readonly object mDisposeLock; [CanBeNull] private IDisposable mWebAppHandle; public RestApi(ushort apiPort) { + mDisposeLock = new object(); + StartOptions = new StartOptions(); Configuration = ApiConfiguration.Create(); @@ -111,7 +114,7 @@ public void Configure(IAppBuilder appBuilder) public void Dispose() { - lock (this) + lock (mDisposeLock) { if (Disposed || Disposing) { diff --git a/Intersect.Server/Web/RestApi/Routes/V1/DocController.cs b/Intersect.Server/Web/RestApi/Routes/V1/DocController.cs index 7d3852c0b5..1515cb293f 100644 --- a/Intersect.Server/Web/RestApi/Routes/V1/DocController.cs +++ b/Intersect.Server/Web/RestApi/Routes/V1/DocController.cs @@ -39,7 +39,7 @@ private IEnumerable Descriptions [HttpGet] public object Authorized(string path) { - var segments = path?.Trim().Split('/') ?? new string[0]; + var segments = path?.Trim().Split('/') ?? Array.Empty(); var pathSegments = new List(); var descriptions = Descriptions.OrderBy(description => description?.RelativePath) @@ -224,7 +224,7 @@ public static dynamic ToJson([NotNull] this ApiParameterDescription description) if (parameters) { json.parameters = description.ParameterDescriptions?.Select(parameter => parameter?.ToJson()) ?? - new object[0]; + Array.Empty(); } return json; From f1f47d314df10722539b5816cd212c38648e92cd Mon Sep 17 00:00:00 2001 From: Robert Lodico Date: Tue, 28 Jul 2020 00:40:44 -0400 Subject: [PATCH 21/45] Additional cleanup and documentation --- Intersect (Core)/Factories/FactoryRegistry.cs | 32 ++- Intersect (Core)/Intersect (Core).csproj | 26 +++ .../Contexts/PluginBootstrapContext.cs | 99 ++++++++ .../Plugins/Contexts/PluginContext.cs | 58 +++++ .../Plugins/Helpers/CommandLineHelper.cs | 59 +++++ .../Plugins/Helpers/ContextHelper.cs | 26 +++ .../Plugins/Helpers/EmbeddedResourceHelper.cs | 106 +++++++++ .../Plugins/Helpers/LoggingHelper.cs | 80 +++++++ Intersect (Core)/Plugins/IPluginContext`2.cs | 4 +- .../Plugins/Plugin.PluginReference.cs | 72 ++++++ Intersect (Core)/Plugins/Plugin.cs | 78 +++++++ .../Plugins/PluginConfiguration.cs | 16 ++ Intersect (Core)/Plugins/PluginEntry.cs | 17 ++ Intersect (Core)/Plugins/PluginEntry`1.cs | 34 +++ Intersect (Core)/Plugins/PluginHelper.cs | 27 +++ Intersect (Core)/Plugins/PluginInstance.cs | 57 +++++ Intersect (Core)/Plugins/PluginService.cs | 211 ++++++++++++++++++ Intersect (Core)/Properties/AssemblyInfo.cs | 20 +- .../Properties/ExceptionMessages.Designer.cs | 90 ++++++++ .../Properties/ExceptionMessages.resx | 129 +++++++++++ Intersect (Core)/Reflection/TypeExtensions.cs | 8 +- Intersect (Core)/packages.config | 3 +- Intersect.Building/Intersect.Building.csproj | 3 + Intersect.Building/Properties/AssemblyInfo.cs | 14 +- Intersect.Building/packages.config | 5 +- .../Gwen/Control/Base.cs | 2 + .../Intersect.Client.Framework.csproj | 3 + .../Properties/AssemblyInfo.cs | 12 +- Intersect.Client.Framework/packages.config | 1 + .../Plugins/Helpers/ClientLifecycleHelper.cs | 37 +++ Intersect.Client/Properties/AssemblyInfo.cs | 14 +- Intersect.Client/packages.config | 1 + Intersect.Editor/Intersect.Editor.csproj | 5 +- Intersect.Editor/Properties/AssemblyInfo.cs | 13 +- Intersect.Editor/packages.config | 1 + Intersect.Network/Intersect.Network.csproj | 5 +- Intersect.Network/Properties/AssemblyInfo.cs | 13 +- Intersect.Network/packages.config | 1 + Intersect.Server/Intersect.Server.csproj | 18 +- .../Plugins/Helpers/ServerLifecycleHelper.cs | 15 ++ Intersect.Server/Properties/AssemblyInfo.cs | 16 +- Intersect.Server/packages.config | 1 + .../Intersect.Tests.Client.Framework.csproj | 5 +- .../Properties/AssemblyInfo.cs | 16 +- .../packages.config | 1 + .../Intersect.Tests.Client.csproj | 5 +- .../Properties/AssemblyInfo.cs | 16 +- Intersect.Tests.Client/packages.config | 1 + .../Intersect.Tests.Editor.csproj | 30 +-- .../Properties/AssemblyInfo.cs | 16 +- Intersect.Tests.Editor/packages.config | 1 + .../Intersect.Tests.Network.csproj | 5 +- .../Properties/AssemblyInfo.cs | 16 +- Intersect.Tests.Network/packages.config | 1 + .../Intersect.Tests.Server.csproj | 5 +- .../Properties/AssemblyInfo.cs | 16 +- Intersect.Tests.Server/packages.config | 1 + Intersect.Tests/Intersect.Tests.csproj | 5 +- Intersect.Tests/Properties/AssemblyInfo.cs | 16 +- Intersect.Tests/packages.config | 1 + .../Intersect.Utilities.csproj | 3 + Intersect.Utilities/packages.config | 2 +- 62 files changed, 1454 insertions(+), 140 deletions(-) create mode 100644 Intersect (Core)/Plugins/Contexts/PluginBootstrapContext.cs create mode 100644 Intersect (Core)/Plugins/Contexts/PluginContext.cs create mode 100644 Intersect (Core)/Plugins/Helpers/CommandLineHelper.cs create mode 100644 Intersect (Core)/Plugins/Helpers/ContextHelper.cs create mode 100644 Intersect (Core)/Plugins/Helpers/EmbeddedResourceHelper.cs create mode 100644 Intersect (Core)/Plugins/Helpers/LoggingHelper.cs create mode 100644 Intersect (Core)/Plugins/Plugin.PluginReference.cs create mode 100644 Intersect (Core)/Plugins/Plugin.cs create mode 100644 Intersect (Core)/Plugins/PluginConfiguration.cs create mode 100644 Intersect (Core)/Plugins/PluginEntry.cs create mode 100644 Intersect (Core)/Plugins/PluginEntry`1.cs create mode 100644 Intersect (Core)/Plugins/PluginHelper.cs create mode 100644 Intersect (Core)/Plugins/PluginInstance.cs create mode 100644 Intersect (Core)/Plugins/PluginService.cs create mode 100644 Intersect (Core)/Properties/ExceptionMessages.Designer.cs create mode 100644 Intersect (Core)/Properties/ExceptionMessages.resx create mode 100644 Intersect.Client/Plugins/Helpers/ClientLifecycleHelper.cs create mode 100644 Intersect.Server/Plugins/Helpers/ServerLifecycleHelper.cs diff --git a/Intersect (Core)/Factories/FactoryRegistry.cs b/Intersect (Core)/Factories/FactoryRegistry.cs index 87a632a63a..7a642d84d8 100644 --- a/Intersect (Core)/Factories/FactoryRegistry.cs +++ b/Intersect (Core)/Factories/FactoryRegistry.cs @@ -1,4 +1,10 @@ using System; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; + +using Intersect.Logging; +using Intersect.Properties; +using Intersect.Reflection; using JetBrains.Annotations; @@ -8,6 +14,10 @@ namespace Intersect.Factories /// Utility class that stores instances of . /// /// the type of created instances + [SuppressMessage( + "Design", "CA1000:Do not declare static members on generic types", + Justification = "Static members on this type are actually desirable." + )] public static class FactoryRegistry { /// @@ -43,6 +53,10 @@ public static TValue Create([NotNull] params object[] args) /// if the instance was created /// [Pure] + [SuppressMessage( + "Design", "CA1031:Do not catch general exception types", + Justification = "This exception is intended to log but not throw." + )] public static bool TryCreate(out TValue value, [NotNull] params object[] args) { try @@ -50,11 +64,23 @@ public static bool TryCreate(out TValue value, [NotNull] params object[] args) value = Create(args); return true; } - catch + catch (InvalidOperationException exception) { - value = default; - return false; + Log.Warn(exception); } + catch (Exception exception) + { + Log.Error( + exception, + string.Format( + CultureInfo.CurrentCulture, ExceptionMessages.SwallowingExceptionFromWithQualifiedName, + typeof(FactoryRegistry).QualifiedGenericName(), nameof(Create) + ) + ); + } + + value = default; + return false; } /// diff --git a/Intersect (Core)/Intersect (Core).csproj b/Intersect (Core)/Intersect (Core).csproj index 3e9db0438b..550d93df44 100644 --- a/Intersect (Core)/Intersect (Core).csproj +++ b/Intersect (Core)/Intersect (Core).csproj @@ -119,6 +119,9 @@ ..\packages\Microsoft.Extensions.Primitives.3.1.0\lib\netstandard2.0\Microsoft.Extensions.Primitives.dll + + ..\packages\Microsoft.VisualStudio.Validation.15.5.31\lib\netstandard2.0\Microsoft.VisualStudio.Validation.dll + ..\packages\ncalc.1.3.8\lib\NCalc.dll @@ -493,6 +496,12 @@ + + + + + + @@ -510,11 +519,24 @@ + + + + + + + + True True DeveloperStrings.resx + + True + True + ExceptionMessages.resx + @@ -634,6 +656,10 @@ ResXFileCodeGenerator DeveloperStrings.Designer.cs + + ResXFileCodeGenerator + ExceptionMessages.Designer.cs + ResXFileCodeGenerator Resources.Designer.cs diff --git a/Intersect (Core)/Plugins/Contexts/PluginBootstrapContext.cs b/Intersect (Core)/Plugins/Contexts/PluginBootstrapContext.cs new file mode 100644 index 0000000000..d0dc9f9b7d --- /dev/null +++ b/Intersect (Core)/Plugins/Contexts/PluginBootstrapContext.cs @@ -0,0 +1,99 @@ +using CommandLine; + +using Intersect.Factories; +using Intersect.Plugins.Helpers; +using Intersect.Plugins.Interfaces; +using Intersect.Properties; + +using JetBrains.Annotations; + +using System; +using System.Globalization; +using System.Reflection; + +namespace Intersect.Plugins.Contexts +{ + /// + /// Common implementation. + /// + public sealed class PluginBootstrapContext : IPluginBootstrapContext + { + /// + /// Creates a for . + /// + /// the startup arguments that were parsed + /// the used to parse + /// a instance + public static IFactory + CreateFactory([NotNull] string[] args, [NotNull] Parser parser) => new Factory(args, parser); + + /// + /// Factory implementation for . + /// + private sealed class Factory : IFactory + { + [NotNull] private readonly string[] mArgs; + + [NotNull] private readonly Parser mParser; + + /// + /// Initializes a for . + /// + /// the startup arguments that were parsed + /// the used to parse + public Factory([NotNull] string[] args, [NotNull] Parser parser) + { + mArgs = args; + mParser = parser; + } + + /// + public IPluginBootstrapContext Create(params object[] args) + { + if (args.Length < 1 || !(args[0] is Plugin plugin)) + { + throw new ArgumentException( + string.Format( + CultureInfo.CurrentCulture, ExceptionMessages.PluginBootstrapContextMissingPluginArgument, + nameof(Plugin) + ), nameof(args) + ); + } + + return new PluginBootstrapContext(mArgs, mParser, plugin); + } + } + + [NotNull] private Plugin Plugin { get; } + + /// + public ICommandLineHelper CommandLine { get; } + + /// + public Assembly Assembly => Plugin.Reference.Assembly; + + /// + public PluginConfiguration Configuration => Plugin.Configuration; + + /// + public IEmbeddedResourceHelper EmbeddedResources { get; } + + /// + public ILoggingHelper Logging => Plugin.Logging; + + /// + public IManifestHelper Manifest => Plugin.Manifest; + + private PluginBootstrapContext([NotNull] string[] args, [NotNull] Parser parser, [NotNull] Plugin plugin) + { + Plugin = plugin; + + CommandLine = new CommandLineHelper(plugin.Logging.Plugin, args, parser); + EmbeddedResources = new EmbeddedResourceHelper(Assembly); + } + + /// + public TConfiguration GetTypedConfiguration() where TConfiguration : PluginConfiguration => + Configuration as TConfiguration; + } +} diff --git a/Intersect (Core)/Plugins/Contexts/PluginContext.cs b/Intersect (Core)/Plugins/Contexts/PluginContext.cs new file mode 100644 index 0000000000..80a5487ae3 --- /dev/null +++ b/Intersect (Core)/Plugins/Contexts/PluginContext.cs @@ -0,0 +1,58 @@ +using System.Reflection; + +using Intersect.Plugins.Helpers; +using Intersect.Plugins.Interfaces; + +using JetBrains.Annotations; + +namespace Intersect.Plugins.Contexts +{ + /// + /// Common implementation class for typed plugin contexts. + /// + /// extension type + /// extension type used by + public abstract class + PluginContext : IPluginContext + where TPluginContext : IPluginContext where TLifecycleHelper : ILifecycleHelper + { + + [NotNull] private Plugin Plugin { get; } + + /// + public Assembly Assembly => Plugin.Reference.Assembly; + + /// + public PluginConfiguration Configuration => Plugin.Configuration; + + /// + public IEmbeddedResourceHelper EmbeddedResources { get; } + + ILifecycleHelper IPluginContext.Lifecycle => Lifecycle; + + /// + public abstract TLifecycleHelper Lifecycle { get; } + + /// + public ILoggingHelper Logging => Plugin.Logging; + + /// + public IManifestHelper Manifest => Plugin.Manifest; + + /// + /// Instantiates a . + /// + /// the this context will be used for + protected PluginContext([NotNull] Plugin plugin) + { + Plugin = plugin; + EmbeddedResources = new EmbeddedResourceHelper(Assembly); + } + + /// + public TConfiguration GetTypedConfiguration() where TConfiguration : PluginConfiguration => + Configuration as TConfiguration; + + } + +} diff --git a/Intersect (Core)/Plugins/Helpers/CommandLineHelper.cs b/Intersect (Core)/Plugins/Helpers/CommandLineHelper.cs new file mode 100644 index 0000000000..ce7800d6e8 --- /dev/null +++ b/Intersect (Core)/Plugins/Helpers/CommandLineHelper.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +using CommandLine; + +using Intersect.Logging; +using Intersect.Plugins.Interfaces; + +using JetBrains.Annotations; + +namespace Intersect.Plugins.Helpers +{ + /// + internal sealed class CommandLineHelper : PluginHelper, ICommandLineHelper + { + [NotNull] private readonly Parser mParser; + + [NotNull] private readonly string[] mArgs; + + internal CommandLineHelper([NotNull] Logger logger, [NotNull] string[] args, [NotNull] Parser parser) : base( + logger + ) + { + mArgs = args; + mParser = parser; + } + + /// + public TArguments ParseArguments() => + mParser.ParseArguments(mArgs).MapResult(arguments => arguments, HandleErrors); + + private TArguments HandleErrors(IEnumerable errors) + { + var errorsAsList = errors?.ToList(); + + var fatalParsingError = errorsAsList?.Any(error => error?.StopsProcessing ?? false) ?? false; + + var errorString = string.Join( + ", ", errorsAsList?.ToList().Select(error => error?.ToString()) ?? Array.Empty() + ); + + var exception = new ArgumentException( + $@"Error parsing plugin arguments of type {typeof(TArguments).FullName}, received the following errors: {errorString}" + ); + + if (fatalParsingError) + { + Logger.Error(exception); + } + else + { + Logger.Warn(exception); + } + + return default; + } + } +} diff --git a/Intersect (Core)/Plugins/Helpers/ContextHelper.cs b/Intersect (Core)/Plugins/Helpers/ContextHelper.cs new file mode 100644 index 0000000000..f729db02a7 --- /dev/null +++ b/Intersect (Core)/Plugins/Helpers/ContextHelper.cs @@ -0,0 +1,26 @@ +using JetBrains.Annotations; + +namespace Intersect.Plugins.Helpers +{ + /// + /// Partial implementation class for helpers that require a known . + /// + /// the type of needed + public abstract class ContextHelper where TContext : IPluginContext + { + /// + /// Reference to the current . + /// + [NotNull] + protected TContext Context { get; } + + /// + /// Partially instantiates a . + /// + /// the required + protected ContextHelper([NotNull] TContext context) + { + Context = context; + } + } +} diff --git a/Intersect (Core)/Plugins/Helpers/EmbeddedResourceHelper.cs b/Intersect (Core)/Plugins/Helpers/EmbeddedResourceHelper.cs new file mode 100644 index 0000000000..d18955cadd --- /dev/null +++ b/Intersect (Core)/Plugins/Helpers/EmbeddedResourceHelper.cs @@ -0,0 +1,106 @@ +using Intersect.Plugins.Interfaces; + +using JetBrains.Annotations; + +using System; +using System.IO; +using System.Linq; +using System.Reflection; + +namespace Intersect.Plugins.Helpers +{ + /// + internal sealed class EmbeddedResourceHelper : IEmbeddedResourceHelper + { + [NotNull] private Assembly Assembly { get; } + + internal EmbeddedResourceHelper([NotNull] Assembly assembly) + { + Assembly = assembly; + } + + /// + public bool Exists(string resourceName) => Assembly.GetManifestResourceInfo(Resolve(resourceName)) != null; + + /// + public ManifestResourceInfo GetInfo(string resourceName) => + Assembly.GetManifestResourceInfo(Resolve(resourceName)) ?? + throw new InvalidOperationException("Resource exists but info is null."); + + /// + public Stream Read(string resourceName) => + Assembly.GetManifestResourceStream(Resolve(resourceName)) ?? + throw new InvalidOperationException("Resource exists but stream is null."); + + /// + public string Resolve(string resourceName) + { + if (string.IsNullOrWhiteSpace(resourceName)) + { + throw new ArgumentNullException( + nameof(resourceName), $@"{nameof(resourceName)} cannot be null, empty, or whitespace." + ); + } + + var partialManifestResourceName = resourceName.Replace("/", "."); + var manifestResourceName = Assembly.GetManifestResourceNames() + .Where( + potentialManifestResourceName => potentialManifestResourceName.EndsWith(partialManifestResourceName) + ) + .OrderBy(potentialManifestResourceName => potentialManifestResourceName.Length) + .FirstOrDefault(); + + if (manifestResourceName == default) + { + throw new FileNotFoundException($@"Unable to find resource: {resourceName}"); + } + + return manifestResourceName; + } + + /// + public bool TryGetInfo(string resourceName, out ManifestResourceInfo resourceInfo) + { + try + { + resourceInfo = GetInfo(resourceName); + return true; + } + catch + { + resourceInfo = default; + return false; + } + } + + /// + public bool TryRead(string resourceName, out Stream stream) + { + try + { + stream = Read(resourceName); + return true; + } + catch + { + stream = default; + return false; + } + } + + /// + public bool TryResolve(string resourceName, out string manifestResourceName) + { + try + { + manifestResourceName = Resolve(resourceName); + return true; + } + catch + { + manifestResourceName = default; + return false; + } + } + } +} diff --git a/Intersect (Core)/Plugins/Helpers/LoggingHelper.cs b/Intersect (Core)/Plugins/Helpers/LoggingHelper.cs new file mode 100644 index 0000000000..8982196dd6 --- /dev/null +++ b/Intersect (Core)/Plugins/Helpers/LoggingHelper.cs @@ -0,0 +1,80 @@ +using Intersect.Logging; +using Intersect.Logging.Output; +using Intersect.Plugins.Interfaces; + +using JetBrains.Annotations; + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.IO; + +namespace Intersect.Plugins.Helpers +{ + /// + internal sealed class LoggingHelper : ILoggingHelper + { + [NotNull] + private static readonly string BasePluginLogPath = Path.Combine( + "plugins", $"{Log.Initial:yyyy_MM_dd-HH_mm_ss_fff}" + ); + + [NotNull] + private static Logger CreateLogger([NotNull] IManifestHelper manifest, CreateLoggerOptions createLoggerOptions) + { + var logName = string.IsNullOrWhiteSpace(createLoggerOptions.Name) + ? manifest.Key + : $"{manifest.Key}.{createLoggerOptions.Name}"; + + var outputs = new List(); + + if (createLoggerOptions.File > LogLevel.None) + { + outputs.Add( + new FileOutput(Path.Combine(BasePluginLogPath, $"{logName}.log"), createLoggerOptions.File) + ); + } + + if (createLoggerOptions.Console > LogLevel.None) + { + outputs.Add(new ConciseConsoleOutput(createLoggerOptions.Console)); + } + + var immutableOutputs = outputs.ToImmutableList(); + Debug.Assert(immutableOutputs != null, $"{nameof(immutableOutputs)} != null"); + + return new Logger( + new LogConfiguration + { + LogLevel = LogConfiguration.Default.LogLevel, + Outputs = immutableOutputs + } + ); + } + + [NotNull] private readonly IManifestHelper mManifest; + + /// + public Logger Application { get; } + + public Logger Plugin { get; } + + internal LoggingHelper([NotNull] Logger applicationLogger, [NotNull] IManifestHelper manifest) + { + mManifest = manifest; + + Application = applicationLogger; + Plugin = CreateLogger( + manifest, new CreateLoggerOptions + { + Console = Debugger.IsAttached ? LogLevel.Debug : LogLevel.None, + File = LogLevel.Info + } + ); + } + + /// + public Logger CreateLogger(CreateLoggerOptions createLoggerOptions) => + CreateLogger(mManifest, createLoggerOptions); + } +} diff --git a/Intersect (Core)/Plugins/IPluginContext`2.cs b/Intersect (Core)/Plugins/IPluginContext`2.cs index 789c84cc2a..17f5b4361e 100644 --- a/Intersect (Core)/Plugins/IPluginContext`2.cs +++ b/Intersect (Core)/Plugins/IPluginContext`2.cs @@ -8,8 +8,8 @@ namespace Intersect.Plugins /// Defines the API of the with a /// specialized during application runtime. /// - /// a subtype - /// a subtype + /// extension type + /// extension type used by public interface IPluginContext : IPluginContext where TPluginContext : IPluginContext where TLifecycleHelper : ILifecycleHelper { diff --git a/Intersect (Core)/Plugins/Plugin.PluginReference.cs b/Intersect (Core)/Plugins/Plugin.PluginReference.cs new file mode 100644 index 0000000000..ca7d01b796 --- /dev/null +++ b/Intersect (Core)/Plugins/Plugin.PluginReference.cs @@ -0,0 +1,72 @@ +using JetBrains.Annotations; + +using System; +using System.IO; +using System.Reflection; + +namespace Intersect.Plugins +{ + /// + /// Reference container type of type, method handles, and other information gathered via reflection. + /// + internal sealed class PluginReference + { + /// + /// Initializes a . + /// + /// The this plugin belongs to. + /// The plugin configuration , should descend from or be . + /// The of the entry point for the plugin, should descend from . + internal PluginReference( + [NotNull] Assembly assembly, + [NotNull] Type configurationType, + [NotNull] Type entryType + ) + { + Assembly = assembly; + ConfigurationType = configurationType; + EntryType = entryType; + } + + /// + /// The this plugin belongs to. + /// + [NotNull] + internal Assembly Assembly { get; } + + /// + /// The plugin configuration , should descend from or be . + /// + [NotNull] + internal Type ConfigurationType { get; } + + /// + /// The of the entry point for the plugin, should descend from . + /// + [NotNull] + internal Type EntryType { get; } + + /// + /// The path to the configuration file for this plugin. + /// + [NotNull] + internal string ConfigurationFile => Path.Combine(Directory, "config.json"); + + /// + /// The path to the directory this plugin is located in. + /// + [NotNull] + internal string Directory => Path.GetDirectoryName(Assembly.Location) ?? + throw new InvalidOperationException( + $"Error getting plugin directory for assembly {Assembly.FullName} ({Assembly.Location})." + ); + + /// + /// Create an instance of the plugin entry type. + /// + /// an instance of specific to this plugin + [NotNull] + internal IPluginEntry CreateInstance() => + Activator.CreateInstance(EntryType) as IPluginEntry ?? throw new InvalidOperationException(); + } +} diff --git a/Intersect (Core)/Plugins/Plugin.cs b/Intersect (Core)/Plugins/Plugin.cs new file mode 100644 index 0000000000..2ddf680ab8 --- /dev/null +++ b/Intersect (Core)/Plugins/Plugin.cs @@ -0,0 +1,78 @@ +using Intersect.Core; +using Intersect.Plugins.Helpers; +using Intersect.Plugins.Interfaces; + +using JetBrains.Annotations; + +namespace Intersect.Plugins +{ + /// + /// Representation of a loaded plugin descriptor. + /// + public sealed class Plugin + { + /// + /// Create a instance for the given context, manifest and reference. + /// + /// the the plugin is running in + /// the that describes this plugin + /// the with pre-searched reflection information + /// + internal static Plugin Create( + [NotNull] IApplicationContext applicationContext, + [NotNull] IManifestHelper manifest, + [NotNull] PluginReference reference + ) => new Plugin(manifest, new LoggingHelper(applicationContext.Logger, manifest), reference); + + // ReSharper disable once NotNullMemberIsNotInitialized + // Plugin instance is created at the Discovery phase and Configuration is loaded afterwards + private Plugin( + [NotNull] IManifestHelper manifest, + [NotNull] ILoggingHelper logging, + [NotNull] PluginReference reference + ) + { + Manifest = manifest; + Logging = logging; + Reference = reference; + } + + /// + /// The that describes this . + /// + [NotNull] + public IManifestHelper Manifest { get; } + + /// + /// The for this . + /// + [NotNull] + public ILoggingHelper Logging { get; } + + /// + /// The for this . + /// + [NotNull] + public PluginConfiguration Configuration { get; internal set; } + + /// + /// The to reflection information for this plugin. + /// + [NotNull] + internal PluginReference Reference { get; } + + /// + public bool IsEnabled + { + get => Configuration.IsEnabled; + internal set => Configuration.IsEnabled = value; + } + + /// + [NotNull] + public string Key => Manifest.Key; + + /// + public override int GetHashCode() => Manifest.Key.GetHashCode(); + } +} diff --git a/Intersect (Core)/Plugins/PluginConfiguration.cs b/Intersect (Core)/Plugins/PluginConfiguration.cs new file mode 100644 index 0000000000..ef1615e64c --- /dev/null +++ b/Intersect (Core)/Plugins/PluginConfiguration.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; + +namespace Intersect.Plugins +{ + /// + /// The basic plugin configuration class. + /// + public class PluginConfiguration + { + /// + /// If this plugin is enabled or not. + /// + [JsonProperty] + public bool IsEnabled { get; internal set; } + } +} diff --git a/Intersect (Core)/Plugins/PluginEntry.cs b/Intersect (Core)/Plugins/PluginEntry.cs new file mode 100644 index 0000000000..19bf88f693 --- /dev/null +++ b/Intersect (Core)/Plugins/PluginEntry.cs @@ -0,0 +1,17 @@ +namespace Intersect.Plugins +{ + /// + /// Abstract class that virtually defines all of the methods declared by . + /// + public abstract class PluginEntry : IPluginEntry + { + /// + public virtual void OnBootstrap(IPluginBootstrapContext context) { } + + /// + public virtual void OnStart(IPluginContext context) { } + + /// + public virtual void OnStop(IPluginContext context) { } + } +} diff --git a/Intersect (Core)/Plugins/PluginEntry`1.cs b/Intersect (Core)/Plugins/PluginEntry`1.cs new file mode 100644 index 0000000000..421ce7498a --- /dev/null +++ b/Intersect (Core)/Plugins/PluginEntry`1.cs @@ -0,0 +1,34 @@ +namespace Intersect.Plugins +{ + /// + /// Abstract class that defines translates between the generic methods and the context type specific methods of . + /// + /// The specific plugin context type. + public abstract class PluginEntry : PluginEntry, IPluginEntry + where TPluginContext : IPluginContext + { + /// + public override void OnStart(IPluginContext context) + { + if (context is TPluginContext typedPluginContext) + { + OnStart(typedPluginContext); + } + } + + /// + public override void OnStop(IPluginContext context) + { + if (context is TPluginContext typedPluginContext) + { + OnStop(typedPluginContext); + } + } + + /// + public abstract void OnStart(TPluginContext context); + + /// + public abstract void OnStop(TPluginContext context); + } +} diff --git a/Intersect (Core)/Plugins/PluginHelper.cs b/Intersect (Core)/Plugins/PluginHelper.cs new file mode 100644 index 0000000000..fbead8939b --- /dev/null +++ b/Intersect (Core)/Plugins/PluginHelper.cs @@ -0,0 +1,27 @@ +using Intersect.Logging; + +using JetBrains.Annotations; + +namespace Intersect.Plugins +{ + /// + /// Convenience abstract class that defines commonly used properties for certain plugin helpers. + /// + public abstract class PluginHelper + { + /// + /// The for this helper to use. + /// + [NotNull] + protected Logger Logger { get; } + + /// + /// Initializes this . + /// + /// The for this helper to use. + protected PluginHelper([NotNull] Logger logger) + { + Logger = logger; + } + } +} diff --git a/Intersect (Core)/Plugins/PluginInstance.cs b/Intersect (Core)/Plugins/PluginInstance.cs new file mode 100644 index 0000000000..7971e30bf5 --- /dev/null +++ b/Intersect (Core)/Plugins/PluginInstance.cs @@ -0,0 +1,57 @@ + +using Intersect.Factories; + +using JetBrains.Annotations; + +using Microsoft; + +namespace Intersect.Plugins +{ + /// + /// Representation of an instance of a . + /// + public class PluginInstance + { + /// + /// Create a for the given . + /// + /// The plugin descriptor to create an instance for. + /// A for . + [NotNull] + public static PluginInstance Create([NotNull, ValidatedNotNull] Plugin plugin) + { + var bootstrapContext = FactoryRegistry.Create(plugin); + var context = FactoryRegistry.Create(plugin); + var entry = plugin.Reference.CreateInstance(); + return new PluginInstance(entry, bootstrapContext, context); + } + + /// + /// The entry point instance. + /// + [NotNull] public IPluginEntry Entry { get; } + + /// + /// The context used for bootstrap lifecycle actions. + /// + [NotNull] public IPluginBootstrapContext BootstrapContext { get; } + + /// + /// The context used for non-bootstrap lifecycle actions. + /// + [NotNull] public IPluginContext Context { get; } + + private PluginInstance( + [NotNull] IPluginEntry entry, + [NotNull] IPluginBootstrapContext bootstrapContext, + [NotNull] IPluginContext context + ) + { + Entry = entry; + BootstrapContext = bootstrapContext; + Context = context; + } + + } + +} diff --git a/Intersect (Core)/Plugins/PluginService.cs b/Intersect (Core)/Plugins/PluginService.cs new file mode 100644 index 0000000000..6803beba16 --- /dev/null +++ b/Intersect (Core)/Plugins/PluginService.cs @@ -0,0 +1,211 @@ +using Intersect.Core; +using Intersect.Factories; +using Intersect.Plugins.Loaders; + +using JetBrains.Annotations; + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; + +namespace Intersect.Plugins +{ + /// + /// Implementation of . + /// + internal sealed class PluginService : ApplicationService, IPluginService + { + [NotNull] private static readonly string BuiltInPluginDirectory = Path.Combine("resources", "plugins"); + + /// + /// Initializes the . + /// + public PluginService() + { + if (FactoryRegistry.Factory == null) + { + throw new InvalidOperationException( + $@"Factory has not been registered for {nameof(IPluginBootstrapContext)}." + ); + } + + if (FactoryRegistry.Factory == null) + { + throw new InvalidOperationException($@"Factory has not been registered for {nameof(IPluginContext)}."); + } + + PluginDirectories = new List + { + BuiltInPluginDirectory + }; + + Plugins = new ConcurrentDictionary(); + Instances = new ConcurrentDictionary(); + Loader = new PluginLoader(); + } + + /// + /// Map of all loaded s by their name. + /// + [NotNull] + private ConcurrentDictionary Plugins { get; } + + /// + /// Map of all s by their describing . + /// + [NotNull] + private ConcurrentDictionary Instances { get; } + + /// + /// Reference to the used for discovering and loading s and their configuration. + /// + [NotNull] + private PluginLoader Loader { get; } + + /// + public override bool IsEnabled => true; + + /// + public override bool Bootstrap(IApplicationContext applicationContext) + { + try + { + // Configure the plugin server + PluginDirectories.AddRange( + applicationContext.StartupOptions.PluginDirectories ?? Array.Empty() + ); + + // Discover plugins + var discoveredPlugins = Loader.DiscoverPlugins(applicationContext, PluginDirectories); + + // Load configuration for plugins + Loader.LoadConfigurations(applicationContext, discoveredPlugins.Values); + + // Register discovered plugins + RegisterPlugins(discoveredPlugins); + + // Create plugin instances + CreateInstances(); + + // Run bootstrap plugin + RunOnAllInstances(OnBootstrap); + + return true; + } + catch (Exception exception) + { + throw new ServiceLifecycleFailureException(ServiceLifecycleStage.Bootstrap, Name, exception); + } + } + + /// + protected override void TaskStart(IApplicationContext applicationContext) => RunOnAllInstances(OnStart); + + /// + protected override void TaskStop(IApplicationContext applicationContext) + { + RunOnAllInstances(OnStop); + + Instances.Clear(); + Plugins.Clear(); + } + + /// + public Plugin this[string pluginKey] => + Plugins.TryGetValue(pluginKey, out var plugin) ? plugin : null; + + /// + public List PluginDirectories { get; } + + /// + public bool IsPluginEnabled(string pluginKey) => this[pluginKey]?.IsEnabled ?? false; + + /// + public bool EnablePlugin(string pluginKey) + { + var plugin = this[pluginKey]; + if (plugin?.IsEnabled ?? true) + { + return plugin?.IsEnabled ?? false; + } + + return plugin.IsEnabled = true; + } + + /// + public bool DisablePlugin(string pluginKey) + { + var plugin = this[pluginKey]; + if (!(plugin?.IsEnabled ?? false)) + { + return !(plugin?.IsEnabled ?? false); + } + + plugin.IsEnabled = false; + return true; + } + + private void RegisterPlugins([NotNull] IDictionary plugins) + { + foreach (var pluginEntry in plugins) + { + Debug.Assert(pluginEntry.Key != null, "pluginEntry.Key != null"); + if (!Plugins.TryAdd(pluginEntry.Key, pluginEntry.Value)) + { + throw new Exception($@"Failed to register plugin: {pluginEntry.Key}"); + } + } + } + + private void CreateInstances() + { + foreach (var pluginEntry in Plugins) + { + var plugin = pluginEntry.Value; + Debug.Assert(plugin != null, nameof(plugin) + " != null"); + + var instance = PluginInstance.Create(plugin); + if (!Instances.TryAdd(plugin, instance)) + { + throw new Exception($@"Failed to add plugin instance: {plugin.Key}"); + } + } + } + + private void RunOnAllInstances([NotNull] Action> action) => + Instances.Where(instance => instance.Key?.IsEnabled ?? false).ToList().ForEach(action); + + private void OnBootstrap(KeyValuePair instancePair) + { + Debug.Assert(instancePair.Key != null, $@"{nameof(instancePair)}.Key != null"); + Debug.Assert(instancePair.Value != null, $@"{nameof(instancePair)}.Value != null"); + + var instance = instancePair.Value; + var entry = instance.Entry; + entry.OnBootstrap(instance.BootstrapContext); + } + + private void OnStart(KeyValuePair instancePair) + { + Debug.Assert(instancePair.Key != null, $@"{nameof(instancePair)}.Key != null"); + Debug.Assert(instancePair.Value != null, $@"{nameof(instancePair)}.Value != null"); + + var instance = instancePair.Value; + var entry = instance.Entry; + entry.OnStart(instance.Context); + } + + private void OnStop(KeyValuePair instancePair) + { + Debug.Assert(instancePair.Key != null, $@"{nameof(instancePair)}.Key != null"); + Debug.Assert(instancePair.Value != null, $@"{nameof(instancePair)}.Value != null"); + + var instance = instancePair.Value; + var entry = instance.Entry; + entry.OnStop(instance.Context); + } + } +} diff --git a/Intersect (Core)/Properties/AssemblyInfo.cs b/Intersect (Core)/Properties/AssemblyInfo.cs index a2ed1af8e9..8f999f98db 100644 --- a/Intersect (Core)/Properties/AssemblyInfo.cs +++ b/Intersect (Core)/Properties/AssemblyInfo.cs @@ -1,4 +1,5 @@ -using System.Reflection; +using System.Resources; +using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -7,24 +8,21 @@ // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. - -[assembly: AssemblyTitle("Intersect Library")] -[assembly: AssemblyDescription("2D ORPG Library - Part of the Intersect Game Creation Suite")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Ascension Game Development")] +[assembly: AssemblyTitle("Intersect Core Library")] +[assembly: AssemblyDescription("Core Library - Part of the Intersect 2D Game Creation Suite")] [assembly: AssemblyProduct("Intersect Game Engine")] -[assembly: AssemblyCopyright("Copyright © 2020 Ascension Game Dev")] -[assembly: AssemblyTrademark("")] +[assembly: AssemblyCompany("Ascension Game Development")] +[assembly: AssemblyCopyright("Copyright © Ascension Game Dev 2020")] +[assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] +[assembly: AssemblyTrademark("")] // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. - [assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM - [assembly: Guid("bc304b43-1f2c-4454-9edc-48f47357ca94")] // Version information for an assembly consists of the following four values: @@ -37,6 +35,6 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] - [assembly: AssemblyVersion("0.7.0.0")] [assembly: AssemblyFileVersion("0.7.0.0")] +[assembly: NeutralResourcesLanguage("en-US")] diff --git a/Intersect (Core)/Properties/ExceptionMessages.Designer.cs b/Intersect (Core)/Properties/ExceptionMessages.Designer.cs new file mode 100644 index 0000000000..96a018f458 --- /dev/null +++ b/Intersect (Core)/Properties/ExceptionMessages.Designer.cs @@ -0,0 +1,90 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Intersect.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class ExceptionMessages { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal ExceptionMessages() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Intersect.Properties.ExceptionMessages", typeof(ExceptionMessages).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to PluginBootstrap factory needs at lease one non-null argument of type '{0}'.. + /// + internal static string PluginBootstrapContextMissingPluginArgument { + get { + return ResourceManager.GetString("PluginBootstrapContextMissingPluginArgument", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to . + /// + internal static string SwallowingExceptionFrom { + get { + return ResourceManager.GetString("SwallowingExceptionFrom", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to . + /// + internal static string SwallowingExceptionFromWithQualifiedName { + get { + return ResourceManager.GetString("SwallowingExceptionFromWithQualifiedName", resourceCulture); + } + } + } +} diff --git a/Intersect (Core)/Properties/ExceptionMessages.resx b/Intersect (Core)/Properties/ExceptionMessages.resx new file mode 100644 index 0000000000..0e206915f9 --- /dev/null +++ b/Intersect (Core)/Properties/ExceptionMessages.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + PluginBootstrap factory needs at lease one non-null argument of type '{0}'. + + + Swallowing unexpected exception from {0}. + + + Swallowing unexpected exception from {0}.{1}. + + \ No newline at end of file diff --git a/Intersect (Core)/Reflection/TypeExtensions.cs b/Intersect (Core)/Reflection/TypeExtensions.cs index fca121d919..13eb1fb4d3 100644 --- a/Intersect (Core)/Reflection/TypeExtensions.cs +++ b/Intersect (Core)/Reflection/TypeExtensions.cs @@ -6,11 +6,15 @@ using System.Linq; using System.Reflection; +using Microsoft; + namespace Intersect.Reflection { - public static class TypeExtensions { + [NotNull] + public static string QualifiedGenericName([NotNull, ValidatedNotNull] this Type type) => + $"{type.Name}<{string.Join(", ", type.GenericTypeArguments.Select(parameterType => parameterType.Name))}>"; [NotNull] public static IEnumerable FindConstructors( @@ -58,7 +62,5 @@ public static class TypeExtensions return true; } ); - } - } diff --git a/Intersect (Core)/packages.config b/Intersect (Core)/packages.config index 88bcde8e5b..90962ea7d7 100644 --- a/Intersect (Core)/packages.config +++ b/Intersect (Core)/packages.config @@ -23,6 +23,7 @@ + @@ -50,4 +51,4 @@ - + \ No newline at end of file diff --git a/Intersect.Building/Intersect.Building.csproj b/Intersect.Building/Intersect.Building.csproj index c5d0aee89e..0f457e560b 100644 --- a/Intersect.Building/Intersect.Building.csproj +++ b/Intersect.Building/Intersect.Building.csproj @@ -37,6 +37,9 @@ + + ..\packages\Microsoft.VisualStudio.Validation.15.5.31\lib\netstandard2.0\Microsoft.VisualStudio.Validation.dll + diff --git a/Intersect.Building/Properties/AssemblyInfo.cs b/Intersect.Building/Properties/AssemblyInfo.cs index a700cc1e40..8aa5078c0b 100644 --- a/Intersect.Building/Properties/AssemblyInfo.cs +++ b/Intersect.Building/Properties/AssemblyInfo.cs @@ -1,18 +1,21 @@ using System.Reflection; +using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +[assembly: InternalsVisibleTo("Intersect.Tests.Building")] + // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("Intersect.Building")] -[assembly: AssemblyDescription("")] +[assembly: AssemblyDescription("Building Target Library - Part of the Intersect 2D Game Creation Suite")] +[assembly: AssemblyProduct("Intersect Game Engine")] +[assembly: AssemblyCompany("Ascension Game Development")] +[assembly: AssemblyCopyright("Copyright © Ascension Game Dev 2020")] [assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Intersect.Building")] -[assembly: AssemblyCopyright("Copyright © 2020")] -[assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] +[assembly: AssemblyTrademark("")] // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from @@ -34,3 +37,4 @@ // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("0.7.0.0")] [assembly: AssemblyFileVersion("0.7.0.0")] +[assembly: NeutralResourcesLanguage("en-US")] diff --git a/Intersect.Building/packages.config b/Intersect.Building/packages.config index c2911d2717..975be9829e 100644 --- a/Intersect.Building/packages.config +++ b/Intersect.Building/packages.config @@ -1,4 +1,5 @@  - - \ No newline at end of file + + + diff --git a/Intersect.Client.Framework/Gwen/Control/Base.cs b/Intersect.Client.Framework/Gwen/Control/Base.cs index 67e2cfb67f..404ae0992a 100644 --- a/Intersect.Client.Framework/Gwen/Control/Base.cs +++ b/Intersect.Client.Framework/Gwen/Control/Base.cs @@ -714,6 +714,8 @@ public virtual void Dispose() mChildren?.ForEach(child => child?.Dispose()); mChildren?.Clear(); + mInnerPanel?.Dispose(); + mDisposed = true; GC.SuppressFinalize(this); } diff --git a/Intersect.Client.Framework/Intersect.Client.Framework.csproj b/Intersect.Client.Framework/Intersect.Client.Framework.csproj index c9ae5aadbf..a13e38a25c 100644 --- a/Intersect.Client.Framework/Intersect.Client.Framework.csproj +++ b/Intersect.Client.Framework/Intersect.Client.Framework.csproj @@ -45,6 +45,9 @@ ..\packages\JetBrains.Annotations.2018.3.0\lib\net20\JetBrains.Annotations.dll + + ..\packages\Microsoft.VisualStudio.Validation.15.5.31\lib\netstandard2.0\Microsoft.VisualStudio.Validation.dll + ..\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll diff --git a/Intersect.Client.Framework/Properties/AssemblyInfo.cs b/Intersect.Client.Framework/Properties/AssemblyInfo.cs index 4ab50db9a9..8866009202 100644 --- a/Intersect.Client.Framework/Properties/AssemblyInfo.cs +++ b/Intersect.Client.Framework/Properties/AssemblyInfo.cs @@ -1,4 +1,5 @@ using System.Reflection; +using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -8,13 +9,13 @@ // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("Intersect Client Framework")] -[assembly: AssemblyDescription("2D ORPG Client Framework - Part of the Intersect Game Creation Suite")] -[assembly: AssemblyConfiguration("")] +[assembly: AssemblyDescription("Client Framework - Part of the Intersect 2D Game Creation Suite")] +[assembly: AssemblyProduct("Intersect Game Engine")] [assembly: AssemblyCompany("Ascension Game Development")] -[assembly: AssemblyProduct("Intersect Client Framework")] -[assembly: AssemblyCopyright("Copyright © 2020 Ascension Game Dev")] -[assembly: AssemblyTrademark("")] +[assembly: AssemblyCopyright("Copyright © Ascension Game Dev 2020")] +[assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] +[assembly: AssemblyTrademark("")] // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from @@ -36,3 +37,4 @@ // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("0.7.0.0")] [assembly: AssemblyFileVersion("0.7.0.0")] +[assembly: NeutralResourcesLanguage("en-US")] diff --git a/Intersect.Client.Framework/packages.config b/Intersect.Client.Framework/packages.config index a8f9fa30c7..9a0338f71e 100644 --- a/Intersect.Client.Framework/packages.config +++ b/Intersect.Client.Framework/packages.config @@ -6,5 +6,6 @@ + \ No newline at end of file diff --git a/Intersect.Client/Plugins/Helpers/ClientLifecycleHelper.cs b/Intersect.Client/Plugins/Helpers/ClientLifecycleHelper.cs new file mode 100644 index 0000000000..485e4d9824 --- /dev/null +++ b/Intersect.Client/Plugins/Helpers/ClientLifecycleHelper.cs @@ -0,0 +1,37 @@ +using Intersect.Client.General; +using Intersect.Client.Interface; +using Intersect.Client.Plugins.Interfaces; +using Intersect.Plugins.Helpers; + +using JetBrains.Annotations; + +namespace Intersect.Client.Plugins.Helpers +{ + /// + internal sealed class ClientLifecycleHelper : ContextHelper, IClientLifecycleHelper + { + /// + public event LifecycleChangeStateHandler LifecycleChangeState; + + internal ClientLifecycleHelper([NotNull] IClientPluginContext context) : base(context) + { + Globals.ClientLifecycleHelpers.Add(this); + } + + ~ClientLifecycleHelper() + { + Globals.ClientLifecycleHelpers.Remove(this); + } + + /// + public IMutableInterface Interface => + Client.Interface.Interface.MenuUi ?? Client.Interface.Interface.GameUi as IMutableInterface; + + /// + public void OnLifecycleChangeState(GameStates state) + { + var lifecycleChangeStateArgs = new LifecycleChangeStateArgs(state); + LifecycleChangeState?.Invoke(Context, lifecycleChangeStateArgs); + } + } +} diff --git a/Intersect.Client/Properties/AssemblyInfo.cs b/Intersect.Client/Properties/AssemblyInfo.cs index 022316e104..faea00f25b 100644 --- a/Intersect.Client/Properties/AssemblyInfo.cs +++ b/Intersect.Client/Properties/AssemblyInfo.cs @@ -1,4 +1,5 @@ using System.Reflection; +using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -7,24 +8,21 @@ // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. - [assembly: AssemblyTitle("Intersect Client")] +[assembly: AssemblyDescription("Game Client - Part of the Intersect 2D Game Creation Suite")] [assembly: AssemblyProduct("Intersect Game Engine")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyDescription("2D ORPG Client - Part of the Intersect Game Creation Suite")] [assembly: AssemblyCompany("Ascension Game Development")] -[assembly: AssemblyCopyright("Copyright © 2020 Ascension Game Dev")] -[assembly: AssemblyTrademark("")] +[assembly: AssemblyCopyright("Copyright © Ascension Game Dev 2020")] +[assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] +[assembly: AssemblyTrademark("")] // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. - [assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM - [assembly: Guid("06a02cb5-2c81-44e9-a58a-154fa4261677")] // Version information for an assembly consists of the following four values: @@ -37,6 +35,6 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] - [assembly: AssemblyVersion("0.7.0.0")] [assembly: AssemblyFileVersion("0.7.0.0")] +[assembly: NeutralResourcesLanguage("en-US")] diff --git a/Intersect.Client/packages.config b/Intersect.Client/packages.config index af7edb97cd..5383e4ac18 100644 --- a/Intersect.Client/packages.config +++ b/Intersect.Client/packages.config @@ -11,6 +11,7 @@ + diff --git a/Intersect.Editor/Intersect.Editor.csproj b/Intersect.Editor/Intersect.Editor.csproj index 3975961e1b..fdfcce103a 100644 --- a/Intersect.Editor/Intersect.Editor.csproj +++ b/Intersect.Editor/Intersect.Editor.csproj @@ -562,6 +562,9 @@ ..\packages\AscensionGameDev.Lidgren.Network.1.7.2\lib\net46\Lidgren.Network.dll + + ..\packages\Microsoft.VisualStudio.Validation.15.5.31\lib\netstandard2.0\Microsoft.VisualStudio.Validation.dll + ..\packages\Mono.Data.Sqlite.Portable.1.0.3.5\lib\net4\Mono.Data.Sqlite.dll @@ -933,4 +936,4 @@ --> - + \ No newline at end of file diff --git a/Intersect.Editor/Properties/AssemblyInfo.cs b/Intersect.Editor/Properties/AssemblyInfo.cs index afc999273e..5c9e329a34 100644 --- a/Intersect.Editor/Properties/AssemblyInfo.cs +++ b/Intersect.Editor/Properties/AssemblyInfo.cs @@ -1,4 +1,5 @@ using System.Reflection; +using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -9,22 +10,20 @@ // associated with an assembly. [assembly: AssemblyTitle("Intersect Editor")] +[assembly: AssemblyDescription("Game Content Editor - Part of the Intersect 2D Game Creation Suite")] [assembly: AssemblyProduct("Intersect Game Engine")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyDescription("2D ORPG Editor - Part of the Intersect Game Creation Suite")] [assembly: AssemblyCompany("Ascension Game Development")] -[assembly: AssemblyCopyright("Copyright © 2020 Ascension Game Dev")] -[assembly: AssemblyTrademark("")] +[assembly: AssemblyCopyright("Copyright © Ascension Game Dev 2020")] +[assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] +[assembly: AssemblyTrademark("")] // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. - [assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM - [assembly: Guid("d33fe268-4892-45f7-b392-422709232a6d")] // Version information for an assembly consists of the following four values: @@ -37,6 +36,6 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] - [assembly: AssemblyVersion("0.7.0.0")] [assembly: AssemblyFileVersion("0.7.0.0")] +[assembly: NeutralResourcesLanguage("en-US")] diff --git a/Intersect.Editor/packages.config b/Intersect.Editor/packages.config index 5f3cd12350..55add7c269 100644 --- a/Intersect.Editor/packages.config +++ b/Intersect.Editor/packages.config @@ -11,6 +11,7 @@ + diff --git a/Intersect.Network/Intersect.Network.csproj b/Intersect.Network/Intersect.Network.csproj index ee604ddf8e..bb7a3a6519 100644 --- a/Intersect.Network/Intersect.Network.csproj +++ b/Intersect.Network/Intersect.Network.csproj @@ -71,6 +71,9 @@ ..\packages\AscensionGameDev.Lidgren.Network.1.7.2\lib\net46\Lidgren.Network.dll + + ..\packages\Microsoft.VisualStudio.Validation.15.5.31\lib\netstandard2.0\Microsoft.VisualStudio.Validation.dll + @@ -129,4 +132,4 @@ - + \ No newline at end of file diff --git a/Intersect.Network/Properties/AssemblyInfo.cs b/Intersect.Network/Properties/AssemblyInfo.cs index 58af533e69..a2f93294a4 100644 --- a/Intersect.Network/Properties/AssemblyInfo.cs +++ b/Intersect.Network/Properties/AssemblyInfo.cs @@ -1,4 +1,5 @@ using System.Reflection; +using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -8,13 +9,13 @@ // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("Intersect Network")] -[assembly: AssemblyDescription("2D ORPG Lidgren Wrapper - Part of the Intersect Game Creation Suite")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Ascension Game Dev")] +[assembly: AssemblyDescription("Common Networking Library - Part of the Intersect 2D Game Creation Suite")] [assembly: AssemblyProduct("Intersect Game Engine")] -[assembly: AssemblyCopyright("Copyright © 2020 Ascension Game Dev")] -[assembly: AssemblyTrademark("")] +[assembly: AssemblyCompany("Ascension Game Development")] +[assembly: AssemblyCopyright("Copyright © Ascension Game Dev 2020")] +[assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] +[assembly: AssemblyTrademark("")] // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from @@ -34,6 +35,6 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] - [assembly: AssemblyVersion("0.7.0.0")] [assembly: AssemblyFileVersion("0.7.0.0")] +[assembly: NeutralResourcesLanguage("en-US")] diff --git a/Intersect.Network/packages.config b/Intersect.Network/packages.config index cb5626ecd4..7474bbca72 100644 --- a/Intersect.Network/packages.config +++ b/Intersect.Network/packages.config @@ -7,5 +7,6 @@ + \ No newline at end of file diff --git a/Intersect.Server/Intersect.Server.csproj b/Intersect.Server/Intersect.Server.csproj index f3345ff833..c43f116e06 100644 --- a/Intersect.Server/Intersect.Server.csproj +++ b/Intersect.Server/Intersect.Server.csproj @@ -302,6 +302,9 @@ ..\packages\Microsoft.Owin.Security.OAuth.4.1.0\lib\net45\Microsoft.Owin.Security.OAuth.dll + + ..\packages\Microsoft.VisualStudio.Validation.15.5.31\lib\netstandard2.0\Microsoft.VisualStudio.Validation.dll + ..\packages\Mono.Data.Sqlite.Portable.1.0.3.5\lib\net4\Mono.Data.Sqlite.dll @@ -419,21 +422,6 @@ ..\packages\WebSocketSharp.1.0.3-rc11\lib\websocket-sharp.dll - - ..\packages\Microsoft.IdentityModel.Logging.5.4.0\lib\net461\Microsoft.IdentityModel.Logging.dll - - - ..\packages\Microsoft.IdentityModel.Tokens.5.4.0\lib\net461\Microsoft.IdentityModel.Tokens.dll - - - ..\packages\Microsoft.IdentityModel.JsonWebTokens.5.4.0\lib\net461\Microsoft.IdentityModel.JsonWebTokens.dll - - - ..\packages\System.IdentityModel.Tokens.Jwt.5.4.0\lib\net461\System.IdentityModel.Tokens.Jwt.dll - - - ..\packages\Microsoft.Owin.Security.Jwt.4.0.1\lib\net45\Microsoft.Owin.Security.Jwt.dll - ..\packages\Owin.Security.AesDataProtectorProvider.1.1.2\lib\net45\Owin.Security.AesDataProtectorProvider.dll diff --git a/Intersect.Server/Plugins/Helpers/ServerLifecycleHelper.cs b/Intersect.Server/Plugins/Helpers/ServerLifecycleHelper.cs new file mode 100644 index 0000000000..4216a5419a --- /dev/null +++ b/Intersect.Server/Plugins/Helpers/ServerLifecycleHelper.cs @@ -0,0 +1,15 @@ +using Intersect.Plugins.Helpers; + +using JetBrains.Annotations; + +namespace Intersect.Server.Plugins.Helpers +{ + /// + internal sealed class ServerLifecycleHelper : ContextHelper, IServerLifecycleHelper + { + /// + public ServerLifecycleHelper([NotNull] IServerPluginContext context) : base(context) + { + } + } +} diff --git a/Intersect.Server/Properties/AssemblyInfo.cs b/Intersect.Server/Properties/AssemblyInfo.cs index 35405c18d0..119e1c7fcc 100644 --- a/Intersect.Server/Properties/AssemblyInfo.cs +++ b/Intersect.Server/Properties/AssemblyInfo.cs @@ -1,4 +1,5 @@ using System.Reflection; +using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -7,24 +8,21 @@ // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. - [assembly: AssemblyTitle("Intersect Server")] -[assembly: AssemblyDescription("2D ORPG Server - Part of the Intersect Game Creation Suite")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Ascension Game Dev")] +[assembly: AssemblyDescription("Game Server - Part of the Intersect 2D Game Creation Suite")] [assembly: AssemblyProduct("Intersect Game Engine")] -[assembly: AssemblyCopyright("Copyright © 2020 Ascension Game Dev")] -[assembly: AssemblyTrademark("")] +[assembly: AssemblyCompany("Ascension Game Development")] +[assembly: AssemblyCopyright("Copyright © Ascension Game Dev 2020")] +[assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] +[assembly: AssemblyTrademark("")] // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. - [assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM - [assembly: Guid("1d769c08-9a2c-4a6d-8ba5-20dbc8ee036b")] // Version information for an assembly consists of the following four values: @@ -34,6 +32,6 @@ // Build Number // Revision // - [assembly: AssemblyVersion("0.7.0.0")] [assembly: AssemblyFileVersion("0.7.0.0")] +[assembly: NeutralResourcesLanguage("en-US")] diff --git a/Intersect.Server/packages.config b/Intersect.Server/packages.config index f32410e9dd..18b1bb505b 100644 --- a/Intersect.Server/packages.config +++ b/Intersect.Server/packages.config @@ -50,6 +50,7 @@ + diff --git a/Intersect.Tests.Client.Framework/Intersect.Tests.Client.Framework.csproj b/Intersect.Tests.Client.Framework/Intersect.Tests.Client.Framework.csproj index a8809a5267..64a05db1b4 100644 --- a/Intersect.Tests.Client.Framework/Intersect.Tests.Client.Framework.csproj +++ b/Intersect.Tests.Client.Framework/Intersect.Tests.Client.Framework.csproj @@ -54,6 +54,9 @@ ..\packages\MSTest.TestFramework.1.3.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll + + ..\packages\Microsoft.VisualStudio.Validation.15.5.31\lib\netstandard2.0\Microsoft.VisualStudio.Validation.dll + ..\packages\NUnit.3.11.0\lib\net45\nunit.framework.dll @@ -105,4 +108,4 @@ - + \ No newline at end of file diff --git a/Intersect.Tests.Client.Framework/Properties/AssemblyInfo.cs b/Intersect.Tests.Client.Framework/Properties/AssemblyInfo.cs index e7b23579fe..803528fb5f 100644 --- a/Intersect.Tests.Client.Framework/Properties/AssemblyInfo.cs +++ b/Intersect.Tests.Client.Framework/Properties/AssemblyInfo.cs @@ -1,19 +1,21 @@ using System.Reflection; +using System.Resources; using System.Runtime.InteropServices; [assembly: AssemblyTitle("Intersect.Tests.Client.Framework")] -[assembly: AssemblyDescription("")] +[assembly: AssemblyDescription("Tests for the Client Framework - Part of the Intersect 2D Game Creation Suite")] +[assembly: AssemblyProduct("Intersect Game Engine")] +[assembly: AssemblyCompany("Ascension Game Development")] +[assembly: AssemblyCopyright("Copyright © Ascension Game Dev 2020")] [assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Intersect.Tests.Client.Framework")] -[assembly: AssemblyCopyright("Copyright © 2020")] -[assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] +[assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("83033560-06f2-4155-9956-e4f0f06dc113")] // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyVersion("0.7.0.0")] +[assembly: AssemblyFileVersion("0.7.0.0")] +[assembly: NeutralResourcesLanguage("en-US")] diff --git a/Intersect.Tests.Client.Framework/packages.config b/Intersect.Tests.Client.Framework/packages.config index 7e103885f9..a4e66031c1 100644 --- a/Intersect.Tests.Client.Framework/packages.config +++ b/Intersect.Tests.Client.Framework/packages.config @@ -5,6 +5,7 @@ + diff --git a/Intersect.Tests.Client/Intersect.Tests.Client.csproj b/Intersect.Tests.Client/Intersect.Tests.Client.csproj index b6a5c2e093..a07dda37f9 100644 --- a/Intersect.Tests.Client/Intersect.Tests.Client.csproj +++ b/Intersect.Tests.Client/Intersect.Tests.Client.csproj @@ -54,6 +54,9 @@ ..\packages\MSTest.TestFramework.1.3.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll + + ..\packages\Microsoft.VisualStudio.Validation.15.5.31\lib\netstandard2.0\Microsoft.VisualStudio.Validation.dll + ..\packages\NUnit.3.11.0\lib\net45\nunit.framework.dll @@ -109,4 +112,4 @@ - + \ No newline at end of file diff --git a/Intersect.Tests.Client/Properties/AssemblyInfo.cs b/Intersect.Tests.Client/Properties/AssemblyInfo.cs index ced88a6785..5735add15f 100644 --- a/Intersect.Tests.Client/Properties/AssemblyInfo.cs +++ b/Intersect.Tests.Client/Properties/AssemblyInfo.cs @@ -1,19 +1,21 @@ using System.Reflection; +using System.Resources; using System.Runtime.InteropServices; [assembly: AssemblyTitle("Intersect.Tests.Client")] -[assembly: AssemblyDescription("")] +[assembly: AssemblyDescription("Tests for the Game Client - Part of the Intersect 2D Game Creation Suite")] +[assembly: AssemblyProduct("Intersect Game Engine")] +[assembly: AssemblyCompany("Ascension Game Development")] +[assembly: AssemblyCopyright("Copyright © Ascension Game Dev 2020")] [assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Intersect.Tests.Client")] -[assembly: AssemblyCopyright("Copyright © 2020")] -[assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] +[assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("49599c25-3c71-4ee5-98c9-957ee20f7de7")] // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyVersion("0.7.0.0")] +[assembly: AssemblyFileVersion("0.7.0.0")] +[assembly: NeutralResourcesLanguage("en-US")] diff --git a/Intersect.Tests.Client/packages.config b/Intersect.Tests.Client/packages.config index 7e103885f9..a4e66031c1 100644 --- a/Intersect.Tests.Client/packages.config +++ b/Intersect.Tests.Client/packages.config @@ -5,6 +5,7 @@ + diff --git a/Intersect.Tests.Editor/Intersect.Tests.Editor.csproj b/Intersect.Tests.Editor/Intersect.Tests.Editor.csproj index 40a6f96869..a703a371eb 100644 --- a/Intersect.Tests.Editor/Intersect.Tests.Editor.csproj +++ b/Intersect.Tests.Editor/Intersect.Tests.Editor.csproj @@ -28,24 +28,25 @@ - + true - full - false - ..\build\debug\tests\ + bin\x86\Debug\ DEBUG;TRACE + full + x86 + 7.3 prompt - 4 - false + MinimumRecommendedRules.ruleset - - pdbonly - true - ..\build\release\tests\ + + bin\x86\Release\ TRACE + true + pdbonly + x86 + 7.3 prompt - 4 - false + MinimumRecommendedRules.ruleset @@ -54,6 +55,9 @@ ..\packages\MSTest.TestFramework.1.3.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll + + ..\packages\Microsoft.VisualStudio.Validation.15.5.31\lib\netstandard2.0\Microsoft.VisualStudio.Validation.dll + ..\packages\NUnit.3.11.0\lib\net45\nunit.framework.dll @@ -105,4 +109,4 @@ - + \ No newline at end of file diff --git a/Intersect.Tests.Editor/Properties/AssemblyInfo.cs b/Intersect.Tests.Editor/Properties/AssemblyInfo.cs index 5de5e0f09d..17b470e232 100644 --- a/Intersect.Tests.Editor/Properties/AssemblyInfo.cs +++ b/Intersect.Tests.Editor/Properties/AssemblyInfo.cs @@ -1,19 +1,21 @@ using System.Reflection; +using System.Resources; using System.Runtime.InteropServices; [assembly: AssemblyTitle("Intersect.Tests.Editor")] -[assembly: AssemblyDescription("")] +[assembly: AssemblyDescription("Tests for the Game Content Editor - Part of the Intersect 2D Game Creation Suite")] +[assembly: AssemblyProduct("Intersect Game Engine")] +[assembly: AssemblyCompany("Ascension Game Development")] +[assembly: AssemblyCopyright("Copyright © Ascension Game Dev 2020")] [assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Intersect.Tests.Editor")] -[assembly: AssemblyCopyright("Copyright © 2020")] -[assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] +[assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("3e8fc79e-d0b3-4554-9abd-bd6dcbfbee5d")] // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyVersion("0.7.0.0")] +[assembly: AssemblyFileVersion("0.7.0.0")] +[assembly: NeutralResourcesLanguage("en-US")] diff --git a/Intersect.Tests.Editor/packages.config b/Intersect.Tests.Editor/packages.config index 7e103885f9..a4e66031c1 100644 --- a/Intersect.Tests.Editor/packages.config +++ b/Intersect.Tests.Editor/packages.config @@ -5,6 +5,7 @@ + diff --git a/Intersect.Tests.Network/Intersect.Tests.Network.csproj b/Intersect.Tests.Network/Intersect.Tests.Network.csproj index 43baebcf36..a9d54479a2 100644 --- a/Intersect.Tests.Network/Intersect.Tests.Network.csproj +++ b/Intersect.Tests.Network/Intersect.Tests.Network.csproj @@ -54,6 +54,9 @@ ..\packages\MSTest.TestFramework.1.3.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll + + ..\packages\Microsoft.VisualStudio.Validation.15.5.31\lib\netstandard2.0\Microsoft.VisualStudio.Validation.dll + ..\packages\NUnit.3.11.0\lib\net45\nunit.framework.dll @@ -105,4 +108,4 @@ - + \ No newline at end of file diff --git a/Intersect.Tests.Network/Properties/AssemblyInfo.cs b/Intersect.Tests.Network/Properties/AssemblyInfo.cs index 6b6b8a6a39..5240b612e5 100644 --- a/Intersect.Tests.Network/Properties/AssemblyInfo.cs +++ b/Intersect.Tests.Network/Properties/AssemblyInfo.cs @@ -1,19 +1,21 @@ using System.Reflection; +using System.Resources; using System.Runtime.InteropServices; [assembly: AssemblyTitle("Intersect.Tests.Network")] -[assembly: AssemblyDescription("")] +[assembly: AssemblyDescription("Tests for the Common Networking Library - Part of the Intersect 2D Game Creation Suite")] +[assembly: AssemblyProduct("Intersect Game Engine")] +[assembly: AssemblyCompany("Ascension Game Development")] +[assembly: AssemblyCopyright("Copyright © Ascension Game Dev 2020")] [assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Intersect.Tests.Network")] -[assembly: AssemblyCopyright("Copyright © 2020")] -[assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] +[assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("e66a4b60-e79c-4222-a702-94d3cc7a2377")] // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyVersion("0.7.0.0")] +[assembly: AssemblyFileVersion("0.7.0.0")] +[assembly: NeutralResourcesLanguage("en-US")] diff --git a/Intersect.Tests.Network/packages.config b/Intersect.Tests.Network/packages.config index 7e103885f9..a4e66031c1 100644 --- a/Intersect.Tests.Network/packages.config +++ b/Intersect.Tests.Network/packages.config @@ -5,6 +5,7 @@ + diff --git a/Intersect.Tests.Server/Intersect.Tests.Server.csproj b/Intersect.Tests.Server/Intersect.Tests.Server.csproj index 96e0da8ec9..3c669f05d6 100644 --- a/Intersect.Tests.Server/Intersect.Tests.Server.csproj +++ b/Intersect.Tests.Server/Intersect.Tests.Server.csproj @@ -54,6 +54,9 @@ ..\packages\MSTest.TestFramework.1.3.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll + + ..\packages\Microsoft.VisualStudio.Validation.15.5.31\lib\netstandard2.0\Microsoft.VisualStudio.Validation.dll + ..\packages\NUnit.3.11.0\lib\net45\nunit.framework.dll @@ -109,4 +112,4 @@ - + \ No newline at end of file diff --git a/Intersect.Tests.Server/Properties/AssemblyInfo.cs b/Intersect.Tests.Server/Properties/AssemblyInfo.cs index af296313dc..962a90a38b 100644 --- a/Intersect.Tests.Server/Properties/AssemblyInfo.cs +++ b/Intersect.Tests.Server/Properties/AssemblyInfo.cs @@ -1,19 +1,21 @@ using System.Reflection; +using System.Resources; using System.Runtime.InteropServices; [assembly: AssemblyTitle("Intersect.Tests.Server")] -[assembly: AssemblyDescription("")] +[assembly: AssemblyDescription("Tests for the Game Server - Part of the Intersect 2D Game Creation Suite")] +[assembly: AssemblyProduct("Intersect Game Engine")] +[assembly: AssemblyCompany("Ascension Game Development")] +[assembly: AssemblyCopyright("Copyright © Ascension Game Dev 2020")] [assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Intersect.Tests.Server")] -[assembly: AssemblyCopyright("Copyright © 2020")] -[assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] +[assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("f55a208d-6e20-462f-a204-ef13bccf67d8")] // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyVersion("0.7.0.0")] +[assembly: AssemblyFileVersion("0.7.0.0")] +[assembly: NeutralResourcesLanguage("en-US")] diff --git a/Intersect.Tests.Server/packages.config b/Intersect.Tests.Server/packages.config index 7e103885f9..a4e66031c1 100644 --- a/Intersect.Tests.Server/packages.config +++ b/Intersect.Tests.Server/packages.config @@ -5,6 +5,7 @@ + diff --git a/Intersect.Tests/Intersect.Tests.csproj b/Intersect.Tests/Intersect.Tests.csproj index 08c21e7805..1a7d974305 100644 --- a/Intersect.Tests/Intersect.Tests.csproj +++ b/Intersect.Tests/Intersect.Tests.csproj @@ -62,6 +62,9 @@ ..\packages\MSTest.TestFramework.1.3.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll + + ..\packages\Microsoft.VisualStudio.Validation.15.5.31\lib\netstandard2.0\Microsoft.VisualStudio.Validation.dll + ..\packages\Moq.4.14.1\lib\net45\Moq.dll @@ -152,4 +155,4 @@ - + \ No newline at end of file diff --git a/Intersect.Tests/Properties/AssemblyInfo.cs b/Intersect.Tests/Properties/AssemblyInfo.cs index ba91196022..1dd6ec5380 100644 --- a/Intersect.Tests/Properties/AssemblyInfo.cs +++ b/Intersect.Tests/Properties/AssemblyInfo.cs @@ -1,19 +1,21 @@ using System.Reflection; +using System.Resources; using System.Runtime.InteropServices; [assembly: AssemblyTitle("Intersect.Tests")] -[assembly: AssemblyDescription("")] +[assembly: AssemblyDescription("Tests for the Core Library - Part of the Intersect 2D Game Creation Suite")] +[assembly: AssemblyProduct("Intersect Game Engine")] +[assembly: AssemblyCompany("Ascension Game Development")] +[assembly: AssemblyCopyright("Copyright © Ascension Game Dev 2020")] [assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Intersect.Tests")] -[assembly: AssemblyCopyright("Copyright © 2018")] -[assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] +[assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("4030d4d5-9dc6-496d-9763-9a41dd1decbe")] // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyVersion("0.7.0.0")] +[assembly: AssemblyFileVersion("0.7.0.0")] +[assembly: NeutralResourcesLanguage("en-US")] diff --git a/Intersect.Tests/packages.config b/Intersect.Tests/packages.config index 948bb735aa..04c28b77c8 100644 --- a/Intersect.Tests/packages.config +++ b/Intersect.Tests/packages.config @@ -8,6 +8,7 @@ + diff --git a/Intersect.Utilities/Intersect.Utilities.csproj b/Intersect.Utilities/Intersect.Utilities.csproj index 928f926cd0..5ebc44da6a 100644 --- a/Intersect.Utilities/Intersect.Utilities.csproj +++ b/Intersect.Utilities/Intersect.Utilities.csproj @@ -57,6 +57,9 @@ ..\packages\JetBrains.Annotations.2018.3.0\lib\net20\JetBrains.Annotations.dll + + ..\packages\Microsoft.VisualStudio.Validation.15.5.31\lib\netstandard2.0\Microsoft.VisualStudio.Validation.dll + ..\packages\Newtonsoft.Json.12.0.1\lib\net45\Newtonsoft.Json.dll diff --git a/Intersect.Utilities/packages.config b/Intersect.Utilities/packages.config index 778aece9a7..3b0c7589e5 100644 --- a/Intersect.Utilities/packages.config +++ b/Intersect.Utilities/packages.config @@ -1,7 +1,7 @@  - + \ No newline at end of file From 1856a25f92b549fc2b4313a2713650d889c5d61f Mon Sep 17 00:00:00 2001 From: Robert Lodico Date: Tue, 28 Jul 2020 01:26:18 -0400 Subject: [PATCH 22/45] Cleanup, example client plugin --- .../Assets/join-our-discord.png | Bin 0 -> 6877 bytes .../ExampleClientPluginEntry.cs | 131 +++++++++++++++ .../ExampleCommandLineOptions.cs | 48 ++++++ .../ExamplePluginConfiguration.cs | 22 +++ .../Intersect.Examples.ClientPlugin.csproj | 112 +++++++++++++ .../Manifest.cs | 55 +++++++ .../Properties/AssemblyInfo.cs | 40 +++++ .../app.config | 79 +++++++++ .../packages.config | 17 ++ ...tersect.Examples.Tests.ClientPlugin.csproj | 115 ++++++++++++++ .../ManifestTest.cs | 24 +++ .../Properties/AssemblyInfo.cs | 34 ++++ .../packages.config | 19 +++ Examples/Intersect.Examples.sln | 71 +++++++++ .../Core/ServiceLifecycleFailureException.cs | 150 ++++++++++++++++++ .../Core/ServiceLifecycleStage.cs | 31 ++++ Intersect (Core)/Intersect (Core).csproj | 2 + Intersect (Core)/Plugins/IPluginEntry.cs | 12 +- Intersect (Core)/Plugins/IPluginEntry`1.cs | 6 +- Intersect (Core)/Plugins/PluginEntry.cs | 25 ++- .../Intersect.Client.Framework.csproj | 2 +- .../Intersect.Client.Framework.nuspec | 1 - 22 files changed, 987 insertions(+), 9 deletions(-) create mode 100644 Examples/Intersect.Examples.ClientPlugin/Assets/join-our-discord.png create mode 100644 Examples/Intersect.Examples.ClientPlugin/ExampleClientPluginEntry.cs create mode 100644 Examples/Intersect.Examples.ClientPlugin/ExampleCommandLineOptions.cs create mode 100644 Examples/Intersect.Examples.ClientPlugin/ExamplePluginConfiguration.cs create mode 100644 Examples/Intersect.Examples.ClientPlugin/Intersect.Examples.ClientPlugin.csproj create mode 100644 Examples/Intersect.Examples.ClientPlugin/Manifest.cs create mode 100644 Examples/Intersect.Examples.ClientPlugin/Properties/AssemblyInfo.cs create mode 100644 Examples/Intersect.Examples.ClientPlugin/app.config create mode 100644 Examples/Intersect.Examples.ClientPlugin/packages.config create mode 100644 Examples/Intersect.Examples.Tests.ClientPlugin/Intersect.Examples.Tests.ClientPlugin.csproj create mode 100644 Examples/Intersect.Examples.Tests.ClientPlugin/ManifestTest.cs create mode 100644 Examples/Intersect.Examples.Tests.ClientPlugin/Properties/AssemblyInfo.cs create mode 100644 Examples/Intersect.Examples.Tests.ClientPlugin/packages.config create mode 100644 Examples/Intersect.Examples.sln create mode 100644 Intersect (Core)/Core/ServiceLifecycleFailureException.cs create mode 100644 Intersect (Core)/Core/ServiceLifecycleStage.cs diff --git a/Examples/Intersect.Examples.ClientPlugin/Assets/join-our-discord.png b/Examples/Intersect.Examples.ClientPlugin/Assets/join-our-discord.png new file mode 100644 index 0000000000000000000000000000000000000000..561c0c9e32da977a739ad92a7af8876e85de40f5 GIT binary patch literal 6877 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 XTs$df>1puq00000NkvXXu0mjf%GW1N literal 0 HcmV?d00001 diff --git a/Examples/Intersect.Examples.ClientPlugin/ExampleClientPluginEntry.cs b/Examples/Intersect.Examples.ClientPlugin/ExampleClientPluginEntry.cs new file mode 100644 index 0000000000..077938f1c4 --- /dev/null +++ b/Examples/Intersect.Examples.ClientPlugin/ExampleClientPluginEntry.cs @@ -0,0 +1,131 @@ +using Intersect.Client.Framework.Content; +using Intersect.Client.Framework.Graphics; +using Intersect.Client.Framework.Gwen; +using Intersect.Client.Framework.Gwen.Control; +using Intersect.Client.Plugins; +using Intersect.Client.Plugins.Interfaces; +using Intersect.Plugins; + +using JetBrains.Annotations; + +using Microsoft; + +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Threading; + +namespace Intersect.Examples.ClientPlugin +{ + /// + /// Demonstrates basic plugin functionality for the client. + /// + [SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters")] + public class ExampleClientPluginEntry : ClientPluginEntry + { + private bool mDisposed; + [UsedImplicitly] private Mutex mMutex; + + private GameTexture mButtonTexture; + + /// + public override void OnBootstrap([NotNull, ValidatedNotNull] IPluginBootstrapContext context) + { + context.Logging.Application.Info( + $@"{nameof(ExampleClientPluginEntry)}.{nameof(OnBootstrap)} writing to the application log!"); + + context.Logging.Plugin.Info( + $@"{nameof(ExampleClientPluginEntry)}.{nameof(OnBootstrap)} writing to the plugin log!"); + + mMutex = new Mutex(true, "testplugin", out var createdNew); + if (!createdNew) + { + Environment.Exit(-1); + } + + var exampleCommandLineOptions = context.CommandLine.ParseArguments(); + if (!exampleCommandLineOptions.ExampleFlag) + { + context.Logging.Plugin.Warn("Client wasn't started with the start-up flag!"); + } + + context.Logging.Plugin.Info( + $@"{nameof(exampleCommandLineOptions.ExampleVariable)} = {exampleCommandLineOptions.ExampleVariable}"); + } + + /// + public override void OnStart([NotNull, ValidatedNotNull] IClientPluginContext context) + { + context.Logging.Application.Info( + $@"{nameof(ExampleClientPluginEntry)}.{nameof(OnStart)} writing to the application log!"); + + context.Logging.Plugin.Info( + $@"{nameof(ExampleClientPluginEntry)}.{nameof(OnStart)} writing to the plugin log!"); + + mButtonTexture = context.ContentManager.LoadEmbedded( + context, ContentTypes.Interface, "Assets/join-our-discord.png"); + + context.Lifecycle.LifecycleChangeState += HandleLifecycleChangeState; + } + + /// + public override void OnStop([NotNull, ValidatedNotNull] IClientPluginContext context) + { + context.Logging.Application.Info( + $@"{nameof(ExampleClientPluginEntry)}.{nameof(OnStop)} writing to the application log!"); + + context.Logging.Plugin.Info( + $@"{nameof(ExampleClientPluginEntry)}.{nameof(OnStop)} writing to the plugin log!"); + } + + private void HandleLifecycleChangeState([NotNull, ValidatedNotNull] IClientPluginContext context, + [NotNull, ValidatedNotNull] LifecycleChangeStateArgs lifecycleChangeStateArgs) + { + Debug.Assert(mButtonTexture != null, nameof(mButtonTexture) + " != null"); + + var activeInterface = context.Lifecycle.Interface; + if (activeInterface == null) + { + return; + } + + var button = activeInterface.Create