1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2026-01-01 02:03:32 +00:00

Merge branch 'development'

This commit is contained in:
SpoinkyNL 2017-11-12 18:26:31 +01:00
commit 9437c9386b
102 changed files with 5900 additions and 4298 deletions

View File

@ -10,7 +10,7 @@
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly> <dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" /> <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-9.0.0.0" newVersion="9.0.0.0" /> <bindingRedirect oldVersion="0.0.0.0-10.0.0.0" newVersion="10.0.0.0" />
</dependentAssembly> </dependentAssembly>
<dependentAssembly> <dependentAssembly>
<assemblyIdentity name="log4net" publicKeyToken="669e0ddf0bb1aa2a" culture="neutral" /> <assemblyIdentity name="log4net" publicKeyToken="669e0ddf0bb1aa2a" culture="neutral" />
@ -30,7 +30,7 @@
</dependentAssembly> </dependentAssembly>
<dependentAssembly> <dependentAssembly>
<assemblyIdentity name="SharpDX" publicKeyToken="b4dcf0f35e5521f1" culture="neutral" /> <assemblyIdentity name="SharpDX" publicKeyToken="b4dcf0f35e5521f1" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.0.2.0" newVersion="3.0.2.0" /> <bindingRedirect oldVersion="0.0.0.0-4.0.1.0" newVersion="4.0.1.0" />
</dependentAssembly> </dependentAssembly>
<dependentAssembly> <dependentAssembly>
<assemblyIdentity name="Castle.Core" publicKeyToken="407dd0808d44fbdc" culture="neutral" /> <assemblyIdentity name="Castle.Core" publicKeyToken="407dd0808d44fbdc" culture="neutral" />
@ -44,6 +44,10 @@
<assemblyIdentity name="DeltaCompressionDotNet.MsDelta" publicKeyToken="46b2138a390abf55" culture="neutral" /> <assemblyIdentity name="DeltaCompressionDotNet.MsDelta" publicKeyToken="46b2138a390abf55" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-1.1.0.0" newVersion="1.1.0.0" /> <bindingRedirect oldVersion="0.0.0.0-1.1.0.0" newVersion="1.1.0.0" />
</dependentAssembly> </dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="SharpCompress" publicKeyToken="afb0a02973931d96" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-0.18.1.0" newVersion="0.18.1.0" />
</dependentAssembly>
</assemblyBinding></runtime> </assemblyBinding></runtime>
</configuration> </configuration>

View File

@ -45,6 +45,7 @@
<CreateDesktopShortcut>true</CreateDesktopShortcut> <CreateDesktopShortcut>true</CreateDesktopShortcut>
<PublishWizardCompleted>true</PublishWizardCompleted> <PublishWizardCompleted>true</PublishWizardCompleted>
<BootstrapperEnabled>true</BootstrapperEnabled> <BootstrapperEnabled>true</BootstrapperEnabled>
<HockeyAppResourceId>38ead84566f241de8f334abe173b1038</HockeyAppResourceId>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>x64</PlatformTarget> <PlatformTarget>x64</PlatformTarget>
@ -140,32 +141,23 @@
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="Caliburn.Micro, Version=3.0.3.0, Culture=neutral, PublicKeyToken=8e5891231f2ed21f, processorArchitecture=MSIL"> <Reference Include="Caliburn.Micro, Version=3.0.3.0, Culture=neutral, PublicKeyToken=8e5891231f2ed21f, processorArchitecture=MSIL">
<HintPath>..\packages\Caliburn.Micro.Core.3.0.3\lib\net45\Caliburn.Micro.dll</HintPath> <HintPath>..\packages\Caliburn.Micro.Core.3.1.0\lib\net45\Caliburn.Micro.dll</HintPath>
<Private>True</Private>
</Reference> </Reference>
<Reference Include="Caliburn.Micro.Platform, Version=3.0.3.0, Culture=neutral, PublicKeyToken=8e5891231f2ed21f, processorArchitecture=MSIL"> <Reference Include="Caliburn.Micro.Platform, Version=3.0.3.0, Culture=neutral, PublicKeyToken=8e5891231f2ed21f, processorArchitecture=MSIL">
<HintPath>..\packages\Caliburn.Micro.3.0.3\lib\net45\Caliburn.Micro.Platform.dll</HintPath> <HintPath>..\packages\Caliburn.Micro.3.1.0\lib\net45\Caliburn.Micro.Platform.dll</HintPath>
<Private>True</Private>
</Reference> </Reference>
<Reference Include="Caliburn.Micro.Platform.Core, Version=3.0.3.0, Culture=neutral, PublicKeyToken=8e5891231f2ed21f, processorArchitecture=MSIL"> <Reference Include="Caliburn.Micro.Platform.Core, Version=3.0.3.0, Culture=neutral, PublicKeyToken=8e5891231f2ed21f, processorArchitecture=MSIL">
<HintPath>..\packages\Caliburn.Micro.3.0.3\lib\net45\Caliburn.Micro.Platform.Core.dll</HintPath> <HintPath>..\packages\Caliburn.Micro.3.1.0\lib\net45\Caliburn.Micro.Platform.Core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Castle.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc, processorArchitecture=MSIL">
<HintPath>..\packages\Castle.Core.4.0.0\lib\net45\Castle.Core.dll</HintPath>
<Private>True</Private>
</Reference> </Reference>
<Reference Include="Corale.Colore, Version=5.1.0.0, Culture=neutral, processorArchitecture=MSIL"> <Reference Include="Corale.Colore, Version=5.1.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Colore.5.1.0\lib\net35\Corale.Colore.dll</HintPath> <HintPath>..\packages\Colore.5.1.0\lib\net35\Corale.Colore.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="CSCore, Version=1.1.6245.30570, Culture=neutral, PublicKeyToken=5a08f2b6f4415dea, processorArchitecture=MSIL"> <Reference Include="CSCore, Version=1.2.1.2, Culture=neutral, PublicKeyToken=5a08f2b6f4415dea, processorArchitecture=MSIL">
<HintPath>..\packages\CSCore.1.2.0\lib\net35-client\CSCore.dll</HintPath> <HintPath>..\packages\CSCore.1.2.1.2\lib\net35-client\CSCore.dll</HintPath>
<Private>True</Private>
</Reference> </Reference>
<Reference Include="CUE.NET, Version=1.1.3.0, Culture=neutral, processorArchitecture=MSIL"> <Reference Include="CUE.NET, Version=1.1.3.1, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\CUE.NET.1.1.3.0\lib\net45\CUE.NET.dll</HintPath> <HintPath>..\packages\CUE.NET.1.1.3.1\lib\net45\CUE.NET.dll</HintPath>
<Private>True</Private>
</Reference> </Reference>
<Reference Include="DeltaCompressionDotNet, Version=1.1.0.0, Culture=neutral, PublicKeyToken=1d14d6e5194e7f4a, processorArchitecture=MSIL"> <Reference Include="DeltaCompressionDotNet, Version=1.1.0.0, Culture=neutral, PublicKeyToken=1d14d6e5194e7f4a, processorArchitecture=MSIL">
<HintPath>..\packages\DeltaCompressionDotNet.1.1.0\lib\net20\DeltaCompressionDotNet.dll</HintPath> <HintPath>..\packages\DeltaCompressionDotNet.1.1.0\lib\net20\DeltaCompressionDotNet.dll</HintPath>
@ -179,9 +171,8 @@
<HintPath>..\packages\DeltaCompressionDotNet.1.1.0\lib\net20\DeltaCompressionDotNet.PatchApi.dll</HintPath> <HintPath>..\packages\DeltaCompressionDotNet.1.1.0\lib\net20\DeltaCompressionDotNet.PatchApi.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="DynamicExpresso.Core, Version=1.3.3.0, Culture=neutral, processorArchitecture=MSIL"> <Reference Include="DynamicExpresso.Core, Version=1.3.4.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\DynamicExpresso.Core.1.3.3.5\lib\net40\DynamicExpresso.Core.dll</HintPath> <HintPath>..\packages\DynamicExpresso.Core.1.3.4.7\lib\net40\DynamicExpresso.Core.dll</HintPath>
<Private>True</Private>
</Reference> </Reference>
<Reference Include="Gma.System.MouseKeyHook, Version=5.4.0.0, Culture=neutral, processorArchitecture=MSIL"> <Reference Include="Gma.System.MouseKeyHook, Version=5.4.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\MouseKeyHook.5.4.0\lib\net40\Gma.System.MouseKeyHook.dll</HintPath> <HintPath>..\packages\MouseKeyHook.5.4.0\lib\net40\Gma.System.MouseKeyHook.dll</HintPath>
@ -197,7 +188,6 @@
</Reference> </Reference>
<Reference Include="ICSharpCode.SharpZipLib, Version=0.86.0.518, Culture=neutral, processorArchitecture=MSIL"> <Reference Include="ICSharpCode.SharpZipLib, Version=0.86.0.518, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\squirrel.windows.1.4.4\lib\Net45\ICSharpCode.SharpZipLib.dll</HintPath> <HintPath>..\packages\squirrel.windows.1.4.4\lib\Net45\ICSharpCode.SharpZipLib.dll</HintPath>
<Private>True</Private>
</Reference> </Reference>
<Reference Include="log4net, Version=2.0.8.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a, processorArchitecture=MSIL"> <Reference Include="log4net, Version=2.0.8.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a, processorArchitecture=MSIL">
<HintPath>..\packages\log4net.2.0.8\lib\net45-full\log4net.dll</HintPath> <HintPath>..\packages\log4net.2.0.8\lib\net45-full\log4net.dll</HintPath>
@ -205,7 +195,9 @@
</Reference> </Reference>
<Reference Include="MahApps.Metro, Version=1.4.3.0, Culture=neutral, PublicKeyToken=f4fb5a3c4d1e5b4f, processorArchitecture=MSIL"> <Reference Include="MahApps.Metro, Version=1.4.3.0, Culture=neutral, PublicKeyToken=f4fb5a3c4d1e5b4f, processorArchitecture=MSIL">
<HintPath>..\packages\MahApps.Metro.1.4.3\lib\net45\MahApps.Metro.dll</HintPath> <HintPath>..\packages\MahApps.Metro.1.4.3\lib\net45\MahApps.Metro.dll</HintPath>
<Private>True</Private> </Reference>
<Reference Include="Microsoft.Win32.TaskScheduler, Version=2.6.5.0, Culture=neutral, PublicKeyToken=c416bc1b32d97233, processorArchitecture=MSIL">
<HintPath>..\packages\TaskScheduler.2.6.5\lib\net452\Microsoft.Win32.TaskScheduler.dll</HintPath>
</Reference> </Reference>
<Reference Include="Mono.Cecil, Version=0.9.6.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL"> <Reference Include="Mono.Cecil, Version=0.9.6.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
<HintPath>..\packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.dll</HintPath> <HintPath>..\packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.dll</HintPath>
@ -227,9 +219,8 @@
<HintPath>..\packages\MoonSharp.2.0.0.0\lib\net40-client\MoonSharp.Interpreter.dll</HintPath> <HintPath>..\packages\MoonSharp.2.0.0.0\lib\net40-client\MoonSharp.Interpreter.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL"> <Reference Include="Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll</HintPath> <HintPath>..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
<Private>True</Private>
</Reference> </Reference>
<Reference Include="Ninject, Version=3.2.0.0, Culture=neutral, PublicKeyToken=c7192dc5380945e7, processorArchitecture=MSIL"> <Reference Include="Ninject, Version=3.2.0.0, Culture=neutral, PublicKeyToken=c7192dc5380945e7, processorArchitecture=MSIL">
<HintPath>..\packages\Ninject.3.2.2.0\lib\net45-full\Ninject.dll</HintPath> <HintPath>..\packages\Ninject.3.2.2.0\lib\net45-full\Ninject.dll</HintPath>
@ -248,36 +239,42 @@
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL"> <Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.4.4\lib\net45\NLog.dll</HintPath> <HintPath>..\packages\NLog.4.4.12\lib\net45\NLog.dll</HintPath>
<Private>True</Private>
</Reference> </Reference>
<Reference Include="NuGet.Squirrel, Version=3.0.0.0, Culture=neutral, processorArchitecture=MSIL"> <Reference Include="NuGet.Squirrel, Version=3.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\squirrel.windows.1.4.4\lib\Net45\NuGet.Squirrel.dll</HintPath> <HintPath>..\packages\squirrel.windows.1.4.4\lib\Net45\NuGet.Squirrel.dll</HintPath>
<Private>True</Private> </Reference>
<Reference Include="PcapDotNet.Base, Version=1.0.4.25027, Culture=neutral, PublicKeyToken=06a20bc2fabb1931, processorArchitecture=MSIL">
<HintPath>..\packages\Pcap.Net.x64.1.0.4.1\lib\net45\PcapDotNet.Base.dll</HintPath>
</Reference>
<Reference Include="PcapDotNet.Core, Version=1.0.4.25149, Culture=neutral, PublicKeyToken=06a20bc2fabb1931, processorArchitecture=AMD64">
<HintPath>..\packages\Pcap.Net.x64.1.0.4.1\lib\net45\PcapDotNet.Core.dll</HintPath>
</Reference>
<Reference Include="PcapDotNet.Core.Extensions, Version=1.0.4.25151, Culture=neutral, PublicKeyToken=06a20bc2fabb1931, processorArchitecture=AMD64">
<HintPath>..\packages\Pcap.Net.x64.1.0.4.1\lib\net45\PcapDotNet.Core.Extensions.dll</HintPath>
</Reference>
<Reference Include="PcapDotNet.Packets, Version=1.0.4.25028, Culture=neutral, PublicKeyToken=06a20bc2fabb1931, processorArchitecture=MSIL">
<HintPath>..\packages\Pcap.Net.x64.1.0.4.1\lib\net45\PcapDotNet.Packets.dll</HintPath>
</Reference> </Reference>
<Reference Include="Process.NET, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL"> <Reference Include="Process.NET, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Process.NET.1.0.8\lib\Process.NET.dll</HintPath> <HintPath>..\packages\Process.NET.1.0.8\lib\Process.NET.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="SharpDX, Version=3.1.1.0, Culture=neutral, PublicKeyToken=b4dcf0f35e5521f1, processorArchitecture=MSIL"> <Reference Include="SharpDX, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b4dcf0f35e5521f1, processorArchitecture=MSIL">
<HintPath>..\packages\SharpDX.3.1.1\lib\net45\SharpDX.dll</HintPath> <HintPath>..\packages\SharpDX.4.0.1\lib\net45\SharpDX.dll</HintPath>
<Private>True</Private>
</Reference> </Reference>
<Reference Include="SharpDX.Direct3D9, Version=3.1.1.0, Culture=neutral, PublicKeyToken=b4dcf0f35e5521f1, processorArchitecture=MSIL"> <Reference Include="SharpDX.Direct3D9, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b4dcf0f35e5521f1, processorArchitecture=MSIL">
<HintPath>..\packages\SharpDX.Direct3D9.3.1.1\lib\net45\SharpDX.Direct3D9.dll</HintPath> <HintPath>..\packages\SharpDX.Direct3D9.4.0.1\lib\net45\SharpDX.Direct3D9.dll</HintPath>
<Private>True</Private>
</Reference> </Reference>
<Reference Include="Splat, Version=2.0.0.0, Culture=neutral, processorArchitecture=MSIL"> <Reference Include="Splat, Version=2.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Splat.2.0.0\lib\Net45\Splat.dll</HintPath> <HintPath>..\packages\Splat.2.0.0\lib\Net45\Splat.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="SpotifyAPI, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL"> <Reference Include="SpotifyAPI, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\SpotifyAPI-NET.2.13.1\lib\SpotifyAPI.dll</HintPath> <HintPath>..\packages\SpotifyAPI-NET.2.16.1\lib\SpotifyAPI.dll</HintPath>
<Private>True</Private>
</Reference> </Reference>
<Reference Include="Squirrel, Version=1.4.3.0, Culture=neutral, processorArchitecture=MSIL"> <Reference Include="Squirrel, Version=1.4.3.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\squirrel.windows.1.4.4\lib\Net45\Squirrel.dll</HintPath> <HintPath>..\packages\squirrel.windows.1.4.4\lib\Net45\Squirrel.dll</HintPath>
<Private>True</Private>
</Reference> </Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.ComponentModel.Composition" /> <Reference Include="System.ComponentModel.Composition" />
@ -291,7 +288,6 @@
<Reference Include="System.Windows.Forms" /> <Reference Include="System.Windows.Forms" />
<Reference Include="System.Windows.Interactivity, Version=4.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> <Reference Include="System.Windows.Interactivity, Version=4.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\MahApps.Metro.1.4.3\lib\net45\System.Windows.Interactivity.dll</HintPath> <HintPath>..\packages\MahApps.Metro.1.4.3\lib\net45\System.Windows.Interactivity.dll</HintPath>
<Private>True</Private>
</Reference> </Reference>
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
<Reference Include="Microsoft.CSharp" /> <Reference Include="Microsoft.CSharp" />
@ -340,6 +336,7 @@
<Compile Include="DeviceProviders\Logitech\G810.cs" /> <Compile Include="DeviceProviders\Logitech\G810.cs" />
<Compile Include="DeviceProviders\Logitech\LogitechGeneric.cs" /> <Compile Include="DeviceProviders\Logitech\LogitechGeneric.cs" />
<Compile Include="DeviceProviders\Logitech\LogitechKeyboard.cs" /> <Compile Include="DeviceProviders\Logitech\LogitechKeyboard.cs" />
<Compile Include="DeviceProviders\Logitech\Utilities\KeyMapG810.cs" />
<Compile Include="Dialogs\MarkdownDialog.xaml.cs"> <Compile Include="Dialogs\MarkdownDialog.xaml.cs">
<DependentUpon>MarkdownDialog.xaml</DependentUpon> <DependentUpon>MarkdownDialog.xaml</DependentUpon>
</Compile> </Compile>
@ -384,6 +381,13 @@
<DependentUpon>AssettoCorsaView.xaml</DependentUpon> <DependentUpon>AssettoCorsaView.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="Modules\Games\AssettoCorsa\AssettoCorsaViewModel.cs" /> <Compile Include="Modules\Games\AssettoCorsa\AssettoCorsaViewModel.cs" />
<Compile Include="Modules\Games\FormulaOne2017\FormulaOne2017Model.cs" />
<Compile Include="Modules\Games\FormulaOne2017\FormulaOne2017Settings.cs" />
<Compile Include="Modules\Games\FormulaOne2017\FormulaOne2017View.xaml.cs">
<DependentUpon>FormulaOne2017View.xaml</DependentUpon>
</Compile>
<Compile Include="Modules\Games\FormulaOne2017\FormulaOne2017ViewModel.cs" />
<Compile Include="Modules\Games\FormulaOne2017\FormulaOne2017DataModel.cs" />
<Compile Include="Modules\Games\Terraria\TerrariaDataModel.cs" /> <Compile Include="Modules\Games\Terraria\TerrariaDataModel.cs" />
<Compile Include="Modules\Games\Terraria\TerrariaSettings.cs" /> <Compile Include="Modules\Games\Terraria\TerrariaSettings.cs" />
<Compile Include="Modules\Games\Terraria\TerrariaModel.cs" /> <Compile Include="Modules\Games\Terraria\TerrariaModel.cs" />
@ -391,6 +395,13 @@
<DependentUpon>TerrariaView.xaml</DependentUpon> <DependentUpon>TerrariaView.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="Modules\Games\Terraria\TerrariaViewModel.cs" /> <Compile Include="Modules\Games\Terraria\TerrariaViewModel.cs" />
<Compile Include="Modules\Games\WoW\Models\WoWAura.cs" />
<Compile Include="Modules\Games\WoW\Models\WoWCastBar.cs" />
<Compile Include="Modules\Games\WoW\Models\WoWEnums.cs" />
<Compile Include="Modules\Games\WoW\Models\WoWSpecialization.cs" />
<Compile Include="Modules\Games\WoW\Models\WoWSpell.cs" />
<Compile Include="Modules\Games\WoW\Models\WoWUnit.cs" />
<Compile Include="Modules\Games\WoW\WoWPacketScanner.cs" />
<Compile Include="Modules\General\GeneralProfile\PerformanceInfo.cs" /> <Compile Include="Modules\General\GeneralProfile\PerformanceInfo.cs" />
<Compile Include="Modules\Games\EurotruckSimulator2\Data\Ets2TelemetryData.cs" /> <Compile Include="Modules\Games\EurotruckSimulator2\Data\Ets2TelemetryData.cs" />
<Compile Include="Modules\Games\EurotruckSimulator2\Data\Ets2TelemetryDataReader.cs" /> <Compile Include="Modules\Games\EurotruckSimulator2\Data\Ets2TelemetryDataReader.cs" />
@ -470,15 +481,6 @@
<DependentUpon>UnrealTournamentView.xaml</DependentUpon> <DependentUpon>UnrealTournamentView.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="Modules\Games\UnrealTournament\UnrealTournamentViewModel.cs" /> <Compile Include="Modules\Games\UnrealTournament\UnrealTournamentViewModel.cs" />
<Compile Include="Modules\Games\WoW\Data\Int128.cs" />
<Compile Include="Modules\Games\WoW\Data\WoWEnums.cs" />
<Compile Include="Modules\Games\WoW\Data\WoWOffsets.cs" />
<Compile Include="Modules\Games\WoW\Data\WoWStructs.cs" />
<Compile Include="Modules\Games\WoW\Data\WoWNameCache.cs" />
<Compile Include="Modules\Games\WoW\Data\WoWObjectManager.cs" />
<Compile Include="Modules\Games\WoW\Data\WoWObject.cs" />
<Compile Include="Modules\Games\WoW\Data\WoWPlayer.cs" />
<Compile Include="Modules\Games\WoW\Data\WoWUnit.cs" />
<Compile Include="Modules\Games\WoW\WoWView.xaml.cs"> <Compile Include="Modules\Games\WoW\WoWView.xaml.cs">
<DependentUpon>WoWView.xaml</DependentUpon> <DependentUpon>WoWView.xaml</DependentUpon>
</Compile> </Compile>
@ -486,7 +488,6 @@
<Compile Include="Modules\Games\WoW\WoWModel.cs" /> <Compile Include="Modules\Games\WoW\WoWModel.cs" />
<Compile Include="Modules\Games\WoW\WoWSettings.cs" /> <Compile Include="Modules\Games\WoW\WoWSettings.cs" />
<Compile Include="Modules\Games\WoW\WoWViewModel.cs" /> <Compile Include="Modules\Games\WoW\WoWViewModel.cs" />
<Compile Include="Modules\Games\WoW\WoWAddresses.cs" />
<Compile Include="Modules\Overlays\OverlayProfile\OverlayProfileDataModel.cs" /> <Compile Include="Modules\Overlays\OverlayProfile\OverlayProfileDataModel.cs" />
<Compile Include="Modules\Overlays\OverlayProfile\OverlayProfileModel.cs" /> <Compile Include="Modules\Overlays\OverlayProfile\OverlayProfileModel.cs" />
<Compile Include="Modules\Overlays\OverlayProfile\OverlayProfileSettings.cs" /> <Compile Include="Modules\Overlays\OverlayProfile\OverlayProfileSettings.cs" />
@ -573,6 +574,7 @@
<Compile Include="Profiles\Lua\Modules\Gui\LuaCheckBox.cs" /> <Compile Include="Profiles\Lua\Modules\Gui\LuaCheckBox.cs" />
<Compile Include="Profiles\Lua\Modules\Gui\LuaComboBox.cs" /> <Compile Include="Profiles\Lua\Modules\Gui\LuaComboBox.cs" />
<Compile Include="Profiles\Lua\Modules\Gui\LuaLabel.cs" /> <Compile Include="Profiles\Lua\Modules\Gui\LuaLabel.cs" />
<Compile Include="Profiles\Lua\Modules\Gui\LuaSlider.cs" />
<Compile Include="Profiles\Lua\Modules\Gui\LuaTextBox.cs" /> <Compile Include="Profiles\Lua\Modules\Gui\LuaTextBox.cs" />
<Compile Include="Profiles\Lua\Modules\Gui\LuaWindowView.xaml.cs"> <Compile Include="Profiles\Lua\Modules\Gui\LuaWindowView.xaml.cs">
<DependentUpon>LuaWindowView.xaml</DependentUpon> <DependentUpon>LuaWindowView.xaml</DependentUpon>
@ -801,6 +803,17 @@
<Compile Include="Properties\AssemblyInfo.cs"> <Compile Include="Properties\AssemblyInfo.cs">
<SubType>Code</SubType> <SubType>Code</SubType>
</Compile> </Compile>
<None Include="Modules\Games\WoW\Resources\Addon source\Artemis.toc" />
<None Include="Modules\Games\WoW\Resources\Addon source\Core.lua" />
<None Include="Modules\Games\WoW\Resources\Addon source\Libs\AceAddon-3.0\AceAddon-3.0.lua" />
<None Include="Modules\Games\WoW\Resources\Addon source\Libs\AceComm-3.0\AceComm-3.0.lua" />
<None Include="Modules\Games\WoW\Resources\Addon source\Libs\AceComm-3.0\ChatThrottleLib.lua" />
<None Include="Modules\Games\WoW\Resources\Addon source\Libs\AceConsole-3.0\AceConsole-3.0.lua" />
<None Include="Modules\Games\WoW\Resources\Addon source\Libs\AceEvent-3.0\AceEvent-3.0.lua" />
<None Include="Modules\Games\WoW\Resources\Addon source\Libs\AceTimer-3.0\AceTimer-3.0.lua" />
<None Include="Modules\Games\WoW\Resources\Addon source\Libs\json.lua" />
<None Include="Modules\Games\WoW\Resources\Addon source\Libs\LibStub\LibStub.lua" />
<None Include="Modules\Games\WoW\Resources\wow-addon.zip" />
<None Include="NLog.xsd"> <None Include="NLog.xsd">
<SubType>Designer</SubType> <SubType>Designer</SubType>
</None> </None>
@ -880,6 +893,10 @@
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType> <SubType>Designer</SubType>
</Page> </Page>
<Page Include="Modules\Games\FormulaOne2017\FormulaOne2017View.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Modules\Games\Terraria\TerrariaView.xaml"> <Page Include="Modules\Games\Terraria\TerrariaView.xaml">
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
@ -1075,6 +1092,13 @@
<None Include="Modules\Games\EurotruckSimulator2\Resources\Win64\ets2-telemetry-server.dll" /> <None Include="Modules\Games\EurotruckSimulator2\Resources\Win64\ets2-telemetry-server.dll" />
<None Include="Resources\audio.png" /> <None Include="Resources\audio.png" />
<None Include="Resources\ambilight.png" /> <None Include="Resources\ambilight.png" />
<None Include="Modules\Games\WoW\Resources\Addon source\embeds.xml" />
<None Include="Modules\Games\WoW\Resources\Addon source\Libs\AceAddon-3.0\AceAddon-3.0.xml" />
<None Include="Modules\Games\WoW\Resources\Addon source\Libs\AceComm-3.0\AceComm-3.0.xml" />
<None Include="Modules\Games\WoW\Resources\Addon source\Libs\AceConsole-3.0\AceConsole-3.0.xml" />
<None Include="Modules\Games\WoW\Resources\Addon source\Libs\AceEvent-3.0\AceEvent-3.0.xml" />
<None Include="Modules\Games\WoW\Resources\Addon source\Libs\AceTimer-3.0\AceTimer-3.0.xml" />
<Resource Include="Resources\Artemis autorun.xml" />
<Content Include="Resources\CounterStrike\csgoGamestateConfiguration.txt" /> <Content Include="Resources\CounterStrike\csgoGamestateConfiguration.txt" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -1096,15 +1120,16 @@
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="Modules\General\GeneralProfile\Discord\" />
<Folder Include="Resources\Lua\" /> <Folder Include="Resources\Lua\" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\packages\CUE.NET.1.1.3.0\build\net45\CUE.NET.targets" Condition="Exists('..\packages\CUE.NET.1.1.3.0\build\net45\CUE.NET.targets')" /> <Import Project="..\packages\CUE.NET.1.1.3.1\build\net45\CUE.NET.targets" Condition="Exists('..\packages\CUE.NET.1.1.3.1\build\net45\CUE.NET.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup> <PropertyGroup>
<ErrorText>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}.</ErrorText> <ErrorText>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}.</ErrorText>
</PropertyGroup> </PropertyGroup>
<Error Condition="!Exists('..\packages\CUE.NET.1.1.3.0\build\net45\CUE.NET.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\CUE.NET.1.1.3.0\build\net45\CUE.NET.targets'))" /> <Error Condition="!Exists('..\packages\CUE.NET.1.1.3.1\build\net45\CUE.NET.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\CUE.NET.1.1.3.1\build\net45\CUE.NET.targets'))" />
</Target> </Target>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets. Other similar extension points exist, see Microsoft.Common.targets.

View File

@ -1,8 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Input; using System.Windows.Input;
@ -18,8 +15,6 @@ using Artemis.ViewModels;
using Caliburn.Micro; using Caliburn.Micro;
using Newtonsoft.Json; using Newtonsoft.Json;
using Ninject; using Ninject;
using NLog;
using LogManager = NLog.LogManager;
namespace Artemis namespace Artemis
{ {
@ -41,7 +36,7 @@ namespace Artemis
AppDomain.CurrentDomain.UnhandledException += CurrentDomainOnUnhandledException; AppDomain.CurrentDomain.UnhandledException += CurrentDomainOnUnhandledException;
} }
private void CurrentDomainOnUnhandledException(object sender, UnhandledExceptionEventArgs unhandledExceptionEventArgs) private void CurrentDomainOnUnhandledException(object sender, UnhandledExceptionEventArgs unhandledExceptionEventArgs)
{ {
// Get rid of the keyboard hook in case of a crash, otherwise input freezes up system wide until Artemis is gone // Get rid of the keyboard hook in case of a crash, otherwise input freezes up system wide until Artemis is gone
@ -57,10 +52,10 @@ namespace Artemis
var e = ctx.EventArgs as MouseEventArgs; var e = ctx.EventArgs as MouseEventArgs;
// If there is an image control, get the scaled position // If there is an image control, get the scaled position
if ((img != null) && (e != null)) if (img != null && e != null)
{ {
var position = e.GetPosition(img); var position = e.GetPosition(img);
return (int) (img.Source.Width*(position.X/img.ActualWidth)); return (int) (img.Source.Width * (position.X / img.ActualWidth));
} }
// If there is another type of of IInputControl get the non-scaled position - or do some processing to get a scaled position, whatever needs to happen // If there is another type of of IInputControl get the non-scaled position - or do some processing to get a scaled position, whatever needs to happen
@ -77,14 +72,14 @@ namespace Artemis
var e = ctx.EventArgs as MouseEventArgs; var e = ctx.EventArgs as MouseEventArgs;
// If there is an image control, get the scaled position // If there is an image control, get the scaled position
if ((img != null) && (e != null)) if (img != null && e != null)
{ {
var position = e.GetPosition(img); var position = e.GetPosition(img);
return (int) (img.Source.Width*(position.Y/img.ActualWidth)); return (int) (img.Source.Width * (position.Y / img.ActualWidth));
} }
// If there is another type of of IInputControl get the non-scaled position - or do some processing to get a scaled position, whatever needs to happen // If there is another type of of IInputControl get the non-scaled position - or do some processing to get a scaled position, whatever needs to happen
if ((e != null) && (input != null)) if (e != null && input != null)
return e.GetPosition(input).Y; return e.GetPosition(input).Y;
// Return 0 if no processing could be done // Return 0 if no processing could be done
@ -94,14 +89,6 @@ namespace Artemis
protected override void Configure() protected override void Configure()
{ {
// Sleep for a while if ran from autorun to allow full system boot
if (Environment.GetCommandLineArgs().Contains("--autorun"))
{
var logger = LogManager.GetCurrentClassLogger();
logger.Info("Artemis was run using the autorun shortcut, sleeping for 15 sec.");
Thread.Sleep(15000);
}
_kernel = new StandardKernel(new BaseModules(), new ManagerModules()); _kernel = new StandardKernel(new BaseModules(), new ManagerModules());
_kernel.Bind<IWindowManager>().To<WindowManager>().InSingletonScope(); _kernel.Bind<IWindowManager>().To<WindowManager>().InSingletonScope();
@ -151,4 +138,4 @@ namespace Artemis
InputHook.Start(); InputHook.Start();
} }
} }
} }

View File

@ -102,12 +102,16 @@ namespace Artemis.DAL
if (string.IsNullOrEmpty(name)) if (string.IsNullOrEmpty(name))
return; return;
// Remove the old profile // Store the profile path before it is renamed
DeleteProfile(profile); var oldPath = ProfileFolder + $@"\{profile.KeyboardSlug}\{profile.GameName}\{profile.Slug}.json";
// Update the profile, creating a new file // Update the profile, creating a new file
profile.Name = name; profile.Name = name;
AddOrUpdate(profile); AddOrUpdate(profile);
// Remove the file with the old name
if (File.Exists(oldPath))
File.Delete(oldPath);
} }
public static void DeleteProfile(ProfileModel prof) public static void DeleteProfile(ProfileModel prof)
@ -163,7 +167,8 @@ namespace Artemis.DAL
var gifDir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + @"\Artemis\gifs"; var gifDir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + @"\Artemis\gifs";
Directory.CreateDirectory(gifDir); Directory.CreateDirectory(gifDir);
var gifPath = gifDir + $"\\{fileName}.gif"; var gifPath = gifDir + $"\\{fileName}.gif";
gifFile.Save(gifPath); if (!File.Exists(gifPath))
gifFile.Save(gifPath);
foreach (var profile in profiles) foreach (var profile in profiles)
{ {

View File

@ -23,9 +23,16 @@ namespace Artemis.DeviceProviders.Corsair
public override bool TryEnable() public override bool TryEnable()
{ {
CanUse = CanInitializeSdk(); try
if (CanUse && !CueSDK.IsInitialized) {
CueSDK.Initialize(); CanUse = CanInitializeSdk();
if (CanUse && !CueSDK.IsInitialized)
CueSDK.Initialize();
}
catch (Exception)
{
CanUse = false;
}
Logger.Debug("Attempted to enable Corsair headset. CanUse: {0}", CanUse); Logger.Debug("Attempted to enable Corsair headset. CanUse: {0}", CanUse);
@ -69,4 +76,4 @@ namespace Artemis.DeviceProviders.Corsair
return false; return false;
} }
} }
} }

View File

@ -128,6 +128,8 @@ namespace Artemis.DeviceProviders.Corsair
{ {
cueLed = _keyboard.Leds.FirstOrDefault(k => k.Id.ToString() == keyCode.ToString()) ?? cueLed = _keyboard.Leds.FirstOrDefault(k => k.Id.ToString() == keyCode.ToString()) ??
_keyboard.Leds.FirstOrDefault(k => k.Id == KeyMap.FormsKeys[keyCode]); _keyboard.Leds.FirstOrDefault(k => k.Id == KeyMap.FormsKeys[keyCode]);
Logger.Trace("Keycode: {0} resolved to CUE LED: {1}", keyCode, cueLed);
} }
catch (Exception) catch (Exception)
{ {

View File

@ -23,9 +23,16 @@ namespace Artemis.DeviceProviders.Corsair
public override bool TryEnable() public override bool TryEnable()
{ {
CanUse = CanInitializeSdk(); try
if (CanUse && !CueSDK.IsInitialized) {
CueSDK.Initialize(); CanUse = CanInitializeSdk();
if (CanUse && !CueSDK.IsInitialized)
CueSDK.Initialize();
}
catch (Exception)
{
CanUse = false;
}
Logger.Debug("Attempted to enable Corsair mice. CanUse: {0}", CanUse); Logger.Debug("Attempted to enable Corsair mice. CanUse: {0}", CanUse);

View File

@ -23,9 +23,16 @@ namespace Artemis.DeviceProviders.Corsair
public override bool TryEnable() public override bool TryEnable()
{ {
CanUse = CanInitializeSdk(); try
if (CanUse && !CueSDK.IsInitialized) {
CueSDK.Initialize(); CanUse = CanInitializeSdk();
if (CanUse && !CueSDK.IsInitialized)
CueSDK.Initialize();
}
catch (Exception)
{
CanUse = false;
}
Logger.Debug("Attempted to enable Corsair mousemat. CanUse: {0}", CanUse); Logger.Debug("Attempted to enable Corsair mousemat. CanUse: {0}", CanUse);

View File

@ -23,9 +23,9 @@ namespace Artemis.DeviceProviders.Corsair.Utilities
{Keys.Capital, CorsairLedId.CapsLock}, {Keys.Capital, CorsairLedId.CapsLock},
{Keys.Oem1, CorsairLedId.SemicolonAndColon}, {Keys.Oem1, CorsairLedId.SemicolonAndColon},
{Keys.Oem7, CorsairLedId.ApostropheAndDoubleQuote}, {Keys.Oem7, CorsairLedId.ApostropheAndDoubleQuote},
{Keys.OemBackslash, CorsairLedId.Backslash}, {Keys.OemBackslash, CorsairLedId.NonUsBackslash},
{Keys.LShiftKey, CorsairLedId.LeftShift}, {Keys.LShiftKey, CorsairLedId.LeftShift},
{Keys.Oem5, CorsairLedId.NonUsBackslash}, {Keys.Oem5, CorsairLedId.NonUsTilde},
{Keys.Oemcomma, CorsairLedId.CommaAndLessThan}, {Keys.Oemcomma, CorsairLedId.CommaAndLessThan},
{Keys.OemPeriod, CorsairLedId.PeriodAndBiggerThan}, {Keys.OemPeriod, CorsairLedId.PeriodAndBiggerThan},
{Keys.OemQuestion, CorsairLedId.SlashAndQuestionMark}, {Keys.OemQuestion, CorsairLedId.SlashAndQuestionMark},

View File

@ -1,4 +1,5 @@
using System.Linq; using System.Drawing;
using System.Linq;
using System.Windows; using System.Windows;
using System.Windows.Forms; using System.Windows.Forms;
using Artemis.DAL; using Artemis.DAL;
@ -20,23 +21,68 @@ namespace Artemis.DeviceProviders.Logitech
"Please check your cables and updating the Logitech Gaming Software\n" + "Please check your cables and updating the Logitech Gaming Software\n" +
"A minimum version of 8.81.15 is required.\n\n" + "A minimum version of 8.81.15 is required.\n\n" +
"If needed, you can select a different keyboard in Artemis under settings."; "If needed, you can select a different keyboard in Artemis under settings.";
Height = 6; Height = 7;
Width = 21; Width = 21;
PreviewSettings = new PreviewSettings(new Rect(19, 70, 1010, 269), Resources.g810); PreviewSettings = new PreviewSettings(new Rect(19, 36, 1010, 304), Resources.g810);
_generalSettings = SettingsProvider.Load<GeneralSettings>(); _generalSettings = SettingsProvider.Load<GeneralSettings>();
} }
/// <summary>
/// The G910 also updates the G-logo, G-badge and G-keys
/// </summary>
/// <param name="bitmap"></param>
public override void DrawBitmap(Bitmap bitmap)
{
using (var croppedBitmap = new Bitmap(21 * 4, 6 * 4))
{
// Deal with non-standard DPI
croppedBitmap.SetResolution(96, 96);
// Don't forget that the image is upscaled 4 times
using (var g = Graphics.FromImage(croppedBitmap))
{
g.DrawImage(bitmap, new Rectangle(0, 0, 84, 24), new Rectangle(4, 4, 84, 24), GraphicsUnit.Pixel);
}
LogitechGSDK.LogiLedSetTargetDevice(LogitechGSDK.LOGI_DEVICETYPE_PERKEY_RGB);
// TODO: Remapping
LogitechGSDK.LogiLedSetLightingFromBitmap(OrionUtilities.BitmapToByteArray(bitmap));
}
using (var resized = OrionUtilities.ResizeImage(bitmap, 21, 7))
{
// Color G-logo, lets also try some other values to see what happens
SetLogitechColorFromCoordinates(resized, KeyboardNames.G_LOGO, 0, 0);
SetLogitechColorFromCoordinates(resized, KeyboardNames.G_BADGE, 0, 0);
SetLogitechColorFromCoordinates(resized, KeyboardNames.G_1, 0, 0);
SetLogitechColorFromCoordinates(resized, KeyboardNames.G_2, 0, 0);
SetLogitechColorFromCoordinates(resized, KeyboardNames.G_3, 0, 0);
SetLogitechColorFromCoordinates(resized, KeyboardNames.G_4, 0, 0);
SetLogitechColorFromCoordinates(resized, KeyboardNames.G_5, 0, 0);
SetLogitechColorFromCoordinates(resized, KeyboardNames.G_6, 0, 0);
SetLogitechColorFromCoordinates(resized, KeyboardNames.G_7, 0, 0);
SetLogitechColorFromCoordinates(resized, KeyboardNames.G_8, 0, 0);
SetLogitechColorFromCoordinates(resized, KeyboardNames.G_9, 0, 0);
}
}
public override KeyMatch? GetKeyPosition(Keys keyCode) public override KeyMatch? GetKeyPosition(Keys keyCode)
{ {
KeyMatch value;
switch (_generalSettings.Layout) switch (_generalSettings.Layout)
{ {
case "Qwerty": case "Qwerty":
return KeyMap.QwertyLayout.FirstOrDefault(k => k.KeyCode == keyCode); value = KeyMap.QwertyLayout.FirstOrDefault(k => k.KeyCode == keyCode);
break;
case "Qwertz": case "Qwertz":
return KeyMap.QwertzLayout.FirstOrDefault(k => k.KeyCode == keyCode); value = KeyMap.QwertzLayout.FirstOrDefault(k => k.KeyCode == keyCode);
break;
default: default:
return KeyMap.AzertyLayout.FirstOrDefault(k => k.KeyCode == keyCode); value = KeyMap.AzertyLayout.FirstOrDefault(k => k.KeyCode == keyCode);
break;
} }
// Adjust the distance by 1 on y for the G810
return new KeyMatch(value.KeyCode, value.X, value.Y + 1);
} }
} }
} }

View File

@ -64,7 +64,8 @@ namespace Artemis.DeviceProviders.Logitech
g.DrawImage(bitmap, new Rectangle(0, 0, 84, 24), new Rectangle(4, 4, 84, 24), GraphicsUnit.Pixel); g.DrawImage(bitmap, new Rectangle(0, 0, 84, 24), new Rectangle(4, 4, 84, 24), GraphicsUnit.Pixel);
} }
base.DrawBitmap(croppedBitmap); LogitechGSDK.LogiLedSetTargetDevice(LogitechGSDK.LOGI_DEVICETYPE_PERKEY_RGB);
LogitechGSDK.LogiLedSetLightingFromBitmap(OrionUtilities.BitmapToByteArray(bitmap, G910Keymappings));
} }
using (var resized = OrionUtilities.ResizeImage(bitmap, 22, 7)) using (var resized = OrionUtilities.ResizeImage(bitmap, 22, 7))
@ -88,14 +89,147 @@ namespace Artemis.DeviceProviders.Logitech
} }
} }
private void SetLogitechColorFromCoordinates(Bitmap bitmap, KeyboardNames key, int x, int y) // These mappings are used by the G910 to fix alignments
public static OrionUtilities.KeyMapping[] G910Keymappings =
{ {
var color = bitmap.GetPixel(x, y); // First row
var rPer = (int) Math.Round(color.R / 2.55); new OrionUtilities.KeyMapping(0, 0),
var gPer = (int) Math.Round(color.G / 2.55); new OrionUtilities.KeyMapping(1, 1),
var bPer = (int) Math.Round(color.B / 2.55); new OrionUtilities.KeyMapping(2, 1),
new OrionUtilities.KeyMapping(3, 2),
new OrionUtilities.KeyMapping(4, 3),
new OrionUtilities.KeyMapping(5, 4),
new OrionUtilities.KeyMapping(6, 5),
new OrionUtilities.KeyMapping(7, 6),
new OrionUtilities.KeyMapping(8, 7),
new OrionUtilities.KeyMapping(9, 8),
new OrionUtilities.KeyMapping(10, 9),
new OrionUtilities.KeyMapping(11, 9),
new OrionUtilities.KeyMapping(12, 10),
new OrionUtilities.KeyMapping(13, 11),
new OrionUtilities.KeyMapping(13, 12),
new OrionUtilities.KeyMapping(14, 13),
new OrionUtilities.KeyMapping(15, 14),
new OrionUtilities.KeyMapping(16, 15),
new OrionUtilities.KeyMapping(17, 16),
new OrionUtilities.KeyMapping(18, 17),
new OrionUtilities.KeyMapping(19, 18),
LogitechGSDK.LogiLedSetLightingForKeyWithKeyName(key, rPer, gPer, bPer); // Second row
} new OrionUtilities.KeyMapping(21, 21),
new OrionUtilities.KeyMapping(22, 22),
new OrionUtilities.KeyMapping(23, 23),
new OrionUtilities.KeyMapping(24, 24),
new OrionUtilities.KeyMapping(25, 25),
new OrionUtilities.KeyMapping(26, 26),
new OrionUtilities.KeyMapping(27, 27),
new OrionUtilities.KeyMapping(28, 28),
new OrionUtilities.KeyMapping(29, 29),
new OrionUtilities.KeyMapping(30, 30),
new OrionUtilities.KeyMapping(31, 31),
new OrionUtilities.KeyMapping(32, 32),
new OrionUtilities.KeyMapping(33, 33),
new OrionUtilities.KeyMapping(34, 34),
new OrionUtilities.KeyMapping(35, 35),
new OrionUtilities.KeyMapping(36, 36),
new OrionUtilities.KeyMapping(37, 37),
new OrionUtilities.KeyMapping(38, 38),
new OrionUtilities.KeyMapping(39, 39),
new OrionUtilities.KeyMapping(40, 40),
new OrionUtilities.KeyMapping(41, 41),
// Third row
new OrionUtilities.KeyMapping(42, 42),
new OrionUtilities.KeyMapping(43, 43),
new OrionUtilities.KeyMapping(44, 44),
new OrionUtilities.KeyMapping(45, 45),
new OrionUtilities.KeyMapping(46, 46),
new OrionUtilities.KeyMapping(47, 46),
new OrionUtilities.KeyMapping(48, 47),
new OrionUtilities.KeyMapping(49, 48),
new OrionUtilities.KeyMapping(50, 49),
new OrionUtilities.KeyMapping(51, 50),
new OrionUtilities.KeyMapping(52, 51),
new OrionUtilities.KeyMapping(53, 52),
new OrionUtilities.KeyMapping(54, 53),
new OrionUtilities.KeyMapping(54, 54),
new OrionUtilities.KeyMapping(55, 55),
new OrionUtilities.KeyMapping(56, 56),
new OrionUtilities.KeyMapping(57, 57),
new OrionUtilities.KeyMapping(58, 58),
new OrionUtilities.KeyMapping(59, 59),
new OrionUtilities.KeyMapping(60, 60),
new OrionUtilities.KeyMapping(61, 61),
new OrionUtilities.KeyMapping(62, 62),
// Fourth row
new OrionUtilities.KeyMapping(63, 63),
new OrionUtilities.KeyMapping(64, 64),
new OrionUtilities.KeyMapping(65, 65),
new OrionUtilities.KeyMapping(66, 65),
new OrionUtilities.KeyMapping(67, 66),
new OrionUtilities.KeyMapping(68, 67),
new OrionUtilities.KeyMapping(69, 68),
new OrionUtilities.KeyMapping(70, 69),
new OrionUtilities.KeyMapping(71, 70),
new OrionUtilities.KeyMapping(72, 71),
new OrionUtilities.KeyMapping(73, 72),
new OrionUtilities.KeyMapping(74, 73),
new OrionUtilities.KeyMapping(75, 74),
new OrionUtilities.KeyMapping(76, 75),
new OrionUtilities.KeyMapping(76, 76),
new OrionUtilities.KeyMapping(78, 77),
new OrionUtilities.KeyMapping(79, 78),
new OrionUtilities.KeyMapping(79, 79),
new OrionUtilities.KeyMapping(80, 80),
new OrionUtilities.KeyMapping(81, 81),
new OrionUtilities.KeyMapping(82, 82),
// Fifth row
new OrionUtilities.KeyMapping(84, 84),
new OrionUtilities.KeyMapping(85, 85),
new OrionUtilities.KeyMapping(86, 86),
new OrionUtilities.KeyMapping(87, 87),
new OrionUtilities.KeyMapping(88, 88),
new OrionUtilities.KeyMapping(89, 89),
new OrionUtilities.KeyMapping(90, 90),
new OrionUtilities.KeyMapping(91, 91),
new OrionUtilities.KeyMapping(92, 92),
new OrionUtilities.KeyMapping(93, 93),
new OrionUtilities.KeyMapping(94, 94),
new OrionUtilities.KeyMapping(95, 95),
new OrionUtilities.KeyMapping(96, 96),
new OrionUtilities.KeyMapping(97, 97),
new OrionUtilities.KeyMapping(98, 98),
new OrionUtilities.KeyMapping(99, 99),
new OrionUtilities.KeyMapping(100, 100),
new OrionUtilities.KeyMapping(101, 101),
new OrionUtilities.KeyMapping(102, 102),
new OrionUtilities.KeyMapping(103, 103),
new OrionUtilities.KeyMapping(104, 104),
// Sixth row
new OrionUtilities.KeyMapping(105, 105),
new OrionUtilities.KeyMapping(106, 106),
new OrionUtilities.KeyMapping(107, 107),
new OrionUtilities.KeyMapping(108, 107),
new OrionUtilities.KeyMapping(109, 109),
new OrionUtilities.KeyMapping(110, 110),
new OrionUtilities.KeyMapping(111, 110),
new OrionUtilities.KeyMapping(112, 111),
new OrionUtilities.KeyMapping(113, 112),
new OrionUtilities.KeyMapping(114, 113),
new OrionUtilities.KeyMapping(115, 114),
new OrionUtilities.KeyMapping(116, 115),
new OrionUtilities.KeyMapping(115, 116), // ALTGR
new OrionUtilities.KeyMapping(116, 117),
new OrionUtilities.KeyMapping(117, 118),
new OrionUtilities.KeyMapping(118, 119),
new OrionUtilities.KeyMapping(119, 120),
new OrionUtilities.KeyMapping(120, 121),
new OrionUtilities.KeyMapping(121, 122),
new OrionUtilities.KeyMapping(122, 123),
new OrionUtilities.KeyMapping(124, 124)
};
} }
} }

View File

@ -1,55 +1,55 @@
using System; //using System;
using System.Drawing; //using System.Drawing;
using Artemis.DeviceProviders.Logitech.Utilities; //using Artemis.DeviceProviders.Logitech.Utilities;
using Ninject.Extensions.Logging; //using Ninject.Extensions.Logging;
//
namespace Artemis.DeviceProviders.Logitech //namespace Artemis.DeviceProviders.Logitech
{ //{
// TODO: Handle shutdown, maybe implement Disable() afterall? // // TODO: Handle shutdown, maybe implement Disable() afterall?
public class LogitechGeneric : DeviceProvider // public class LogitechGeneric : DeviceProvider
{ // {
/// <summary> // /// <summary>
/// A generic Logitech DeviceProvider. Because the Logitech SDK currently doesn't allow specific // /// A generic Logitech DeviceProvider. Because the Logitech SDK currently doesn't allow specific
/// device targeting (only very broad per-key-RGB and full RGB etc..) // /// device targeting (only very broad per-key-RGB and full RGB etc..)
/// </summary> // /// </summary>
public LogitechGeneric(ILogger logger) // public LogitechGeneric(ILogger logger)
{ // {
Logger = logger; // Logger = logger;
Type = DeviceType.Generic; // Type = DeviceType.Generic;
} // }
//
public ILogger Logger { get; set; } // public ILogger Logger { get; set; }
//
public override void UpdateDevice(Bitmap bitmap) // public override void UpdateDevice(Bitmap bitmap)
{ // {
if (!CanUse || bitmap == null) // if (!CanUse || bitmap == null)
return; // return;
//
var col = bitmap.GetPixel(bitmap.Width/2, bitmap.Height/2); // var col = bitmap.GetPixel(bitmap.Width/2, bitmap.Height/2);
LogitechGSDK.LogiLedSetTargetDevice(LogitechGSDK.LOGI_DEVICETYPE_RGB); // LogitechGSDK.LogiLedSetTargetDevice(LogitechGSDK.LOGI_DEVICETYPE_RGB);
LogitechGSDK.LogiLedSetLighting((int) (col.R/2.55), (int) (col.G/2.55), (int) (col.B/2.55)); // LogitechGSDK.LogiLedSetLighting((int) (col.R/2.55), (int) (col.G/2.55), (int) (col.B/2.55));
} // }
//
public override bool TryEnable() // public override bool TryEnable()
{ // {
var majorNum = 0; // var majorNum = 0;
var minorNum = 0; // var minorNum = 0;
var buildNum = 0; // var buildNum = 0;
//
LogitechGSDK.LogiLedInit(); // LogitechGSDK.LogiLedInit();
LogitechGSDK.LogiLedGetSdkVersion(ref majorNum, ref minorNum, ref buildNum); // LogitechGSDK.LogiLedGetSdkVersion(ref majorNum, ref minorNum, ref buildNum);
//
// Turn it into one long number... // // Turn it into one long number...
var version = int.Parse($"{majorNum}{minorNum}{buildNum}"); // var version = int.Parse($"{majorNum}{minorNum}{buildNum}");
CanUse = version >= 88115; // CanUse = version >= 88115;
Logger.Debug("Attempted to enable Logitech generic device. CanUse: {0}", CanUse); // Logger.Debug("Attempted to enable Logitech generic device. CanUse: {0}", CanUse);
//
return CanUse; // return CanUse;
} // }
//
public override void Disable() // public override void Disable()
{ // {
throw new NotSupportedException("Can only disable a keyboard"); // throw new NotSupportedException("Can only disable a keyboard");
} // }
} // }
} //}

View File

@ -1,4 +1,5 @@
using System.Drawing; using System;
using System.Drawing;
using System.Threading; using System.Threading;
using Artemis.DeviceProviders.Logitech.Utilities; using Artemis.DeviceProviders.Logitech.Utilities;
using Artemis.Utilities.DataReaders; using Artemis.Utilities.DataReaders;
@ -12,18 +13,7 @@ namespace Artemis.DeviceProviders.Logitech
{ {
// Just to be sure, restore the Logitech DLL registry key // Just to be sure, restore the Logitech DLL registry key
DllManager.RestoreLogitechDll(); DllManager.RestoreLogitechDll();
// Check to see if VC++ 2012 x64 is installed.
if (Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Classes\Installer\Dependencies\{ca67548a-5ebe-413a-b50c-4b9ceb6d66c6}") == null)
{
CantEnableText = "Couldn't connect to your Logitech keyboard.\n" +
"The Visual C++ 2012 Redistributable v11.0.61030.0 could not be found, which is required.\n" +
"Please download it by going to the following URL (link also in wiki):\n\n" +
"https://download.microsoft.com/download/1/6/B/16B06F60-3B20-4FF2-B699-5E9B7962F9AE/VSU_4/vcredist_x64.exe";
return false;
}
int majorNum = 0, minorNum = 0, buildNum = 0; int majorNum = 0, minorNum = 0, buildNum = 0;
LogitechGSDK.LogiLedInit(); LogitechGSDK.LogiLedInit();
@ -58,10 +48,14 @@ namespace Artemis.DeviceProviders.Logitech
LogitechGSDK.LogiLedShutdown(); LogitechGSDK.LogiLedShutdown();
} }
public override void DrawBitmap(Bitmap bitmap) protected void SetLogitechColorFromCoordinates(Bitmap bitmap, KeyboardNames key, int x, int y)
{ {
LogitechGSDK.LogiLedSetTargetDevice(LogitechGSDK.LOGI_DEVICETYPE_PERKEY_RGB); var color = bitmap.GetPixel(x, y);
LogitechGSDK.LogiLedSetLightingFromBitmap(OrionUtilities.BitmapToByteArray(bitmap)); var rPer = (int)Math.Round(color.R / 2.55);
var gPer = (int)Math.Round(color.G / 2.55);
var bPer = (int)Math.Round(color.B / 2.55);
LogitechGSDK.LogiLedSetLightingForKeyWithKeyName(key, rPer, gPer, bPer);
} }
} }
} }

View File

@ -132,7 +132,7 @@ namespace Artemis.DeviceProviders.Logitech.Utilities
new KeyMatch(Keys.Decimal, 19, 5) new KeyMatch(Keys.Decimal, 19, 5)
}; };
#endregion #endregion
#region Qwertz #region Qwertz
@ -257,7 +257,7 @@ namespace Artemis.DeviceProviders.Logitech.Utilities
new KeyMatch(Keys.Decimal, 19, 5) new KeyMatch(Keys.Decimal, 19, 5)
}; };
#endregion #endregion
#region Azerty #region Azerty
@ -384,9 +384,9 @@ namespace Artemis.DeviceProviders.Logitech.Utilities
#endregion #endregion
} }
public static List<KeyMatch> QwertyLayout { get; set; } public static List<KeyMatch> QwertyLayout { get; set; }
public static List<KeyMatch> QwertzLayout { get; set; } public static List<KeyMatch> QwertzLayout { get; set; }
public static List<KeyMatch> AzertyLayout { get; set; } public static List<KeyMatch> AzertyLayout { get; set; }
} }
} }

View File

@ -0,0 +1,392 @@
using System.Collections.Generic;
using System.Windows.Forms;
namespace Artemis.DeviceProviders.Logitech.Utilities
{
public static class KeyMapG810
{
static KeyMapG810()
{
// There are several keyboard layouts
#region Qwerty
QwertyLayout = new List<KeyMatch>
{
// Row 1
new KeyMatch(Keys.Escape, 0, 0),
new KeyMatch(Keys.F1, 2, 0),
new KeyMatch(Keys.F2, 3, 0),
new KeyMatch(Keys.F3, 4, 0),
new KeyMatch(Keys.F4, 5, 0),
new KeyMatch(Keys.F5, 6, 0),
new KeyMatch(Keys.F6, 7, 0),
new KeyMatch(Keys.F7, 8, 0),
new KeyMatch(Keys.F8, 9, 0),
new KeyMatch(Keys.F9, 11, 0),
new KeyMatch(Keys.F10, 12, 0),
new KeyMatch(Keys.F11, 13, 0),
new KeyMatch(Keys.F12, 14, 0),
new KeyMatch(Keys.PrintScreen, 15, 0),
new KeyMatch(Keys.Scroll, 16, 0),
new KeyMatch(Keys.Pause, 17, 0),
// Row 2
new KeyMatch(Keys.Oemtilde, 0, 1),
new KeyMatch(Keys.D1, 1, 1),
new KeyMatch(Keys.D2, 2, 1),
new KeyMatch(Keys.D3, 3, 1),
new KeyMatch(Keys.D4, 4, 1),
new KeyMatch(Keys.D5, 5, 1),
new KeyMatch(Keys.D6, 6, 1),
new KeyMatch(Keys.D7, 7, 1),
new KeyMatch(Keys.D8, 8, 1),
new KeyMatch(Keys.D9, 9, 1),
new KeyMatch(Keys.D0, 10, 1),
new KeyMatch(Keys.OemMinus, 11, 1),
new KeyMatch(Keys.Oemplus, 12, 1),
new KeyMatch(Keys.Back, 13, 1),
new KeyMatch(Keys.Insert, 14, 1),
new KeyMatch(Keys.Home, 15, 1),
new KeyMatch(Keys.PageUp, 16, 1),
new KeyMatch(Keys.NumLock, 17, 1),
new KeyMatch(Keys.Divide, 18, 1),
new KeyMatch(Keys.Multiply, 19, 1),
new KeyMatch(Keys.Subtract, 20, 1),
// Row 3
new KeyMatch(Keys.Tab, 0, 2),
new KeyMatch(Keys.Q, 1, 2),
new KeyMatch(Keys.W, 2, 2),
new KeyMatch(Keys.E, 3, 2),
new KeyMatch(Keys.R, 5, 2),
new KeyMatch(Keys.T, 6, 2),
new KeyMatch(Keys.Y, 7, 2),
new KeyMatch(Keys.U, 8, 2),
new KeyMatch(Keys.I, 9, 2),
new KeyMatch(Keys.O, 10, 2),
new KeyMatch(Keys.P, 11, 2),
new KeyMatch(Keys.OemOpenBrackets, 12, 2),
new KeyMatch(Keys.Oem6, 13, 2),
new KeyMatch(Keys.Delete, 14, 2),
new KeyMatch(Keys.End, 15, 2),
new KeyMatch(Keys.Next, 16, 2),
new KeyMatch(Keys.NumPad7, 17, 2),
new KeyMatch(Keys.NumPad8, 18, 2),
new KeyMatch(Keys.NumPad9, 19, 2),
new KeyMatch(Keys.Add, 20, 2),
// Row 4
new KeyMatch(Keys.Capital, 0, 3),
new KeyMatch(Keys.A, 1, 3),
new KeyMatch(Keys.S, 3, 3),
new KeyMatch(Keys.D, 4, 3),
new KeyMatch(Keys.F, 5, 3),
new KeyMatch(Keys.G, 6, 3),
new KeyMatch(Keys.H, 7, 3),
new KeyMatch(Keys.J, 8, 3),
new KeyMatch(Keys.K, 9, 3),
new KeyMatch(Keys.L, 10, 3),
new KeyMatch(Keys.Oem1, 11, 3),
new KeyMatch(Keys.Oem7, 12, 3),
new KeyMatch(Keys.Oem5, 13, 3),
new KeyMatch(Keys.Return, 14, 3),
new KeyMatch(Keys.NumPad4, 17, 3),
new KeyMatch(Keys.NumPad5, 18, 3),
new KeyMatch(Keys.NumPad6, 19, 3),
// Row 5
new KeyMatch(Keys.LShiftKey, 1, 4),
new KeyMatch(Keys.OemBackslash, 2, 4),
new KeyMatch(Keys.Z, 2, 4),
new KeyMatch(Keys.X, 3, 4),
new KeyMatch(Keys.C, 4, 4),
new KeyMatch(Keys.V, 5, 4),
new KeyMatch(Keys.B, 6, 4),
new KeyMatch(Keys.N, 7, 4),
new KeyMatch(Keys.M, 8, 4),
new KeyMatch(Keys.Oemcomma, 9, 4),
new KeyMatch(Keys.OemPeriod, 10, 4),
new KeyMatch(Keys.OemQuestion, 11, 4),
new KeyMatch(Keys.RShiftKey, 13, 4),
new KeyMatch(Keys.Up, 15, 4),
new KeyMatch(Keys.NumPad1, 17, 4),
new KeyMatch(Keys.NumPad2, 18, 4),
new KeyMatch(Keys.NumPad3, 19, 4),
// Both returns return "Return" (Yes...)
// new OrionKey(System.Windows.Forms.Keys.Return, 20, 4),
// Row 6
new KeyMatch(Keys.LControlKey, 0, 5),
new KeyMatch(Keys.LWin, 1, 5),
new KeyMatch(Keys.LMenu, 3, 5),
new KeyMatch(Keys.Space, 6, 5),
new KeyMatch(Keys.RMenu, 11, 5),
new KeyMatch(Keys.RWin, 12, 5),
new KeyMatch(Keys.Apps, 13, 5),
new KeyMatch(Keys.RControlKey, 14, 5),
new KeyMatch(Keys.Left, 15, 5),
new KeyMatch(Keys.Down, 16, 5),
new KeyMatch(Keys.Right, 17, 5),
new KeyMatch(Keys.NumPad0, 18, 5),
new KeyMatch(Keys.Decimal, 19, 5)
};
#endregion
#region Qwertz
QwertzLayout = new List<KeyMatch>
{
// Row 1
new KeyMatch(Keys.Escape, 0, 0),
new KeyMatch(Keys.F1, 2, 0),
new KeyMatch(Keys.F2, 3, 0),
new KeyMatch(Keys.F3, 4, 0),
new KeyMatch(Keys.F4, 5, 0),
new KeyMatch(Keys.F5, 6, 0),
new KeyMatch(Keys.F6, 7, 0),
new KeyMatch(Keys.F7, 8, 0),
new KeyMatch(Keys.F8, 9, 0),
new KeyMatch(Keys.F9, 11, 0),
new KeyMatch(Keys.F10, 12, 0), // returns 'None'
new KeyMatch(Keys.F11, 13, 0),
new KeyMatch(Keys.F12, 14, 0),
new KeyMatch(Keys.PrintScreen, 15, 0),
new KeyMatch(Keys.Scroll, 16, 0),
new KeyMatch(Keys.Pause, 17, 0),
// Row 2
new KeyMatch(Keys.Oem5, 0, 1),
new KeyMatch(Keys.D1, 1, 1),
new KeyMatch(Keys.D2, 2, 1),
new KeyMatch(Keys.D3, 3, 1),
new KeyMatch(Keys.D4, 4, 1),
new KeyMatch(Keys.D5, 5, 1),
new KeyMatch(Keys.D6, 6, 1),
new KeyMatch(Keys.D7, 7, 1),
new KeyMatch(Keys.D8, 8, 1),
new KeyMatch(Keys.D9, 9, 1),
new KeyMatch(Keys.D0, 10, 1),
new KeyMatch(Keys.OemOpenBrackets, 11, 1),
new KeyMatch(Keys.Oem6, 12, 1),
new KeyMatch(Keys.Back, 13, 1),
new KeyMatch(Keys.Insert, 14, 1),
new KeyMatch(Keys.Home, 15, 1),
new KeyMatch(Keys.PageUp, 16, 1),
new KeyMatch(Keys.NumLock, 17, 1),
new KeyMatch(Keys.Divide, 18, 1),
new KeyMatch(Keys.Multiply, 19, 1),
new KeyMatch(Keys.Subtract, 20, 1),
// Row 3
new KeyMatch(Keys.Tab, 0, 2),
new KeyMatch(Keys.Q, 1, 2),
new KeyMatch(Keys.W, 2, 2),
new KeyMatch(Keys.E, 3, 2),
new KeyMatch(Keys.R, 5, 2),
new KeyMatch(Keys.T, 6, 2),
new KeyMatch(Keys.Z, 7, 2),
new KeyMatch(Keys.U, 8, 2),
new KeyMatch(Keys.I, 9, 2),
new KeyMatch(Keys.O, 10, 2),
new KeyMatch(Keys.P, 11, 2),
new KeyMatch(Keys.Oem1, 12, 2),
new KeyMatch(Keys.Oemplus, 13, 2),
new KeyMatch(Keys.Delete, 14, 2),
new KeyMatch(Keys.End, 15, 2),
new KeyMatch(Keys.Next, 16, 2),
new KeyMatch(Keys.NumPad7, 17, 2),
new KeyMatch(Keys.NumPad8, 18, 2),
new KeyMatch(Keys.NumPad9, 19, 2),
new KeyMatch(Keys.Add, 20, 2),
// Row 4
new KeyMatch(Keys.Capital, 0, 3),
new KeyMatch(Keys.A, 1, 3),
new KeyMatch(Keys.S, 3, 3),
new KeyMatch(Keys.D, 4, 3),
new KeyMatch(Keys.F, 5, 3),
new KeyMatch(Keys.G, 6, 3),
new KeyMatch(Keys.H, 7, 3),
new KeyMatch(Keys.J, 8, 3),
new KeyMatch(Keys.K, 9, 3),
new KeyMatch(Keys.L, 10, 3),
new KeyMatch(Keys.Oemtilde, 11, 3),
new KeyMatch(Keys.Oem7, 12, 3),
new KeyMatch(Keys.OemQuestion, 13, 3),
new KeyMatch(Keys.Return, 14, 3),
new KeyMatch(Keys.NumPad4, 17, 3),
new KeyMatch(Keys.NumPad5, 18, 3),
new KeyMatch(Keys.NumPad6, 19, 3),
// Row 5
new KeyMatch(Keys.LShiftKey, 1, 4),
new KeyMatch(Keys.OemBackslash, 2, 4),
new KeyMatch(Keys.Y, 2, 4),
new KeyMatch(Keys.X, 3, 4),
new KeyMatch(Keys.C, 4, 4),
new KeyMatch(Keys.V, 5, 4),
new KeyMatch(Keys.B, 6, 4),
new KeyMatch(Keys.N, 7, 4),
new KeyMatch(Keys.M, 8, 4),
new KeyMatch(Keys.Oemcomma, 9, 4),
new KeyMatch(Keys.OemPeriod, 10, 4),
new KeyMatch(Keys.OemMinus, 11, 4),
new KeyMatch(Keys.RShiftKey, 13, 4),
new KeyMatch(Keys.Up, 15, 4),
new KeyMatch(Keys.NumPad1, 17, 4),
new KeyMatch(Keys.NumPad2, 18, 4),
new KeyMatch(Keys.NumPad3, 19, 4),
// Both returns return "Return" (Yes...)
// new OrionKey(System.Windows.Forms.Keys.Return, 20, 4),
// Row 6
new KeyMatch(Keys.LControlKey, 0, 5),
new KeyMatch(Keys.LWin, 1, 5),
new KeyMatch(Keys.Menu, 3, 5), // returns 'None'
new KeyMatch(Keys.Space, 6, 5),
new KeyMatch(Keys.RMenu, 11, 5),
new KeyMatch(Keys.RWin, 12, 5),
new KeyMatch(Keys.Apps, 13, 5),
new KeyMatch(Keys.RControlKey, 14, 5),
new KeyMatch(Keys.Left, 15, 5),
new KeyMatch(Keys.Down, 16, 5),
new KeyMatch(Keys.Right, 17, 5),
new KeyMatch(Keys.NumPad0, 18, 5),
new KeyMatch(Keys.Decimal, 19, 5)
};
#endregion
#region Azerty
AzertyLayout = new List<KeyMatch>
{
// Row 1
new KeyMatch(Keys.Escape, 0, 0),
new KeyMatch(Keys.F1, 2, 0),
new KeyMatch(Keys.F2, 3, 0),
new KeyMatch(Keys.F3, 4, 0),
new KeyMatch(Keys.F4, 5, 0),
new KeyMatch(Keys.F5, 6, 0),
new KeyMatch(Keys.F6, 7, 0),
new KeyMatch(Keys.F7, 8, 0),
new KeyMatch(Keys.F8, 9, 0),
new KeyMatch(Keys.F9, 11, 0),
new KeyMatch(Keys.F10, 12, 0),
new KeyMatch(Keys.F11, 13, 0),
new KeyMatch(Keys.F12, 14, 0),
new KeyMatch(Keys.PrintScreen, 15, 0),
new KeyMatch(Keys.Scroll, 16, 0),
new KeyMatch(Keys.Pause, 17, 0),
// Row 2
new KeyMatch(Keys.Oemtilde, 0, 1),
new KeyMatch(Keys.D1, 1, 1),
new KeyMatch(Keys.D2, 2, 1),
new KeyMatch(Keys.D3, 3, 1),
new KeyMatch(Keys.D4, 4, 1),
new KeyMatch(Keys.D5, 5, 1),
new KeyMatch(Keys.D6, 6, 1),
new KeyMatch(Keys.D7, 7, 1),
new KeyMatch(Keys.D8, 8, 1),
new KeyMatch(Keys.D9, 9, 1),
new KeyMatch(Keys.D0, 10, 1),
new KeyMatch(Keys.OemMinus, 11, 1),
new KeyMatch(Keys.Oemplus, 12, 1),
new KeyMatch(Keys.Back, 13, 1),
new KeyMatch(Keys.Insert, 14, 1),
new KeyMatch(Keys.Home, 15, 1),
new KeyMatch(Keys.PageUp, 16, 1),
new KeyMatch(Keys.NumLock, 17, 1),
new KeyMatch(Keys.Divide, 18, 1),
new KeyMatch(Keys.Multiply, 19, 1),
new KeyMatch(Keys.Subtract, 20, 1),
// Row 3
new KeyMatch(Keys.Tab, 0, 2),
new KeyMatch(Keys.A, 1, 2),
new KeyMatch(Keys.Z, 2, 2),
new KeyMatch(Keys.E, 3, 2),
new KeyMatch(Keys.R, 5, 2),
new KeyMatch(Keys.T, 6, 2),
new KeyMatch(Keys.Y, 7, 2),
new KeyMatch(Keys.U, 8, 2),
new KeyMatch(Keys.I, 9, 2),
new KeyMatch(Keys.O, 10, 2),
new KeyMatch(Keys.P, 11, 2),
new KeyMatch(Keys.OemQuotes, 12, 2),
new KeyMatch(Keys.Oem6, 13, 2),
new KeyMatch(Keys.Delete, 14, 2),
new KeyMatch(Keys.End, 15, 2),
new KeyMatch(Keys.Next, 16, 2),
new KeyMatch(Keys.NumPad7, 17, 2),
new KeyMatch(Keys.NumPad8, 18, 2),
new KeyMatch(Keys.NumPad9, 19, 2),
new KeyMatch(Keys.Add, 20, 2),
// Row 4
new KeyMatch(Keys.Capital, 0, 3),
new KeyMatch(Keys.Q, 1, 3),
new KeyMatch(Keys.S, 3, 3),
new KeyMatch(Keys.D, 4, 3),
new KeyMatch(Keys.F, 5, 3),
new KeyMatch(Keys.G, 6, 3),
new KeyMatch(Keys.H, 7, 3),
new KeyMatch(Keys.J, 8, 3),
new KeyMatch(Keys.K, 9, 3),
new KeyMatch(Keys.L, 10, 3),
new KeyMatch(Keys.M, 11, 3),
new KeyMatch(Keys.Oem7, 12, 3),
new KeyMatch(Keys.Oem5, 13, 3),
new KeyMatch(Keys.Return, 14, 3),
new KeyMatch(Keys.NumPad4, 17, 3),
new KeyMatch(Keys.NumPad5, 18, 3),
new KeyMatch(Keys.NumPad6, 19, 3),
// Row 5
new KeyMatch(Keys.LShiftKey, 1, 4),
new KeyMatch(Keys.OemBackslash, 2, 4),
new KeyMatch(Keys.W, 2, 4),
new KeyMatch(Keys.X, 3, 4),
new KeyMatch(Keys.C, 4, 4),
new KeyMatch(Keys.V, 5, 4),
new KeyMatch(Keys.B, 6, 4),
new KeyMatch(Keys.N, 7, 4),
new KeyMatch(Keys.OemQuestion, 8, 4),
new KeyMatch(Keys.Oemcomma, 9, 4),
new KeyMatch(Keys.OemPeriod, 10, 4),
new KeyMatch(Keys.OemQuestion, 11, 4),
new KeyMatch(Keys.RShiftKey, 13, 4),
new KeyMatch(Keys.Up, 15, 4),
new KeyMatch(Keys.NumPad1, 17, 4),
new KeyMatch(Keys.NumPad2, 18, 4),
new KeyMatch(Keys.NumPad3, 19, 4),
// Both returns return "Return" (Yes...)
// new OrionKey(System.Windows.Forms.Keys.Return, 20, 4),
// Row 6
new KeyMatch(Keys.LControlKey, 0, 5),
new KeyMatch(Keys.LWin, 1, 5),
new KeyMatch(Keys.LMenu, 3, 5),
new KeyMatch(Keys.Space, 6, 5),
new KeyMatch(Keys.RMenu, 11, 5),
new KeyMatch(Keys.RWin, 12, 5),
new KeyMatch(Keys.Apps, 13, 5),
new KeyMatch(Keys.RControlKey, 14, 5),
new KeyMatch(Keys.Left, 15, 5),
new KeyMatch(Keys.Down, 16, 5),
new KeyMatch(Keys.Right, 17, 5),
new KeyMatch(Keys.NumPad0, 18, 5),
new KeyMatch(Keys.Decimal, 19, 5)
};
#endregion
}
public static List<KeyMatch> QwertyLayout { get; set; }
public static List<KeyMatch> QwertzLayout { get; set; }
public static List<KeyMatch> AzertyLayout { get; set; }
}
}

View File

@ -7,149 +7,7 @@ namespace Artemis.DeviceProviders.Logitech.Utilities
{ {
public static class OrionUtilities public static class OrionUtilities
{ {
public static KeyMapping[] Keymappings = public static byte[] BitmapToByteArray(Bitmap b, KeyMapping[] keymappings = null)
{
// First row
new KeyMapping(0, 0),
new KeyMapping(1, 1),
new KeyMapping(2, 1),
new KeyMapping(3, 2),
new KeyMapping(4, 3),
new KeyMapping(5, 4),
new KeyMapping(6, 5),
new KeyMapping(7, 6),
new KeyMapping(8, 7),
new KeyMapping(9, 8),
new KeyMapping(10, 9),
new KeyMapping(11, 9),
new KeyMapping(12, 10),
new KeyMapping(13, 11),
new KeyMapping(13, 12),
new KeyMapping(14, 13),
new KeyMapping(15, 14),
new KeyMapping(16, 15),
new KeyMapping(17, 16),
new KeyMapping(18, 17),
new KeyMapping(19, 18),
// Second row
new KeyMapping(21, 21),
new KeyMapping(22, 22),
new KeyMapping(23, 23),
new KeyMapping(24, 24),
new KeyMapping(25, 25),
new KeyMapping(26, 26),
new KeyMapping(27, 27),
new KeyMapping(28, 28),
new KeyMapping(29, 29),
new KeyMapping(30, 30),
new KeyMapping(31, 31),
new KeyMapping(32, 32),
new KeyMapping(33, 33),
new KeyMapping(34, 34),
new KeyMapping(35, 35),
new KeyMapping(36, 36),
new KeyMapping(37, 37),
new KeyMapping(38, 38),
new KeyMapping(39, 39),
new KeyMapping(40, 40),
new KeyMapping(41, 41),
// Third row
new KeyMapping(42, 42),
new KeyMapping(43, 43),
new KeyMapping(44, 44),
new KeyMapping(45, 45),
new KeyMapping(46, 46),
new KeyMapping(47, 46),
new KeyMapping(48, 47),
new KeyMapping(49, 48),
new KeyMapping(50, 49),
new KeyMapping(51, 50),
new KeyMapping(52, 51),
new KeyMapping(53, 52),
new KeyMapping(54, 53),
new KeyMapping(54, 54),
new KeyMapping(55, 55),
new KeyMapping(56, 56),
new KeyMapping(57, 57),
new KeyMapping(58, 58),
new KeyMapping(59, 59),
new KeyMapping(60, 60),
new KeyMapping(61, 61),
new KeyMapping(62, 62),
// Fourth row
new KeyMapping(63, 63),
new KeyMapping(64, 64),
new KeyMapping(65, 65),
new KeyMapping(66, 65),
new KeyMapping(67, 66),
new KeyMapping(68, 67),
new KeyMapping(69, 68),
new KeyMapping(70, 69),
new KeyMapping(71, 70),
new KeyMapping(72, 71),
new KeyMapping(73, 72),
new KeyMapping(74, 73),
new KeyMapping(75, 74),
new KeyMapping(76, 75),
new KeyMapping(76, 76),
new KeyMapping(78, 77),
new KeyMapping(79, 78),
new KeyMapping(79, 79),
new KeyMapping(80, 80),
new KeyMapping(81, 81),
new KeyMapping(82, 82),
// Fifth row
new KeyMapping(84, 84),
new KeyMapping(85, 85),
new KeyMapping(86, 86),
new KeyMapping(87, 87),
new KeyMapping(88, 88),
new KeyMapping(89, 89),
new KeyMapping(90, 90),
new KeyMapping(91, 91),
new KeyMapping(92, 92),
new KeyMapping(93, 93),
new KeyMapping(94, 94),
new KeyMapping(95, 95),
new KeyMapping(96, 96),
new KeyMapping(97, 97),
new KeyMapping(98, 98),
new KeyMapping(99, 99),
new KeyMapping(100, 100),
new KeyMapping(101, 101),
new KeyMapping(102, 102),
new KeyMapping(103, 103),
new KeyMapping(104, 104),
// Sixth row
new KeyMapping(105, 105),
new KeyMapping(106, 106),
new KeyMapping(107, 107),
new KeyMapping(108, 107),
new KeyMapping(109, 109),
new KeyMapping(110, 110),
new KeyMapping(111, 110),
new KeyMapping(112, 111),
new KeyMapping(113, 112),
new KeyMapping(114, 113),
new KeyMapping(115, 114),
new KeyMapping(116, 115),
new KeyMapping(115, 116), // ALTGR
new KeyMapping(116, 117),
new KeyMapping(117, 118),
new KeyMapping(118, 119),
new KeyMapping(119, 120),
new KeyMapping(120, 121),
new KeyMapping(121, 122),
new KeyMapping(122, 123),
new KeyMapping(124, 124)
};
public static byte[] BitmapToByteArray(Bitmap b, bool remap = true)
{ {
if (b.Width > 21 || b.Height > 6) if (b.Width > 21 || b.Height > 6)
b = ResizeImage(b, 21, 6); b = ResizeImage(b, 21, 6);
@ -158,23 +16,23 @@ namespace Artemis.DeviceProviders.Logitech.Utilities
var bitmapData = b.LockBits(rect, ImageLockMode.ReadWrite, b.PixelFormat); var bitmapData = b.LockBits(rect, ImageLockMode.ReadWrite, b.PixelFormat);
var depth = Image.GetPixelFormatSize(b.PixelFormat); var depth = Image.GetPixelFormatSize(b.PixelFormat);
var step = depth/8; var step = depth / 8;
var pixels = new byte[21*6*step]; var pixels = new byte[21 * 6 * step];
var iptr = bitmapData.Scan0; var iptr = bitmapData.Scan0;
// Copy data from pointer to array // Copy data from pointer to array
Marshal.Copy(iptr, pixels, 0, pixels.Length); Marshal.Copy(iptr, pixels, 0, pixels.Length);
if (!remap) if (keymappings == null)
return pixels; return pixels;
var remapped = new byte[pixels.Length]; var remapped = new byte[pixels.Length];
// Every key is 4 bytes // Every key is 4 bytes
for (var i = 0; i <= pixels.Length/4; i++) for (var i = 0; i <= pixels.Length / 4; i++)
{ {
var firstSByte = Keymappings[i].Source*4; var firstSByte = keymappings[i].Source * 4;
var firstTByte = Keymappings[i].Target*4; var firstTByte = keymappings[i].Target * 4;
for (var j = 0; j < 4; j++) for (var j = 0; j < 4; j++)
remapped[firstTByte + j] = pixels[firstSByte + j]; remapped[firstTByte + j] = pixels[firstSByte + j];
@ -234,4 +92,4 @@ namespace Artemis.DeviceProviders.Logitech.Utilities
public int Target { get; set; } public int Target { get; set; }
} }
} }
} }

View File

@ -2,6 +2,7 @@
using Artemis.Managers; using Artemis.Managers;
using Artemis.Models; using Artemis.Models;
using Artemis.Modules.Abstract; using Artemis.Modules.Abstract;
using Artemis.Modules.Games.WoW;
using Artemis.Profiles.Layers.Abstract; using Artemis.Profiles.Layers.Abstract;
using Artemis.Profiles.Layers.Interfaces; using Artemis.Profiles.Layers.Interfaces;
using Artemis.Profiles.Layers.Types.Audio.AudioCapturing; using Artemis.Profiles.Layers.Types.Audio.AudioCapturing;
@ -65,7 +66,7 @@ namespace Artemis.InjectionModules
#endregion #endregion
#region Effects #region Modules
Kernel.Bind(x => Kernel.Bind(x =>
x.FromThisAssembly() x.FromThisAssembly()
@ -81,6 +82,7 @@ namespace Artemis.InjectionModules
.BindAllBaseClasses() .BindAllBaseClasses()
.Configure(b => b.InSingletonScope()) .Configure(b => b.InSingletonScope())
); );
Bind<WowPacketScanner>().ToSelf();
#endregion #endregion

View File

@ -16,9 +16,12 @@ namespace Artemis.Managers
{ {
private readonly DebugViewModel _debugViewModel; private readonly DebugViewModel _debugViewModel;
private readonly DeviceManager _deviceManager; private readonly DeviceManager _deviceManager;
private readonly ILogger _logger; private readonly ILogger _logger;
//private readonly Timer _loopTimer; //private readonly Timer _loopTimer;
private readonly Task _loopTask; private readonly Task _loopTask;
private readonly ModuleManager _moduleManager; private readonly ModuleManager _moduleManager;
public LoopManager(ILogger logger, ModuleManager moduleManager, DeviceManager deviceManager, public LoopManager(ILogger logger, ModuleManager moduleManager, DeviceManager deviceManager,
@ -163,9 +166,7 @@ namespace Artemis.Managers
var keyboardOnly = !mice.Any() && !headsets.Any() && !generics.Any() && !mousemats.Any(); var keyboardOnly = !mice.Any() && !headsets.Any() && !generics.Any() && !mousemats.Any();
// Setup the frame for this tick // Setup the frame for this tick
using ( using (var frame = new FrameModel(_deviceManager.ActiveKeyboard, mice.Any(), headsets.Any(), generics.Any(), mousemats.Any()))
var frame = new FrameModel(_deviceManager.ActiveKeyboard, mice.Any(), headsets.Any(), generics.Any(),
mousemats.Any()))
{ {
if (renderModule.IsInitialized) if (renderModule.IsInitialized)
renderModule.Render(frame, keyboardOnly); renderModule.Render(frame, keyboardOnly);
@ -207,4 +208,4 @@ namespace Artemis.Managers
RenderCompleted?.Invoke(this, EventArgs.Empty); RenderCompleted?.Invoke(this, EventArgs.Empty);
} }
} }
} }

View File

@ -7,7 +7,6 @@ using Artemis.Profiles;
using Artemis.Profiles.Lua; using Artemis.Profiles.Lua;
using Artemis.Profiles.Lua.Modules; using Artemis.Profiles.Lua.Modules;
using Artemis.Profiles.Lua.Modules.Gui; using Artemis.Profiles.Lua.Modules.Gui;
using Castle.Core.Internal;
using MoonSharp.Interpreter; using MoonSharp.Interpreter;
using Ninject; using Ninject;
using Ninject.Extensions.Logging; using Ninject.Extensions.Logging;
@ -64,7 +63,7 @@ namespace Artemis.Managers
LuaScript.Globals[luaModule.ModuleName] = luaModule; LuaScript.Globals[luaModule.ModuleName] = luaModule;
// If there is no LUA script, don't bother executing the string // If there is no LUA script, don't bother executing the string
if (ProfileModel.LuaScript.IsNullOrEmpty()) if (string.IsNullOrEmpty(ProfileModel.LuaScript))
return; return;
try try

View File

@ -24,10 +24,7 @@ namespace Artemis.Managers
Modules = new List<ModuleModel>(moduleModels.Where(m => !m.IsOverlay && !m.IsBoundToProcess)); Modules = new List<ModuleModel>(moduleModels.Where(m => !m.IsOverlay && !m.IsBoundToProcess));
OverlayModules = new List<ModuleModel>(moduleModels.Where(m => m.IsOverlay)); OverlayModules = new List<ModuleModel>(moduleModels.Where(m => m.IsOverlay));
// Exclude WoW if needed ProcessModules = new List<ModuleModel>(moduleModels.Where(m => m.IsBoundToProcess));
ProcessModules = _generalSettings.GamestatePort == 62575
? new List<ModuleModel>(moduleModels.Where(m => m.IsBoundToProcess))
: new List<ModuleModel>(moduleModels.Where(m => m.IsBoundToProcess && m.Name != "WoW"));
_logger.Info("Intialized ModuleManager"); _logger.Info("Intialized ModuleManager");
} }

View File

@ -54,8 +54,7 @@ namespace Artemis.Models
using (var g = Graphics.FromImage(bitmap)) using (var g = Graphics.FromImage(bitmap))
{ {
g.Clear(Color.Black); g.Clear(Color.Black);
g.DrawImage(frame, new Rectangle(0, 0, bitmap.Width, bitmap.Height), RelativeRectangle, g.DrawImage(frame, new Rectangle(0, 0, bitmap.Width, bitmap.Height), RelativeRectangle, GraphicsUnit.Pixel);
GraphicsUnit.Pixel);
} }
return bitmap; return bitmap;
@ -72,4 +71,4 @@ namespace Artemis.Models
_ctx = null; _ctx = null;
} }
} }
} }

View File

@ -141,15 +141,22 @@ namespace Artemis.Models
public async Task RenameProfile(ProfileModel profileModel) public async Task RenameProfile(ProfileModel profileModel)
{ {
// Store the old name
var oldName = profileModel.Name;
var name = await GetValidProfileName("Rename profile", "Please enter a unique new profile name"); var name = await GetValidProfileName("Rename profile", "Please enter a unique new profile name");
// User cancelled // User cancelled
if (name == null) if (name == null)
return; return;
// MakeProfileUnique does a check but also modifies the profile, set the old name back
var doRename = await MakeProfileUnique(profileModel, name, profileModel.Name); var doRename = await MakeProfileUnique(profileModel, name, profileModel.Name);
var newName = profileModel.Name;
profileModel.Name = oldName;
if (!doRename) if (!doRename)
return; return;
ProfileProvider.RenameProfile(profileModel, profileModel.Name); ProfileProvider.RenameProfile(profileModel, newName);
} }
public async Task<ProfileModel> DuplicateProfile(ProfileModel selectedProfile) public async Task<ProfileModel> DuplicateProfile(ProfileModel selectedProfile)
@ -170,21 +177,13 @@ namespace Artemis.Models
return newProfile; return newProfile;
} }
public async Task<bool> DeleteProfile(ProfileModel selectedProfile, ModuleModel moduleModel) public async Task<bool> ConfirmDeleteProfile(ProfileModel selectedProfile, ModuleModel moduleModel)
{ {
var confirm = await _dialogService.ShowQuestionMessageBox("Delete profile", var confirm = await _dialogService.ShowQuestionMessageBox("Delete profile",
$"Are you sure you want to delete the profile named: {selectedProfile.Name}?\n\n" + $"Are you sure you want to delete the profile named: {selectedProfile.Name}?\n\n" +
"This cannot be undone."); "This cannot be undone.");
if (!confirm.Value)
return false;
var defaultProfile = ProfileProvider.GetProfile(_deviceManager.ActiveKeyboard, moduleModel, "Default"); return confirm.Value;
var deleteProfile = selectedProfile;
moduleModel.ChangeProfile(defaultProfile);
ProfileProvider.DeleteProfile(deleteProfile);
return true;
} }
public async Task<ProfileModel> ImportProfile(ModuleModel moduleModel) public async Task<ProfileModel> ImportProfile(ModuleModel moduleModel)

View File

@ -1,12 +1,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Windows;
using Artemis.DAL; using Artemis.DAL;
using Artemis.Events; using Artemis.Events;
using Artemis.Managers; using Artemis.Managers;
using Artemis.Models; using Artemis.Models;
using Artemis.Profiles; using Artemis.Profiles;
using Artemis.Profiles.Layers.Interfaces;
using Artemis.Profiles.Layers.Models; using Artemis.Profiles.Layers.Models;
using Newtonsoft.Json; using Newtonsoft.Json;
using Ninject; using Ninject;
@ -96,10 +94,12 @@ namespace Artemis.Modules.Abstract
public void ChangeProfile(ProfileModel profileModel) public void ChangeProfile(ProfileModel profileModel)
{ {
if (!IsInitialized || Equals(profileModel, ProfileModel)) if (!IsInitialized || Equals(ProfileModel, profileModel))
return; return;
ProfileModel?.Deactivate(_luaManager);
ProfileModel = profileModel; ProfileModel = profileModel;
if (!IsOverlay) if (!IsOverlay)
ProfileModel?.Activate(_luaManager); ProfileModel?.Activate(_luaManager);
if (ProfileModel != null) if (ProfileModel != null)
@ -131,7 +131,7 @@ namespace Artemis.Modules.Abstract
ChangeToLastProfile(); ChangeToLastProfile();
} }
private void ChangeToLastProfile() public void ChangeToLastProfile()
{ {
var profileName = !string.IsNullOrEmpty(Settings?.LastProfile) ? Settings.LastProfile : "Default"; var profileName = !string.IsNullOrEmpty(Settings?.LastProfile) ? Settings.LastProfile : "Default";
@ -203,4 +203,4 @@ namespace Artemis.Modules.Abstract
#endregion #endregion
} }
} }

View File

@ -51,6 +51,8 @@ namespace Artemis.Modules.Games.CounterStrike
public class Round public class Round
{ {
public string phase { get; set; } public string phase { get; set; }
public string bomb { get; set; }
public string win_team { get; set; }
} }
[MoonSharpUserData] [MoonSharpUserData]

View File

@ -0,0 +1,309 @@
using System;
using System.Runtime.InteropServices;
using Artemis.Modules.Abstract;
using MoonSharp.Interpreter;
namespace Artemis.Modules.Games.FormulaOne2017
{
[MoonSharpUserData]
public class FormulaOne2017DataModel : ModuleDataModel
{
public FormulaOne2017DataModel()
{
Car = new Car();
Session = new Session();
}
public Car Car { get; set; }
public Session Session { get; set; }
#region Native structs
[StructLayout(LayoutKind.Sequential)]
public struct UdpPacketData
{
public float m_time;
public float m_lapTime;
public float m_lapDistance;
public float m_totalDistance;
public float m_x; // World space position
public float m_y; // World space position
public float m_z; // World space position
public float m_speed; // Speed of car in MPH
public float m_xv; // Velocity in world space
public float m_yv; // Velocity in world space
public float m_zv; // Velocity in world space
public float m_xr; // World space right direction
public float m_yr; // World space right direction
public float m_zr; // World space right direction
public float m_xd; // World space forward direction
public float m_yd; // World space forward direction
public float m_zd; // World space forward direction
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public float[] m_susp_pos; // Note: All wheel arrays have the order:
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public float[] m_susp_vel; // RL, RR, FL, FR
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public float[] m_wheel_speed;
public float m_throttle;
public float m_steer;
public float m_brake;
public float m_clutch;
public float m_gear;
public float m_gforce_lat;
public float m_gforce_lon;
public float m_lap;
public float m_engineRate;
public float m_sli_pro_native_support; // SLI Pro support
public float m_car_position; // car race position
public float m_kers_level; // kers energy left
public float m_kers_max_level; // kers maximum energy
public float m_drs; // 0 = off, 1 = on
public float m_traction_control; // 0 (off) - 2 (high)
public float m_anti_lock_brakes; // 0 (off) - 1 (on)
public float m_fuel_in_tank; // current fuel mass
public float m_fuel_capacity; // fuel capacity
public float m_in_pits; // 0 = none, 1 = pitting, 2 = in pit area
public float m_sector; // 0 = sector1, 1 = sector2, 2 = sector3
public float m_sector1_time; // time of sector1 (or 0)
public float m_sector2_time; // time of sector2 (or 0)
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public float[] m_brakes_temp; // brakes temperature (centigrade)
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public float[] m_tyres_pressure; // tyres pressure PSI
public float m_team_info; // team ID
public float m_total_laps; // total number of laps in this race
public float m_track_size; // track size meters
public float m_last_lap_time; // last lap time
public float m_max_rpm; // cars max RPM, at which point the rev limiter will kick in
public float m_idle_rpm; // cars idle RPM
public float m_max_gears; // maximum number of gears
public float m_sessionType; // 0 = unknown, 1 = practice, 2 = qualifying, 3 = race
public float m_drsAllowed; // 0 = not allowed, 1 = allowed, -1 = invalid / unknown
public float m_track_number; // -1 for unknown, 0-21 for tracks
public float m_vehicleFIAFlags; // -1 = invalid/unknown, 0 = none, 1 = green, 2 = blue, 3 = yellow, 4 = red
public float m_era; // era, 2017 (modern) or 1980 (classic)
public float m_engine_temperature; // engine temperature (centigrade)
public float m_gforce_vert; // vertical g-force component
public float m_ang_vel_x; // angular velocity x-component
public float m_ang_vel_y; // angular velocity y-component
public float m_ang_vel_z; // angular velocity z-component
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public byte[] m_tyres_temperature; // tyres temperature (centigrade)
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public byte[] m_tyres_wear; // tyre wear percentage
public byte m_tyre_compound; // compound of tyre 0 = ultra soft, 1 = super soft, 2 = soft, 3 = medium, 4 = hard, 5 = inter, 6 = wet
public byte m_front_brake_bias; // front brake bias (percentage)
public byte m_fuel_mix; // fuel mix - 0 = lean, 1 = standard, 2 = rich, 3 = max
public byte m_currentLapInvalid; // current lap invalid - 0 = valid, 1 = invalid
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public byte[] m_tyres_damage; // tyre damage (percentage)
public byte m_front_left_wing_damage; // front left wing damage (percentage)
public byte m_front_right_wing_damage; // front right wing damage (percentage)
public byte m_rear_wing_damage; // rear wing damage (percentage)
public byte m_engine_damage; // engine damage (percentage)
public byte m_gear_box_damage; // gear box damage (percentage)
public byte m_exhaust_damage; // exhaust damage (percentage)
public byte m_pit_limiter_status; // pit limiter status 0 = off, 1 = on
public byte m_pit_speed_limit; // pit speed limit in mph
public float m_session_time_left; // NEW: time left in session in seconds
public byte m_rev_lights_percent; // NEW: rev lights indicator (percentage)
public byte m_is_spectating; // NEW: whether the player is spectating
public byte m_spectator_car_index; // NEW: index of the car being spectated
// Car data
public byte m_num_cars; // number of cars in data
public byte m_player_car_index; // index of player's car in the array
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
public CarUDPData[] m_car_data; // data for all cars on track
public float m_yaw; // NEW (v1.8)
public float m_pitch; // NEW (v1.8)
public float m_roll; // NEW (v1.8)
public float m_x_local_velocity; // NEW (v1.8) Velocity in local space
public float m_y_local_velocity; // NEW (v1.8) Velocity in local space
public float m_z_local_velocity; // NEW (v1.8) Velocity in local space
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public float[] m_susp_acceleration; // NEW (v1.8) RL, RR, FL, FR
public float m_ang_acc_x; // NEW (v1.8) angular acceleration x-component
public float m_ang_acc_y; // NEW (v1.8) angular acceleration y-component
public float m_ang_acc_z; // NEW (v1.8) angular acceleration z-component
}
public struct CarUDPData
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
public float[] m_worldPosition; // world co-ordinates of vehicle
public float m_lastLapTime;
public float m_currentLapTime;
public float m_bestLapTime;
public float m_sector1Time;
public float m_sector2Time;
public float m_lapDistance;
public byte m_driverId;
public byte m_teamId;
public byte m_carPosition; // UPDATED: track positions of vehicle
public byte m_currentLapNum;
public byte m_tyreCompound; // compound of tyre 0 = ultra soft, 1 = super soft, 2 = soft, 3 = medium, 4 = hard, 5 = inter, 6 = wet
public byte m_inPits; // 0 = none, 1 = pitting, 2 = in pit area
public byte m_sector; // 0 = sector1, 1 = sector2, 2 = sector3
public byte m_currentLapInvalid; // current lap invalid - 0 = valid, 1 = invalid
public byte m_penalties; // NEW: accumulated time penalties in seconds to be added
};
#endregion
}
[MoonSharpUserData]
public class Car
{
public Car()
{
Overview = new CarOverview();
Details = new CarDetails();
}
public CarOverview Overview { get; set; }
public CarDetails Details { get; set; }
public double SpeedKph { get; set; }
public double SpeedMph { get; set; }
public float Steering { get; set; }
public float Throttle { get; set; }
public float Brake { get; set; }
public float Clutch { get; set; }
public bool Drs { get; set; }
}
[MoonSharpUserData]
public class CarDetails
{
public float Rpm { get; set; }
public float MaxRpm { get; set; }
public float IdleRpm { get; set; }
public int RevLightsPercent { get; set; }
public int Gear { get; set; }
public int MaxGear { get; set; }
public float Kers { get; set; }
public float MaxKers { get; set; }
public float Fuel { get; set; }
public float MaxFuel { get; set; }
public float LateralG { get; set; }
public float LongitudinalG { get; set; }
}
[MoonSharpUserData]
public class CarOverview
{
public F1Team Team { get; set; }
public AssistLevel TractionControl { get; set; }
public bool AntiLockBrakes { get; set; }
public static AssistLevel FloatToAssistLevel(float input)
{
// ReSharper disable CompareOfFloatsByEqualityOperator
if (input == 0)
return AssistLevel.Off;
if (input == 0.5)
return AssistLevel.Medium;
if (input == 1)
return AssistLevel.High;
// ReSharper restore CompareOfFloatsByEqualityOperator
return AssistLevel.Off;
}
}
public enum F1Team
{
RedBull = 0,
Ferrari = 1,
McLaren = 2,
Renault = 3,
Mercedes = 4,
Sauber = 5,
ForceIndia = 6,
Williams = 7,
TorroRosso = 8,
Haas = 11
}
public enum AssistLevel
{
Off,
Medium,
High
}
[MoonSharpUserData]
public class Session
{
public F1Track Track { get; set; }
public SessionType SessionType { get; set; }
public bool DrsEnabled { get; set; }
public SessionFlag Flags { get; set; }
public float TotalSeconds { get; set; }
public float LapSeconds { get; set; }
public float TrackLength { get; set; }
public float TotalDistance { get; set; }
public float LapDistance { get; set; }
public int LapNumber { get; set; }
public int TotalLaps { get; set; }
public int Position { get; set; }
}
public enum F1Track
{
Australia = 0,
China = 2,
Bahrain = 3,
BahrainShort = 21,
Russia = 18,
Spain = 4,
Monaco = 5,
Canada = 6,
Azerbaijan = 20,
Austria = 17,
Britain = 7,
BritainShort = 22,
Hungary = 9,
Belgium = 10,
Italy = 11,
Singapore = 12,
Malaysia = 1,
Japan = 13,
JapanShort = 24,
USA = 15,
USAShort = 23,
Mexico = 19,
Brazil = 16,
AbuDhabi = 14
}
public enum SessionFlag
{
Unknown = -1,
None = 0,
Green = 1,
Blue = 2,
Yellow = 3,
Red = 4
}
public enum SessionType
{
Unknown,
Practise,
Qualifying,
Race
}
}

View File

@ -0,0 +1,136 @@
using System;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Artemis.DAL;
using Artemis.Managers;
using Artemis.Modules.Abstract;
namespace Artemis.Modules.Games.FormulaOne2017
{
public class FormulaOne2017Model : ModuleModel
{
private bool _mustListen;
private UdpClient _udpClient;
private DateTime _lastUpdate;
private int _revAtZeroFrames;
public FormulaOne2017Model(DeviceManager deviceManager, LuaManager luaManager) : base(deviceManager, luaManager)
{
Settings = SettingsProvider.Load<FormulaOne2017Settings>();
DataModel = new FormulaOne2017DataModel();
ProcessNames.Add("F1_2017");
}
public override string Name => "FormulaOne2017";
public override bool IsOverlay => false;
public override bool IsBoundToProcess => true;
public override void Update()
{
// If we're not receiving updates, assume the game is paused/in the main menu
if (DateTime.Now - _lastUpdate > TimeSpan.FromSeconds(1))
((FormulaOne2017DataModel) DataModel).Session.SessionType = SessionType.Unknown;
}
public override void Enable()
{
_mustListen = true;
Task.Run(async () =>
{
_udpClient = new UdpClient(20777);
while (_mustListen)
{
//IPEndPoint object will allow us to read datagrams sent from any source.
try
{
var receivedResults = await _udpClient.ReceiveAsync();
HandleGameData(receivedResults);
}
catch (ObjectDisposedException)
{
// ignored, happens when shutting the module down
}
}
});
base.Enable();
}
private void HandleGameData(UdpReceiveResult receivedResults)
{
_lastUpdate = DateTime.Now;
var dataModel = (FormulaOne2017DataModel) DataModel;
var pinnedPacket = GCHandle.Alloc(receivedResults.Buffer, GCHandleType.Pinned);
var msg = (FormulaOne2017DataModel.UdpPacketData) Marshal.PtrToStructure(pinnedPacket.AddrOfPinnedObject(), typeof(FormulaOne2017DataModel.UdpPacketData));
pinnedPacket.Free();
dataModel.Car.SpeedKph = msg.m_speed * 1.609344;
dataModel.Car.SpeedMph = msg.m_speed;
dataModel.Car.Steering = msg.m_steer;
dataModel.Car.Throttle = msg.m_throttle;
dataModel.Car.Brake = msg.m_brake;
dataModel.Car.Clutch = msg.m_clutch;
dataModel.Car.Drs = msg.m_drs > 0;
dataModel.Car.Overview.Team = (F1Team) msg.m_team_info;
dataModel.Car.Overview.TractionControl = CarOverview.FloatToAssistLevel(msg.m_traction_control);
dataModel.Car.Overview.AntiLockBrakes = msg.m_anti_lock_brakes > 0;
dataModel.Car.Details.Rpm = msg.m_engineRate;
dataModel.Car.Details.MaxRpm = msg.m_max_rpm;
dataModel.Car.Details.IdleRpm = msg.m_idle_rpm;
// The one the game provides is all over the place at max rev causing blinking etc
// easily fixed by simply ignoring rapid changes from 100 to 0
if (msg.m_rev_lights_percent == 0)
_revAtZeroFrames++;
else
_revAtZeroFrames = 0;
if (_revAtZeroFrames > 2 || msg.m_rev_lights_percent != 0 || dataModel.Car.Details.RevLightsPercent != 100)
dataModel.Car.Details.RevLightsPercent = msg.m_rev_lights_percent;
dataModel.Car.Details.Gear = (int) msg.m_gear;
dataModel.Car.Details.MaxGear = (int) msg.m_max_gears;
dataModel.Car.Details.Kers = msg.m_kers_level;
dataModel.Car.Details.MaxKers = msg.m_kers_max_level;
dataModel.Car.Details.Fuel = msg.m_fuel_in_tank;
dataModel.Car.Details.MaxFuel = msg.m_fuel_capacity;
dataModel.Car.Details.LateralG = msg.m_gforce_lat;
dataModel.Car.Details.LongitudinalG = msg.m_gforce_lon;
dataModel.Session.SessionType = (SessionType) msg.m_sessionType;
// It's unknown in time trial but lets overwrite that to race
if (dataModel.Session.SessionType == SessionType.Unknown && dataModel.Car.SpeedMph > 0)
dataModel.Session.SessionType = SessionType.Race;
dataModel.Session.DrsEnabled = msg.m_drsAllowed > 0;
dataModel.Session.Flags = (SessionFlag) msg.m_vehicleFIAFlags;
dataModel.Session.TotalSeconds = msg.m_time;
dataModel.Session.LapSeconds = msg.m_lapTime;
dataModel.Session.Track = (F1Track) msg.m_track_number;
dataModel.Session.TrackLength = msg.m_track_size;
dataModel.Session.TotalDistance = msg.m_totalDistance;
dataModel.Session.LapDistance = msg.m_lapDistance;
dataModel.Session.LapNumber = (int) msg.m_lap;
dataModel.Session.TotalLaps = (int) msg.m_total_laps;
dataModel.Session.Position = (int) msg.m_car_position;
}
public override void Dispose()
{
_mustListen = false;
_udpClient.Dispose();
_udpClient = null;
base.Dispose();
}
}
}

View File

@ -0,0 +1,8 @@
using Artemis.Modules.Abstract;
namespace Artemis.Modules.Games.FormulaOne2017
{
public class FormulaOne2017Settings : ModuleSettings
{
}
}

View File

@ -0,0 +1,50 @@
<UserControl x:Class="Artemis.Modules.Games.FormulaOne2017.FormulaOne2017View"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:cal="http://www.caliburnproject.org"
xmlns:controls="http://metro.mahapps.com/winfx/xaml/controls"
mc:Ignorable="d"
d:DesignHeight="559.725" d:DesignWidth="882.696">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="30" />
</Grid.RowDefinitions>
<!-- Header -->
<Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" FontSize="20" HorizontalAlignment="Left">
<Label.Content>
<AccessText TextWrapping="Wrap" Text="There is currently no default profile available for F1 2017." />
</Label.Content>
</Label>
<!-- Sub header -->
<TextBlock Grid.Row="1" Grid.Column="0" VerticalAlignment="Bottom" TextWrapping="Wrap" HorizontalAlignment="Left" FontFamily="Segoe UI Semibold" TextAlignment="Justify" Margin="5,0,0,10">
The F1 2017 module requires UDP Telemetry to be enabled in the ingame settings menu.
</TextBlock>
<!-- Enable -->
<StackPanel Grid.Row="1" Grid.Column="1" HorizontalAlignment="Right" Orientation="Horizontal">
<Label Content="Enable module" HorizontalAlignment="Right" Margin="0,0,0,3" />
<controls:ToggleSwitchButton IsChecked="{Binding Path=IsModuleEnabled, Mode=OneWay}" cal:Message.Attach="[Event Click] = [Action ToggleModule]"
Style="{StaticResource MahApps.Metro.Styles.ToggleSwitchButton.Win10}" ToolTip="Note: You can't enable an module when Artemis is disabled" />
</StackPanel>
<!-- Profile editor -->
<ContentControl Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" x:Name="ProfileEditor" />
<!-- Buttons -->
<StackPanel Grid.Column="0" Grid.Row="3" Orientation="Horizontal" VerticalAlignment="Bottom">
<Button x:Name="ResetSettings" Content="Reset effect" VerticalAlignment="Top" Width="100" Style="{DynamicResource SquareButtonStyle}" />
<Button x:Name="SaveSettings" Content="Save changes" VerticalAlignment="Top" Width="100" Margin="10,0,0,0" Style="{DynamicResource SquareButtonStyle}" />
</StackPanel>
</Grid>
</UserControl>

View File

@ -0,0 +1,12 @@
using System.Windows.Controls;
namespace Artemis.Modules.Games.FormulaOne2017
{
public partial class FormulaOne2017View : UserControl
{
public FormulaOne2017View()
{
InitializeComponent();
}
}
}

View File

@ -0,0 +1,17 @@
using Artemis.Managers;
using Artemis.Modules.Abstract;
using Ninject;
namespace Artemis.Modules.Games.FormulaOne2017
{
public sealed class FormulaOne2017ViewModel : ModuleViewModel
{
public FormulaOne2017ViewModel(MainManager mainManager, [Named(nameof(FormulaOne2017Model))] ModuleModel moduleModel,
IKernel kernel) : base(mainManager, moduleModel, kernel)
{
DisplayName = "F1 2017";
}
public override bool UsesProfileEditor => true;
}
}

View File

@ -48,6 +48,8 @@ namespace Artemis.Modules.Games.Overwatch
Symmetra, Symmetra,
Zenyatta, Zenyatta,
Ana, Ana,
Sombra Sombra,
Orisa,
Doomfist
} }
} }

View File

@ -76,7 +76,9 @@ namespace Artemis.Modules.Games.Overwatch
new CharacterColor {Character = OverwatchCharacter.Symmetra, Color = Color.FromRgb(46, 116, 148)}, new CharacterColor {Character = OverwatchCharacter.Symmetra, Color = Color.FromRgb(46, 116, 148)},
new CharacterColor {Character = OverwatchCharacter.Zenyatta, Color = Color.FromRgb(248, 218, 26)}, new CharacterColor {Character = OverwatchCharacter.Zenyatta, Color = Color.FromRgb(248, 218, 26)},
new CharacterColor {Character = OverwatchCharacter.Ana, Color = Color.FromRgb(16, 36, 87)}, new CharacterColor {Character = OverwatchCharacter.Ana, Color = Color.FromRgb(16, 36, 87)},
new CharacterColor {Character = OverwatchCharacter.Sombra, Color = Color.FromRgb(20, 5, 101)} new CharacterColor {Character = OverwatchCharacter.Sombra, Color = Color.FromRgb(20, 5, 101)},
new CharacterColor {Character = OverwatchCharacter.Orisa, Color = Color.FromRgb(1,40,0)},
new CharacterColor {Character = OverwatchCharacter.Doomfist, Color = Color.FromRgb(33, 3, 1)}
}; };
} }

View File

@ -41,7 +41,7 @@
<!-- Game directory --> <!-- Game directory -->
<StackPanel Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" Margin="0,0,1,0"> <StackPanel Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" Margin="0,0,1,0">
<Label FontSize="20" HorizontalAlignment="Left" Content="Overwatch Directory" /> <Label FontSize="20" HorizontalAlignment="Left" Content="Overwatch directory" />
<Grid> <Grid>
<TextBox x:Name="GameDirectory" Height="23" TextWrapping="Wrap" Margin="0,0,30,0" Text="{Binding Path=Settings.GameDirectory, Mode=TwoWay}" cal:Message.Attach="[Event LostFocus] = [Action PlaceDll]" /> <TextBox x:Name="GameDirectory" Height="23" TextWrapping="Wrap" Margin="0,0,30,0" Text="{Binding Path=Settings.GameDirectory, Mode=TwoWay}" cal:Message.Attach="[Event LostFocus] = [Action PlaceDll]" />
<Button x:Name="BrowseDirectory" Content="..." RenderTransformOrigin="-0.039,-0.944" HorizontalAlignment="Right" Width="25" Style="{DynamicResource SquareButtonStyle}" Height="26" /> <Button x:Name="BrowseDirectory" Content="..." RenderTransformOrigin="-0.039,-0.944" HorizontalAlignment="Right" Width="25" Style="{DynamicResource SquareButtonStyle}" Height="26" />

View File

@ -41,7 +41,7 @@ namespace Artemis.Modules.Games.RocketLeague
} }
Updater.GetPointers(); Updater.GetPointers();
var version = SettingsProvider.Load<OffsetSettings>().RocketLeague.GameVersion; var version = SettingsProvider.Load<OffsetSettings>().RocketLeague?.GameVersion;
VersionText = $"Requires patch {version}. When a new patch is released Artemis downloads new pointers for the latest version (unless disabled in settings)."; VersionText = $"Requires patch {version}. When a new patch is released Artemis downloads new pointers for the latest version (unless disabled in settings).";
} }
} }

View File

@ -47,7 +47,7 @@ namespace Artemis.Modules.Games.UnrealTournament
var gameSettings = (UnrealTournamentSettings) Settings; var gameSettings = (UnrealTournamentSettings) Settings;
// If already propertly set up, don't do anything // If already propertly set up, don't do anything
if (gameSettings.GameDirectory != null && if (gameSettings.GameDirectory != null &&
File.Exists(gameSettings.GameDirectory + "UE4-Win64-Shipping.exe")) File.Exists(gameSettings.GameDirectory + @"\Engine\Binaries\Win64\UE4-Win64-Shipping.exe"))
return; return;
// Attempt to read the file // Attempt to read the file
@ -65,7 +65,7 @@ namespace Artemis.Modules.Games.UnrealTournament
// Use backslash in path for consistency // Use backslash in path for consistency
utDir = utDir.Replace('/', '\\'); utDir = utDir.Replace('/', '\\');
if (!File.Exists(utDir + @"\UE4-Win64-Shipping.exe")) if (!File.Exists(utDir + @"\Engine\Binaries\Win64\UE4-Win64-Shipping.exe"))
return; return;
gameSettings.GameDirectory = utDir; gameSettings.GameDirectory = utDir;
@ -78,7 +78,7 @@ namespace Artemis.Modules.Games.UnrealTournament
var gameSettings = (UnrealTournamentSettings) Settings; var gameSettings = (UnrealTournamentSettings) Settings;
var path = gameSettings.GameDirectory; var path = gameSettings.GameDirectory;
if (!File.Exists(path + @"\UE4-Win64-Shipping.exe")) if (!File.Exists(path + @"\Engine\Binaries\Win64\UE4-Win64-Shipping.exe"))
{ {
_dialogService.ShowErrorMessageBox("Please select a valid Unreal Tournament directory\n\n" + _dialogService.ShowErrorMessageBox("Please select a valid Unreal Tournament directory\n\n" +
@"By default Unreal Tournament is in C:\Program Files\Epic Games\UnrealTournament"); @"By default Unreal Tournament is in C:\Program Files\Epic Games\UnrealTournament");

View File

@ -1,52 +1,52 @@
using System.Windows.Forms; using System.Windows.Forms;
using Artemis.DAL; using Artemis.DAL;
using Artemis.Managers; using Artemis.Managers;
using Artemis.Modules.Abstract; using Artemis.Modules.Abstract;
using Artemis.Properties; using Artemis.Properties;
using Ninject; using Ninject;
namespace Artemis.Modules.Games.UnrealTournament namespace Artemis.Modules.Games.UnrealTournament
{ {
public sealed class UnrealTournamentViewModel : ModuleViewModel public sealed class UnrealTournamentViewModel : ModuleViewModel
{ {
public UnrealTournamentViewModel(MainManager mainManager, public UnrealTournamentViewModel(MainManager mainManager,
[Named(nameof(UnrealTournamentModel))] ModuleModel moduleModel, IKernel kernel) [Named(nameof(UnrealTournamentModel))] ModuleModel moduleModel, IKernel kernel)
: base(mainManager, moduleModel, kernel) : base(mainManager, moduleModel, kernel)
{
DisplayName = "Unreal Tournament";
InstallGif();
}
public override bool UsesProfileEditor => true;
public void BrowseDirectory()
{
var dialog = new FolderBrowserDialog {SelectedPath = ((UnrealTournamentSettings) Settings).GameDirectory};
var result = dialog.ShowDialog();
if (result != DialogResult.OK)
return;
((UnrealTournamentSettings) Settings).GameDirectory = dialog.SelectedPath;
((UnrealTournamentModel) ModuleModel).PlaceFiles();
Settings.Save();
NotifyOfPropertyChange(() => Settings);
}
public void PlaceFiles()
{ {
((UnrealTournamentModel)ModuleModel).PlaceFiles(); DisplayName = "Unreal Tournament";
Settings.Save(); InstallGif();
NotifyOfPropertyChange(() => Settings); }
}
public override bool UsesProfileEditor => true;
// Installing GIF on editor open to make sure the proper profiles are loaded
private void InstallGif() public void BrowseDirectory()
{ {
var gif = Resources.redeemer; var dialog = new FolderBrowserDialog {SelectedPath = ((UnrealTournamentSettings) Settings).GameDirectory};
if (gif == null) var result = dialog.ShowDialog();
return; if (result != DialogResult.OK)
return;
ProfileProvider.InsertGif("UnrealTournament", "Default", "Redeemer GIF", gif, "redeemer");
} ((UnrealTournamentSettings) Settings).GameDirectory = dialog.SelectedPath;
} ((UnrealTournamentModel) ModuleModel).PlaceFiles();
Settings.Save();
NotifyOfPropertyChange(() => Settings);
}
public void PlaceFiles()
{
((UnrealTournamentModel)ModuleModel).PlaceFiles();
Settings.Save();
NotifyOfPropertyChange(() => Settings);
}
// Installing GIF on editor open to make sure the proper profiles are loaded
private void InstallGif()
{
var gif = Resources.redeemer;
if (gif == null)
return;
ProfileProvider.InsertGif("UnrealTournament", "Default", "Redeemer GIF", gif, "redeemer");
}
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,157 +0,0 @@
namespace Artemis.Modules.Games.WoW.Data
{
public static class WoWEnums
{
public enum GuidType : byte
{
Null = 0,
Uniq = 1,
Player = 2,
Item = 3,
StaticDoor = 4,
Transport = 5,
Conversation = 6,
Creature = 7,
Vehicle = 8,
Pet = 9,
GameObject = 10,
DynamicObject = 11,
AreaTrigger = 12,
Corpse = 13,
LootObject = 14,
SceneObject = 15,
Scenario = 16,
AiGroup = 17,
DynamicDoor = 18,
ClientActor = 19,
Vignette = 20,
CallForHelp = 21,
AiResource = 22,
AiLock = 23,
AiLockTicket = 24,
ChatChannel = 25,
Party = 26,
Guild = 27,
WowAccount = 28,
BNetAccount = 29,
GmTask = 30,
MobileSession = 31,
RaidGroup = 32,
Spell = 33,
Mail = 34,
WebObj = 35,
LfgObject = 36,
LfgList = 37,
UserRouter = 38,
PvpQueueGroup = 39,
UserClient = 40,
PetBattle = 41,
UniqueUserClient = 42,
BattlePet = 43
}
public enum ObjectType
{
Object = 0,
Item = 1,
Container = 2,
Unit = 3,
Player = 4,
GameObject = 5,
DynamicObject = 6,
Corpse = 7,
AreaTrigger = 8,
SceneObject = 9,
Conversation = 10
}
public enum PowerType
{
Mana = 0,
Rage = 1,
Focus = 2,
Energy = 3,
Happiness = 4,
RunicPower = 5,
Runes = 6,
Health = 7,
Maelstrom = 11,
Insanity = 13,
Fury = 17,
Pain = 18,
UNKNOWN
}
public enum Reaction
{
Hostile = 1,
Neutral = 3,
Friendly = 4
}
public enum ShapeshiftForm
{
Normal = 0,
Cat = 1,
TreeOfLife = 2,
Travel = 3,
Aqua = 4,
Bear = 5,
Ambient = 6,
Ghoul = 7,
DireBear = 8,
CreatureBear = 14,
CreatureCat = 15,
GhostWolf = 16,
BattleStance = 17,
DefensiveStance = 18,
BerserkerStance = 19,
EpicFlightForm = 27,
Shadow = 28,
Stealth = 30,
Moonkin = 31,
SpiritOfRedemption = 32
}
public enum WoWClass
{
None = 0,
Warrior = 1,
Paladin = 2,
Hunter = 3,
Rogue = 4,
Priest = 5,
DeathKnight = 6,
Shaman = 7,
Mage = 8,
Warlock = 9,
Druid = 11
}
public enum WoWRace
{
Human = 1,
Orc = 2,
Dwarf = 3,
NightElf = 4,
Undead = 5,
Tauren = 6,
Gnome = 7,
Troll = 8,
Goblin = 9,
BloodElf = 10,
Draenei = 11,
FelOrc = 12,
Naga = 13,
Broken = 14,
Skeleton = 15,
Worgen = 22
}
public enum WoWType
{
Player,
Npc
}
}
}

View File

@ -1,63 +0,0 @@
using System;
using System.Text;
using Process.NET;
namespace Artemis.Modules.Games.WoW.Data
{
public class WoWNameCache
{
public WoWNameCache(ProcessSharp process, IntPtr baseAddress)
{
Process = process;
CurrentCacheAddress = process.Native.MainModule.BaseAddress + baseAddress.ToInt32();
}
public ProcessSharp Process { get; set; }
public IntPtr CurrentCacheAddress { get; set; }
public WoWDetails GetNameByGuid(Guid searchGuid)
{
var current = Process.Memory.Read<IntPtr>(CurrentCacheAddress);
var index = 0;
while (current != IntPtr.Zero)
{
var guid = Process.Memory.Read<Guid>(current + 0x20);
if (guid.Equals(searchGuid))
{
var pRace = Process.Memory.Read<int>(current + 0x88);
var pClass = Process.Memory.Read<int>(current + 0x90);
var pName = Process.Memory.Read(current + 0x31, Encoding.ASCII, 48);
var name = new WoWDetails(guid, pRace, pClass, WoWEnums.WoWType.Player, pName);
return name;
}
if (index > 20000)
return null;
index++;
current = Process.Memory.Read<IntPtr>(current);
}
return null;
}
}
public class WoWDetails
{
public WoWDetails(Guid guid, int race, int @class, WoWEnums.WoWType type, string name)
{
Guid = guid;
Race = (WoWEnums.WoWRace) race;
Class = (WoWEnums.WoWClass) @class;
Type = type;
Name = name;
}
public Guid Guid { get; set; }
public WoWEnums.WoWRace Race { get; set; }
public WoWEnums.WoWClass Class { get; set; }
public WoWEnums.WoWType Type { get; set; }
public string Name { get; set; }
}
}

View File

@ -1,73 +0,0 @@
using System;
using System.Text;
using Newtonsoft.Json;
using Process.NET;
namespace Artemis.Modules.Games.WoW.Data
{
public class WoWObject
{
private readonly bool _readPointer;
public WoWObject(IProcess process, IntPtr baseAddress, bool readPointer = false)
{
Process = process;
BaseAddress = baseAddress;
_readPointer = readPointer;
Guid = ReadField<Guid>(0x00);
}
[JsonIgnore]
public IntPtr BaseAddress { get; set; }
[JsonIgnore]
public IProcess Process { get; set; }
public Guid Guid { get; set; }
[JsonIgnore]
public WoWStructs.ObjectData Data { get; set; }
public T ReadField<T>(int offset)
{
var address = GetAddress();
if (address == IntPtr.Zero)
return default(T);
var ptr = Process.Memory.Read<IntPtr>(address + 0x10);
return Process.Memory.Read<T>(ptr + offset);
}
public T ReadField<T>(Enum offset)
{
var address = GetAddress();
if (address == IntPtr.Zero)
return default(T);
var ptr = Process.Memory.Read<IntPtr>(address + 0x10);
return Process.Memory.Read<T>(ptr + Convert.ToInt32(offset));
}
private IntPtr GetAddress()
{
return _readPointer
? Process.Memory.Read<IntPtr>(Process.Native.MainModule.BaseAddress + BaseAddress.ToInt32())
: BaseAddress;
}
public WoWDetails GetNpcDetails()
{
var address = GetAddress();
if (address == IntPtr.Zero)
return null;
var npcCachePtr = Process.Memory.Read<IntPtr>(address + 0x1760);
if (npcCachePtr == IntPtr.Zero)
return null;
var npcName = Process.Memory.Read(Process.Memory.Read<IntPtr>(npcCachePtr + 0x00A0), Encoding.ASCII, 48);
return new WoWDetails(Guid, 0, 0, WoWEnums.WoWType.Npc, npcName);
}
}
}

View File

@ -1,92 +0,0 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Process.NET;
namespace Artemis.Modules.Games.WoW.Data
{
public class WoWObjectManager
{
public WoWObjectManager(IProcess process, IntPtr baseAddress)
{
Process = process;
CurrentManagerAddress = process.Native.MainModule.BaseAddress + baseAddress.ToInt32();
}
public IProcess Process { get; set; }
public IntPtr CurrentManagerAddress { get; set; }
public Dictionary<WoWStructs.Guid, WoWObject> WoWObjects { get; set; }
public IntPtr GetFirstObject()
{
var mgr = GetCurrentManager();
return mgr.VisibleObjects.m_fulllist.baseClass.m_terminator.m_next;
}
public WoWStructs.CurrentManager GetCurrentManager()
{
return Process.Memory.Read<WoWStructs.CurrentManager>(Process.Memory.Read<IntPtr>(CurrentManagerAddress));
}
public IntPtr GetNextObjectFromCurrent(IntPtr current)
{
var mgr = GetCurrentManager();
return Process.Memory.Read<IntPtr>(
current + mgr.VisibleObjects.m_fulllist.baseClass.m_linkoffset + IntPtr.Size);
}
public void Update()
{
WoWObjects.Clear();
var wowObjects = EnumVisibleObjects();
foreach (var wowObject in wowObjects)
WoWObjects[wowObject.Data.Guid] = wowObject;
OnObjectsUpdated(WoWObjects);
}
public event EventHandler<Dictionary<WoWStructs.Guid, WoWObject>> ObjectsUpdated;
// Loop through the games object list.
public IEnumerable<WoWObject> EnumVisibleObjects()
{
var first = GetFirstObject();
var typeOffset = Marshal.OffsetOf(typeof(WoWStructs.ObjectData), "ObjectType").ToInt32();
while (((first.ToInt64() & 1) == 0) && (first != IntPtr.Zero))
{
var type = (WoWEnums.ObjectType) Process.Memory.Read<int>(first + typeOffset);
// Fix below with other object types as added.
// ReSharper disable once SwitchStatementMissingSomeCases
switch (type)
{
case WoWEnums.ObjectType.Object:
yield return new WoWObject(Process, first);
break;
case WoWEnums.ObjectType.Container:
break;
case WoWEnums.ObjectType.Unit:
yield return new WoWUnit(Process, first);
break;
case WoWEnums.ObjectType.Player:
yield return new WoWPlayer(Process, first, new IntPtr(0x179A6E0));
break;
default:
yield return new WoWObject(Process, first);
break;
}
first = GetNextObjectFromCurrent(first);
}
}
protected virtual void OnObjectsUpdated(Dictionary<WoWStructs.Guid, WoWObject> e)
{
ObjectsUpdated?.Invoke(this, e);
}
}
}

View File

@ -1,368 +0,0 @@
namespace Artemis.Modules.Games.WoW.Data
{
internal static class WoWOffsets
{
internal enum AreaTriggerData
{
OverrideScaleCurve = 0x30, // Size: 0x7, Flags: 0x201
ExtraScaleCurve = 0x4C, // Size: 0x7, Flags: 0x201
Caster = 0x68, // Size: 0x4, Flags: 0x1
Duration = 0x78, // Size: 0x1, Flags: 0x1
TimeToTarget = 0x7C, // Size: 0x1, Flags: 0x201
TimeToTargetScale = 0x80, // Size: 0x1, Flags: 0x201
TimeToTargetExtraScale = 0x84, // Size: 0x1, Flags: 0x201
SpellId = 0x88, // Size: 0x1, Flags: 0x1
SpellVisualId = 0x8C, // Size: 0x1, Flags: 0x80
BoundsRadius2D = 0x90, // Size: 0x1, Flags: 0x280
DecalPropertiesId = 0x94 // Size: 0x1, Flags: 0x1
}
internal enum ContainerData
{
Slots = 0x150, // Size: 0x90, Flags: 0x1
NumSlots = 0x390 // Size: 0x1, Flags: 0x1
}
internal enum ConversationData
{
LastLineDuration = 0x30 // Size: 0x1, Flags: 0x80
}
internal enum CorpseData
{
Owner = 0x30, // Size: 0x4, Flags: 0x1
PartyGuid = 0x40, // Size: 0x4, Flags: 0x1
DisplayId = 0x50, // Size: 0x1, Flags: 0x1
Items = 0x54, // Size: 0x13, Flags: 0x1
SkinId = 0xA0, // Size: 0x1, Flags: 0x1
FacialHairStyleId = 0xA4, // Size: 0x1, Flags: 0x1
Flags = 0xA8, // Size: 0x1, Flags: 0x1
DynamicFlags = 0xAC, // Size: 0x1, Flags: 0x80
FactionTemplate = 0xB0, // Size: 0x1, Flags: 0x1
CustomDisplayOption = 0xB4 // Size: 0x1, Flags: 0x1
}
internal enum DynamicObjectData
{
Caster = 0x30, // Size: 0x4, Flags: 0x1
TypeAndVisualId = 0x40, // Size: 0x1, Flags: 0x80
SpellId = 0x44, // Size: 0x1, Flags: 0x1
Radius = 0x48, // Size: 0x1, Flags: 0x1
CastTime = 0x4C // Size: 0x1, Flags: 0x1
}
internal enum GameObjectData
{
CreatedBy = 0x30, // Size: 0x4, Flags: 0x1
DisplayId = 0x40, // Size: 0x1, Flags: 0x280
Flags = 0x44, // Size: 0x1, Flags: 0x201
ParentRotation = 0x48, // Size: 0x4, Flags: 0x1
FactionTemplate = 0x58, // Size: 0x1, Flags: 0x1
Level = 0x5C, // Size: 0x1, Flags: 0x1
PercentHealth = 0x60, // Size: 0x1, Flags: 0x201
SpellVisualId = 0x64, // Size: 0x1, Flags: 0x281
StateSpellVisualId = 0x68, // Size: 0x1, Flags: 0x280
StateAnimId = 0x6C, // Size: 0x1, Flags: 0x280
StateAnimKitId = 0x70, // Size: 0x1, Flags: 0x280
StateWorldEffectId = 0x74 // Size: 0x4, Flags: 0x280
}
internal enum ItemData
{
Owner = 0x30, // Size: 0x4, Flags: 0x1
ContainedIn = 0x40, // Size: 0x4, Flags: 0x1
Creator = 0x50, // Size: 0x4, Flags: 0x1
GiftCreator = 0x60, // Size: 0x4, Flags: 0x1
StackCount = 0x70, // Size: 0x1, Flags: 0x4
Expiration = 0x74, // Size: 0x1, Flags: 0x4
SpellCharges = 0x78, // Size: 0x5, Flags: 0x4
DynamicFlags = 0x8C, // Size: 0x1, Flags: 0x1
Enchantment = 0x90, // Size: 0x27, Flags: 0x1
PropertySeed = 0x12C, // Size: 0x1, Flags: 0x1
RandomPropertiesId = 0x130, // Size: 0x1, Flags: 0x1
Durability = 0x134, // Size: 0x1, Flags: 0x4
MaxDurability = 0x138, // Size: 0x1, Flags: 0x4
CreatePlayedTime = 0x13C, // Size: 0x1, Flags: 0x1
ModifiersMask = 0x140, // Size: 0x1, Flags: 0x4
Context = 0x144, // Size: 0x1, Flags: 0x1
ArtifactXp = 0x148, // Size: 0x1, Flags: 0x4
ItemAppearanceModId = 0x14C // Size: 0x1, Flags: 0x4
}
internal enum KeyBinding
{
NumKeyBindings = 0x1700030, // -0x17C0
First = 0xC8,
Next = 0xB8,
Key = 0x30,
Command = 0x58
}
internal enum ObjectData
{
Guid = 0x0, // Size: 0x4, Flags: 0x1
Data = 0x10, // Size: 0x4, Flags: 0x1
Type = 0x20, // Size: 0x1, Flags: 0x1
EntryId = 0x24, // Size: 0x1, Flags: 0x80
DynamicFlags = 0x28, // Size: 0x1, Flags: 0x280
Scale = 0x2C // Size: 0x1, Flags: 0x1
}
internal enum PlayerData
{
DuelArbiter = 0x360, // Size: 0x4, Flags: 0x1
WowAccount = 0x370, // Size: 0x4, Flags: 0x1
LootTargetGuid = 0x380, // Size: 0x4, Flags: 0x1
PlayerFlags = 0x390, // Size: 0x1, Flags: 0x1
PlayerFlagsEx = 0x394, // Size: 0x1, Flags: 0x1
GuildRankId = 0x398, // Size: 0x1, Flags: 0x1
GuildDeleteDate = 0x39C, // Size: 0x1, Flags: 0x1
GuildLevel = 0x3A0, // Size: 0x1, Flags: 0x1
HairColorId = 0x3A4, // Size: 0x1, Flags: 0x1
CustomDisplayOption = 0x3A8, // Size: 0x1, Flags: 0x1
Inebriation = 0x3AC, // Size: 0x1, Flags: 0x1
ArenaFaction = 0x3B0, // Size: 0x1, Flags: 0x1
DuelTeam = 0x3B4, // Size: 0x1, Flags: 0x1
GuildTimeStamp = 0x3B8, // Size: 0x1, Flags: 0x1
QuestLog = 0x3BC, // Size: 0x320, Flags: 0x20
VisibleItems = 0x103C, // Size: 0x26, Flags: 0x1
PlayerTitle = 0x10D4, // Size: 0x1, Flags: 0x1
FakeInebriation = 0x10D8, // Size: 0x1, Flags: 0x1
VirtualPlayerRealm = 0x10DC, // Size: 0x1, Flags: 0x1
CurrentSpecId = 0x10E0, // Size: 0x1, Flags: 0x1
TaxiMountAnimKitId = 0x10E4, // Size: 0x1, Flags: 0x1
AvgItemLevel = 0x10E8, // Size: 0x4, Flags: 0x1
CurrentBattlePetBreedQuality = 0x10F8, // Size: 0x1, Flags: 0x1
Prestige = 0x10FC, // Size: 0x1, Flags: 0x1
HonorLevel = 0x1100, // Size: 0x1, Flags: 0x1
InvSlots = 0x1104, // Size: 0x2EC, Flags: 0x2
FarsightObject = 0x1CB4, // Size: 0x4, Flags: 0x2
SummonedBattlePetGuid = 0x1CC4, // Size: 0x4, Flags: 0x2
KnownTitles = 0x1CD4, // Size: 0xC, Flags: 0x2
Coinage = 0x1D04, // Size: 0x2, Flags: 0x2
Xp = 0x1D0C, // Size: 0x1, Flags: 0x2
NextLevelXp = 0x1D10, // Size: 0x1, Flags: 0x2
Skill = 0x1D14, // Size: 0x1C0, Flags: 0x2
CharacterPoints = 0x2414, // Size: 0x1, Flags: 0x2
MaxTalentTiers = 0x2418, // Size: 0x1, Flags: 0x2
TrackCreatureMask = 0x241C, // Size: 0x1, Flags: 0x2
TrackResourceMask = 0x2420, // Size: 0x1, Flags: 0x2
MainhandExpertise = 0x2424, // Size: 0x1, Flags: 0x2
OffhandExpertise = 0x2428, // Size: 0x1, Flags: 0x2
RangedExpertise = 0x242C, // Size: 0x1, Flags: 0x2
CombatRatingExpertise = 0x2430, // Size: 0x1, Flags: 0x2
BlockPercentage = 0x2434, // Size: 0x1, Flags: 0x2
DodgePercentage = 0x2438, // Size: 0x1, Flags: 0x2
ParryPercentage = 0x243C, // Size: 0x1, Flags: 0x2
CritPercentage = 0x2440, // Size: 0x1, Flags: 0x2
RangedCritPercentage = 0x2444, // Size: 0x1, Flags: 0x2
OffhandCritPercentage = 0x2448, // Size: 0x1, Flags: 0x2
SpellCritPercentage = 0x244C, // Size: 0x1, Flags: 0x2
ShieldBlock = 0x2450, // Size: 0x1, Flags: 0x2
ShieldBlockCritPercentage = 0x2454, // Size: 0x1, Flags: 0x2
Mastery = 0x2458, // Size: 0x1, Flags: 0x2
Speed = 0x245C, // Size: 0x1, Flags: 0x2
Lifesteal = 0x2460, // Size: 0x1, Flags: 0x2
Avoidance = 0x2464, // Size: 0x1, Flags: 0x2
Sturdiness = 0x2468, // Size: 0x1, Flags: 0x2
Versatility = 0x246C, // Size: 0x1, Flags: 0x2
VersatilityBonus = 0x2470, // Size: 0x1, Flags: 0x2
PvpPowerDamage = 0x2474, // Size: 0x1, Flags: 0x2
PvpPowerHealing = 0x2478, // Size: 0x1, Flags: 0x2
ExploredZones = 0x247C, // Size: 0x100, Flags: 0x2
RestInfo = 0x287C, // Size: 0x4, Flags: 0x2
ModDamageDonePos = 0x288C, // Size: 0x7, Flags: 0x2
ModDamageDoneNeg = 0x28A8, // Size: 0x7, Flags: 0x2
ModDamageDonePercent = 0x28C4, // Size: 0x7, Flags: 0x2
ModHealingDonePos = 0x28E0, // Size: 0x1, Flags: 0x2
ModHealingPercent = 0x28E4, // Size: 0x1, Flags: 0x2
ModHealingDonePercent = 0x28E8, // Size: 0x1, Flags: 0x2
ModPeriodicHealingDonePercent = 0x28EC, // Size: 0x1, Flags: 0x2
WeaponDmgMultipliers = 0x28F0, // Size: 0x3, Flags: 0x2
WeaponAtkSpeedMultipliers = 0x28FC, // Size: 0x3, Flags: 0x2
ModSpellPowerPercent = 0x2908, // Size: 0x1, Flags: 0x2
ModResiliencePercent = 0x290C, // Size: 0x1, Flags: 0x2
OverrideSpellPowerByApPercent = 0x2910, // Size: 0x1, Flags: 0x2
OverrideApBySpellPowerPercent = 0x2914, // Size: 0x1, Flags: 0x2
ModTargetResistance = 0x2918, // Size: 0x1, Flags: 0x2
ModTargetPhysicalResistance = 0x291C, // Size: 0x1, Flags: 0x2
LocalFlags = 0x2920, // Size: 0x1, Flags: 0x2
NumRespecs = 0x2924, // Size: 0x1, Flags: 0x2
SelfResSpell = 0x2928, // Size: 0x1, Flags: 0x2
PvpMedals = 0x292C, // Size: 0x1, Flags: 0x2
BuybackPrice = 0x2930, // Size: 0xC, Flags: 0x2
BuybackTimestamp = 0x2960, // Size: 0xC, Flags: 0x2
YesterdayHonorableKills = 0x2990, // Size: 0x1, Flags: 0x2
LifetimeHonorableKills = 0x2994, // Size: 0x1, Flags: 0x2
WatchedFactionIndex = 0x2998, // Size: 0x1, Flags: 0x2
CombatRatings = 0x299C, // Size: 0x20, Flags: 0x2
PvpInfo = 0x2A1C, // Size: 0x24, Flags: 0x2
MaxLevel = 0x2AAC, // Size: 0x1, Flags: 0x2
ScalingPlayerLevelDelta = 0x2AB0, // Size: 0x1, Flags: 0x2
MaxCreatureScalingLevel = 0x2AB4, // Size: 0x1, Flags: 0x2
NoReagentCostMask = 0x2AB8, // Size: 0x4, Flags: 0x2
PetSpellPower = 0x2AC8, // Size: 0x1, Flags: 0x2
Researching = 0x2ACC, // Size: 0xA, Flags: 0x2
ProfessionSkillLine = 0x2AF4, // Size: 0x2, Flags: 0x2
UiHitModifier = 0x2AFC, // Size: 0x1, Flags: 0x2
UiSpellHitModifier = 0x2B00, // Size: 0x1, Flags: 0x2
HomeRealmTimeOffset = 0x2B04, // Size: 0x1, Flags: 0x2
ModPetHaste = 0x2B08, // Size: 0x1, Flags: 0x2
OverrideSpellsId = 0x2B0C, // Size: 0x1, Flags: 0x402
LfgBonusFactionId = 0x2B10, // Size: 0x1, Flags: 0x2
LootSpecId = 0x2B14, // Size: 0x1, Flags: 0x2
OverrideZonePvpType = 0x2B18, // Size: 0x1, Flags: 0x402
BagSlotFlags = 0x2B1C, // Size: 0x4, Flags: 0x2
BankBagSlotFlags = 0x2B2C, // Size: 0x7, Flags: 0x2
InsertItemsLeftToRight = 0x2B48, // Size: 0x1, Flags: 0x2
QuestCompleted = 0x2B4C, // Size: 0x36B, Flags: 0x2
Honor = 0x38F8, // Size: 0x1, Flags: 0x2
HonorNextLevel = 0x38FC // Size: 0x1, Flags: 0x2
}
internal enum SceneObjectData
{
ScriptPackageId = 0x30, // Size: 0x1, Flags: 0x1
RndSeedVal = 0x34, // Size: 0x1, Flags: 0x1
CreatedBy = 0x38, // Size: 0x4, Flags: 0x1
SceneType = 0x48 // Size: 0x1, Flags: 0x1
}
internal enum Unit
{
CurrentCastId = 0x1B98,
CurrentChanneledId = 0x1BB8,
AuraTable = 0x1D10,
AuraCount = 0x2390,
AuraSize = 0x68,
ClientRace = 0x2670,
DisplayData = 0x1718
}
// Note: Invalid possibly!
internal enum UnitAuras : uint
{
AuraCount1 = 0x2390,
AuraCount2 = 0x1D10,
AuraTable1 = 0x1D14,
AuraTable2 = 0x1D18,
AuraSize = 0x68,
OwnerGuid = 0x40,
AuraSpellId = 0x50,
//AuraFlags = 0x54, //Not exactly sure here.
//AuraLevel = 0x58, //Not exactly sure here.
AuraStack = 0x59,
TimeLeft = 0x60,
//In case I need it:
DruidEclipse = 0x2694
}
// Below is all of the World of Warcraft in-game object field offsets.
// Commenting is not used on purpose and enums below should remain internal.
internal enum UnitData
{
Charm = 0x30, // Size: 0x4, Flags: 0x1
Summon = 0x40, // Size: 0x4, Flags: 0x1
Critter = 0x50, // Size: 0x4, Flags: 0x2
CharmedBy = 0x60, // Size: 0x4, Flags: 0x1
SummonedBy = 0x70, // Size: 0x4, Flags: 0x1
CreatedBy = 0x80, // Size: 0x4, Flags: 0x1
DemonCreator = 0x90, // Size: 0x4, Flags: 0x1
Target = 0xA0, // Size: 0x4, Flags: 0x1
BattlePetCompanionGuid = 0xB0, // Size: 0x4, Flags: 0x1
BattlePetDbid = 0xC0, // Size: 0x2, Flags: 0x1
ChannelObject = 0xC8, // Size: 0x4, Flags: 0x201
ChannelSpell = 0xD8, // Size: 0x1, Flags: 0x201
ChannelSpellXSpellVisual = 0xDC, // Size: 0x1, Flags: 0x201
SummonedByHomeRealm = 0xE0, // Size: 0x1, Flags: 0x1
Sex = 0xE4, // Size: 0x1, Flags: 0x1
DisplayPower = 0xE8, // Size: 0x1, Flags: 0x1
OverrideDisplayPowerId = 0xEC, // Size: 0x1, Flags: 0x1
Health = 0xF0, // Size: 0x2, Flags: 0x1
Power = 0xF8, // Size: 0x6, Flags: 0x401
TertiaryPower = 0xFC,
SecondaryPower = 0x100,
MaxHealth = 0x110, // Size: 0x2, Flags: 0x1
MaxPower = 0x118, // Size: 0x6, Flags: 0x1
PowerRegenFlatModifier = 0x130, // Size: 0x6, Flags: 0x46
PowerRegenInterruptedFlatModifier = 0x148, // Size: 0x6, Flags: 0x46
Level = 0x160, // Size: 0x1, Flags: 0x1
EffectiveLevel = 0x164, // Size: 0x1, Flags: 0x1
ScalingLevelMin = 0x168, // Size: 0x1, Flags: 0x1
ScalingLevelMax = 0x16C, // Size: 0x1, Flags: 0x1
ScalingLevelDelta = 0x170, // Size: 0x1, Flags: 0x1
FactionTemplate = 0x174, // Size: 0x1, Flags: 0x1
VirtualItems = 0x178, // Size: 0x6, Flags: 0x1
Flags = 0x190, // Size: 0x1, Flags: 0x201
Flags2 = 0x194, // Size: 0x1, Flags: 0x201
Flags3 = 0x198, // Size: 0x1, Flags: 0x201
AuraState = 0x19C, // Size: 0x1, Flags: 0x1
AttackRoundBaseTime = 0x1A0, // Size: 0x2, Flags: 0x1
RangedAttackRoundBaseTime = 0x1A8, // Size: 0x1, Flags: 0x2
BoundingRadius = 0x1AC, // Size: 0x1, Flags: 0x1
CombatReach = 0x1B0, // Size: 0x1, Flags: 0x1
DisplayId = 0x1B4, // Size: 0x1, Flags: 0x280
NativeDisplayId = 0x1B8, // Size: 0x1, Flags: 0x201
MountDisplayId = 0x1BC, // Size: 0x1, Flags: 0x201
MinDamage = 0x1C0, // Size: 0x1, Flags: 0x16
MaxDamage = 0x1C4, // Size: 0x1, Flags: 0x16
MinOffHandDamage = 0x1C8, // Size: 0x1, Flags: 0x16
MaxOffHandDamage = 0x1CC, // Size: 0x1, Flags: 0x16
AnimTier = 0x1D0, // Size: 0x1, Flags: 0x1
PetNumber = 0x1D4, // Size: 0x1, Flags: 0x1
PetNameTimestamp = 0x1D8, // Size: 0x1, Flags: 0x1
PetExperience = 0x1DC, // Size: 0x1, Flags: 0x4
PetNextLevelExperience = 0x1E0, // Size: 0x1, Flags: 0x4
ModCastingSpeed = 0x1E4, // Size: 0x1, Flags: 0x1
ModSpellHaste = 0x1E8, // Size: 0x1, Flags: 0x1
ModHaste = 0x1EC, // Size: 0x1, Flags: 0x1
ModRangedHaste = 0x1F0, // Size: 0x1, Flags: 0x1
ModHasteRegen = 0x1F4, // Size: 0x1, Flags: 0x1
ModTimeRate = 0x1F8, // Size: 0x1, Flags: 0x1
CreatedBySpell = 0x1FC, // Size: 0x1, Flags: 0x1
NpcFlags = 0x200, // Size: 0x2, Flags: 0x81
EmoteState = 0x208, // Size: 0x1, Flags: 0x1
Stats = 0x20C, // Size: 0x4, Flags: 0x6
StatPosBuff = 0x21C, // Size: 0x4, Flags: 0x6
StatNegBuff = 0x22C, // Size: 0x4, Flags: 0x6
Resistances = 0x23C, // Size: 0x7, Flags: 0x16
ResistanceBuffModsPositive = 0x258, // Size: 0x7, Flags: 0x6
ResistanceBuffModsNegative = 0x274, // Size: 0x7, Flags: 0x6
ModBonusArmor = 0x290, // Size: 0x1, Flags: 0x6
BaseMana = 0x294, // Size: 0x1, Flags: 0x1
BaseHealth = 0x298, // Size: 0x1, Flags: 0x6
ShapeshiftForm = 0x29C, // Size: 0x1, Flags: 0x1
AttackPower = 0x2A0, // Size: 0x1, Flags: 0x6
AttackPowerModPos = 0x2A4, // Size: 0x1, Flags: 0x6
AttackPowerModNeg = 0x2A8, // Size: 0x1, Flags: 0x6
AttackPowerMultiplier = 0x2AC, // Size: 0x1, Flags: 0x6
RangedAttackPower = 0x2B0, // Size: 0x1, Flags: 0x6
RangedAttackPowerModPos = 0x2B4, // Size: 0x1, Flags: 0x6
RangedAttackPowerModNeg = 0x2B8, // Size: 0x1, Flags: 0x6
RangedAttackPowerMultiplier = 0x2BC, // Size: 0x1, Flags: 0x6
SetAttackSpeedAura = 0x2C0, // Size: 0x1, Flags: 0x6
MinRangedDamage = 0x2C4, // Size: 0x1, Flags: 0x6
MaxRangedDamage = 0x2C8, // Size: 0x1, Flags: 0x6
PowerCostModifier = 0x2CC, // Size: 0x7, Flags: 0x6
PowerCostMultiplier = 0x2E8, // Size: 0x7, Flags: 0x6
MaxHealthModifier = 0x304, // Size: 0x1, Flags: 0x6
HoverHeight = 0x308, // Size: 0x1, Flags: 0x1
MinItemLevelCutoff = 0x30C, // Size: 0x1, Flags: 0x1
MinItemLevel = 0x310, // Size: 0x1, Flags: 0x1
MaxItemLevel = 0x314, // Size: 0x1, Flags: 0x1
WildBattlePetLevel = 0x318, // Size: 0x1, Flags: 0x1
BattlePetCompanionNameTimestamp = 0x31C, // Size: 0x1, Flags: 0x1
InteractSpellId = 0x320, // Size: 0x1, Flags: 0x1
StateSpellVisualId = 0x324, // Size: 0x1, Flags: 0x280
StateAnimId = 0x328, // Size: 0x1, Flags: 0x280
StateAnimKitId = 0x32C, // Size: 0x1, Flags: 0x280
StateWorldEffectId = 0x330, // Size: 0x4, Flags: 0x280
ScaleDuration = 0x340, // Size: 0x1, Flags: 0x1
LooksLikeMountId = 0x344, // Size: 0x1, Flags: 0x1
LooksLikeCreatureId = 0x348, // Size: 0x1, Flags: 0x1
LookAtControllerId = 0x34C, // Size: 0x1, Flags: 0x1
LookAtControllerTarget = 0x350 // Size: 0x4, Flags: 0x1
}
}
}

View File

@ -1,23 +0,0 @@
using System;
using System.Linq;
using Process.NET;
namespace Artemis.Modules.Games.WoW.Data
{
public class WoWPlayer : WoWUnit
{
private readonly IntPtr _targetIntPtr;
public WoWPlayer(IProcess process, IntPtr baseAddress, IntPtr targetIntPtr, bool readPointer = false)
: base(process, baseAddress, readPointer)
{
_targetIntPtr = targetIntPtr;
}
public WoWObject GetTarget(WoWObjectManager manager)
{
var targetGuid = Process.Memory.Read<Guid>(Process.Native.MainModule.BaseAddress + _targetIntPtr.ToInt32());
return manager.EnumVisibleObjects().FirstOrDefault(o => o.Guid == targetGuid);
}
}
}

View File

@ -1,181 +0,0 @@
using System;
using System.Runtime.InteropServices;
using Artemis.Modules.Games.WoW.Data.WowSharp.Client.Patchables;
namespace Artemis.Modules.Games.WoW.Data
{
public static class WoWStructs
{
[StructLayout(LayoutKind.Explicit)]
public struct ObjectData
{
// x32 : x64
[FieldOffset(0)] private readonly IntPtr vtable; // 0x00 0x00
[FieldOffset(10)] public IntPtr Descriptors; // 0x04 0x10
[FieldOffset(18)] private readonly IntPtr unk1; // 0x08 0x18
[FieldOffset(20)] public int ObjectType; // 0x0C 0x20
[FieldOffset(24)] private readonly IntPtr unk3; // 0x10 0x24
[FieldOffset(28)] private readonly IntPtr unk4; // 0x14 0x28
[FieldOffset(30)] private readonly IntPtr unk5; // 0x18 0x30
[FieldOffset(38)] private readonly IntPtr unk6; // 0x1C 0x38
[FieldOffset(40)] private readonly IntPtr unk7; // 0x20 0x40
[FieldOffset(48)] private readonly IntPtr unk8; // 0x24 0x48
[FieldOffset(50)] public Guid Guid; // 0x28 0x50
}
public struct Guid
{
private readonly Int128 _mGuid;
public static readonly Guid Zero = new Guid(0);
public Guid(Int128 guid)
{
_mGuid = guid;
}
public override string ToString()
{
// ReSharper disable once SwitchStatementMissingSomeCases
switch (Type)
{
case WoWEnums.GuidType.Creature:
case WoWEnums.GuidType.Vehicle:
case WoWEnums.GuidType.Pet:
case WoWEnums.GuidType.GameObject:
case WoWEnums.GuidType.AreaTrigger:
case WoWEnums.GuidType.DynamicObject:
case WoWEnums.GuidType.Corpse:
case WoWEnums.GuidType.LootObject:
case WoWEnums.GuidType.SceneObject:
case WoWEnums.GuidType.Scenario:
case WoWEnums.GuidType.AiGroup:
case WoWEnums.GuidType.DynamicDoor:
case WoWEnums.GuidType.Vignette:
case WoWEnums.GuidType.Conversation:
case WoWEnums.GuidType.CallForHelp:
case WoWEnums.GuidType.AiResource:
case WoWEnums.GuidType.AiLock:
case WoWEnums.GuidType.AiLockTicket:
return $"{Type}-{SubType}-{RealmId}-{MapId}-{ServerId}-{Id}-{CreationBits:X10}";
case WoWEnums.GuidType.Player:
return $"{Type}-{RealmId}-{(ulong) (_mGuid >> 64):X8}";
case WoWEnums.GuidType.Item:
return $"{Type}-{RealmId}-{(uint) ((_mGuid >> 18) & 0xFFFFFF)}-{(ulong) (_mGuid >> 64):X10}";
//case GuidType.ClientActor:
// return String.Format("{0}-{1}-{2}", Type, RealmId, CreationBits);
//case GuidType.Transport:
//case GuidType.StaticDoor:
// return String.Format("{0}-{1}-{2}", Type, RealmId, CreationBits);
default:
return $"{Type}-{_mGuid:X32}";
}
}
public override bool Equals(object obj)
{
if (obj is Guid)
return _mGuid == ((Guid) obj)._mGuid;
return false;
}
public override int GetHashCode()
{
return _mGuid.GetHashCode();
}
public WoWEnums.GuidType Type => (WoWEnums.GuidType) (byte) ((_mGuid >> 58) & 0x3F);
public byte SubType => (byte) ((_mGuid >> 120) & 0x3F);
public ushort RealmId => (ushort) ((_mGuid >> 42) & 0x1FFF);
public ushort ServerId => (ushort) ((_mGuid >> 104) & 0x1FFF);
public ushort MapId => (ushort) ((_mGuid >> 29) & 0x1FFF);
// Creature, Pet, Vehicle
public uint Id => (uint) ((_mGuid >> 6) & 0x7FFFFF);
public ulong CreationBits => (ulong) ((_mGuid >> 64) & 0xFFFFFFFFFF);
}
#region Manager
// Region is here due to the large amount of structs-
// the CurremtManager struct depends on.
[StructLayout(LayoutKind.Sequential)]
public struct CurrentManager
{
public TsHashTable VisibleObjects; // m_objects
public TsHashTable LazyCleanupObjects; // m_lazyCleanupObjects
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 13)]
// m_lazyCleanupFifo, m_freeObjects, m_visibleObjects, m_reenabledObjects, whateverObjects...
public TsExplicitList[] Links; // Links[13] has all objects stored in VisibleObjects it seems
#if !X64
public int Unknown1; // wtf is that and why x86 only?
#endif
public Int128 ActivePlayer;
public int MapId;
public IntPtr ClientConnection;
public IntPtr MovementGlobals;
public int Unk1;
}
[StructLayout(LayoutKind.Sequential)]
public struct Ts
{
public IntPtr vtable;
public uint m_alloc;
public uint m_count;
public IntPtr m_data; //TSExplicitList* m_data;
}
[StructLayout(LayoutKind.Sequential)]
public struct TsExplicitList
{
public TsList baseClass;
}
[StructLayout(LayoutKind.Sequential)]
public struct TsFixedArray
{
public Ts baseClass;
}
[StructLayout(LayoutKind.Sequential)]
public struct TsGrowableArray
{
public TsFixedArray baseclass;
public uint m_chunk;
}
[StructLayout(LayoutKind.Sequential)]
public struct TsHashTable
{
public IntPtr vtable;
public TsExplicitList m_fulllist;
public int m_fullnessIndicator;
public TsGrowableArray m_slotlistarray;
public int m_slotmask;
}
[StructLayout(LayoutKind.Sequential)]
public struct TsLink
{
public IntPtr m_prevlink; //TSLink *m_prevlink
public IntPtr m_next; // C_OBJECTHASH *m_next
}
[StructLayout(LayoutKind.Sequential)]
public struct TsList
{
public int m_linkoffset;
public TsLink m_terminator;
}
#endregion;
}
}

View File

@ -1,31 +0,0 @@
using System;
using Process.NET;
using static Artemis.Modules.Games.WoW.Data.WoWEnums;
using static Artemis.Modules.Games.WoW.Data.WoWOffsets;
namespace Artemis.Modules.Games.WoW.Data
{
public class WoWUnit : WoWObject
{
public WoWUnit(IProcess process, IntPtr baseAddress, bool readPointer = false)
: base(process, baseAddress, readPointer)
{
}
public int Health => ReadField<int>(UnitData.Health);
public int MaxHealth => ReadField<int>(UnitData.MaxHealth);
public int Power => ReadField<int>(UnitData.Power);
public int SecondaryPower => ReadField<int>(UnitData.SecondaryPower);
public int TertiaryPower => ReadField<int>(UnitData.TertiaryPower);
public int MaxPower => ReadField<int>(UnitData.MaxPower);
public PowerType PowerType => (PowerType) ReadField<int>(UnitData.DisplayPower);
public int Level => ReadField<int>(UnitData.Level);
public WoWDetails Details { get; set; }
public void UpdateDetails(WoWNameCache nameCache)
{
Details = GetNpcDetails() ?? nameCache.GetNameByGuid(Guid);
}
}
}

View File

@ -0,0 +1,45 @@
using System;
using MoonSharp.Interpreter;
using Newtonsoft.Json.Linq;
namespace Artemis.Modules.Games.WoW.Models
{
[MoonSharpUserData]
public class WoWAura
{
public string Name { get; set; }
public int Id { get; set; }
public int Stacks { get; set; }
public DateTime StartTime { set; get; }
public DateTime EndTime { get; set; }
public float Progress { get; set; }
public void ApplyJson(JToken buffJson)
{
Name = buffJson["n"].Value<string>();
Id = buffJson["id"].Value<int>();
if (buffJson["c"] != null)
Stacks = buffJson["c"].Value<int>();
if (buffJson["e"] != null)
{
var expires = buffJson["e"].Value<int>();
var tickCount = Environment.TickCount;
var timeLeft = expires - tickCount;
EndTime = DateTime.Now.AddMilliseconds(timeLeft);
}
if (buffJson["d"] != null)
StartTime = EndTime.AddSeconds(buffJson["d"].Value<int>() * -1);
}
public void UpdateProgress()
{
var elapsed = DateTime.Now - StartTime;
var total = EndTime - StartTime;
Progress = (float) (elapsed.TotalMilliseconds / total.TotalMilliseconds);
if (Progress > 1)
Progress = 1;
}
}
}

View File

@ -0,0 +1,64 @@
using System;
using MoonSharp.Interpreter;
using Newtonsoft.Json.Linq;
namespace Artemis.Modules.Games.WoW.Models
{
[MoonSharpUserData]
public class WoWCastBar
{
public WoWCastBar()
{
Spell = new WoWSpell();
}
public WoWSpell Spell { get; set; }
public DateTime StartTime { set; get; }
public DateTime EndTime { get; set; }
public bool NonInterruptible { get; set; }
public float Progress { get; set; }
public bool IsChannel { get; set; }
public void ApplyJson(JToken spellJson, bool isChannel)
{
var castMs = spellJson["e"].Value<int>() - spellJson["s"].Value<int>();
var tickCount = Environment.TickCount;
var difference = tickCount - spellJson["s"].Value<int>();
Spell.Name = spellJson["n"].Value<string>();
Spell.Id = spellJson["sid"].Value<int>();
StartTime = new DateTime(DateTime.Now.Ticks + difference);
EndTime = StartTime.AddMilliseconds(castMs);
NonInterruptible = spellJson["ni"].Value<bool>();
IsChannel = isChannel;
}
public void UpdateProgress()
{
if (Spell.Name == null)
return;
var elapsed = DateTime.Now - StartTime;
var total = EndTime - StartTime;
if (IsChannel)
Progress = 1 - (float) (elapsed.TotalMilliseconds / total.TotalMilliseconds);
else
Progress = (float) (elapsed.TotalMilliseconds / total.TotalMilliseconds);
if (Progress > 1 || Progress < 0)
Clear();
}
public void Clear()
{
Spell.Name = null;
Spell.Id = 0;
StartTime = DateTime.MinValue;
EndTime = DateTime.MinValue;
NonInterruptible = false;
Progress = 0;
IsChannel = false;
}
}
}

View File

@ -0,0 +1,44 @@
namespace Artemis.Modules.Games.WoW.Models
{
public enum WoWRace
{
Human,
Orc,
Dwarf,
NightElf,
Undead,
Tauren,
Gnome,
Troll,
BloodElf,
Draenei,
Goblin,
Worgen,
Pandaren
}
public enum WoWPowerType
{
Mana = 0,
Rage = 1,
Focus = 2,
Energy = 3,
ComboPoints = 4,
Runes = 5,
RunicPower = 6,
SoulShards = 7,
LunarPower = 8,
HolyPower = 9,
AlternatePower = 10,
Maelstrom = 11,
Chi = 12,
Insanity = 13,
ArcaneCharges = 16
}
public enum WoWGender
{
Male,
Female
}
}

View File

@ -0,0 +1,20 @@
using MoonSharp.Interpreter;
using Newtonsoft.Json.Linq;
namespace Artemis.Modules.Games.WoW.Models
{
[MoonSharpUserData]
public class WoWSpecialization
{
public string Name { get; set; }
public int Id { get; set; }
public string Role { get; set; }
public void ApplyJson(JToken specJson)
{
Name = specJson["n"].Value<string>();
Id = specJson["id"].Value<int>();
Role = specJson["r"].Value<string>();
}
}
}

View File

@ -0,0 +1,11 @@
using MoonSharp.Interpreter;
namespace Artemis.Modules.Games.WoW.Models
{
[MoonSharpUserData]
public class WoWSpell
{
public string Name { get; set; }
public int Id { get; set; }
}
}

View File

@ -0,0 +1,134 @@
using System.Collections.Generic;
using Artemis.Utilities;
using MoonSharp.Interpreter;
using Newtonsoft.Json.Linq;
namespace Artemis.Modules.Games.WoW.Models
{
[MoonSharpUserData]
public class WoWUnit
{
private readonly List<WoWSpell> _currentFrameCasts = new List<WoWSpell>();
public WoWUnit()
{
CastBar = new WoWCastBar();
Specialization = new WoWSpecialization();
Buffs = new List<WoWAura>();
Debuffs = new List<WoWAura>();
RecentIntantCasts = new List<WoWSpell>();
}
public string Name { get; set; }
public int Level { get; set; }
public int Health { get; set; }
public int MaxHealth { get; set; }
public int Power { get; set; }
public int MaxPower { get; set; }
public WoWPowerType PowerType { get; set; }
public string Class { get; set; }
public WoWRace Race { get; set; }
public WoWGender Gender { get; set; }
public WoWCastBar CastBar { get; set; }
public WoWSpecialization Specialization { get; }
public List<WoWAura> Buffs { get; }
public List<WoWAura> Debuffs { get; }
public List<WoWSpell> RecentIntantCasts { get; }
public void ApplyJson(JToken json)
{
if (json["n"] == null)
return;
Name = json["n"].Value<string>();
Level = json["l"].Value<int>();
Class = json["c"].Value<string>();
Gender = json["g"].Value<int>() == 3 ? WoWGender.Female : WoWGender.Male;
if (json["r"] != null)
Race = GeneralHelpers.ParseEnum<WoWRace>(json["r"].Value<string>());
if (json["s"] != null)
Specialization.ApplyJson(json["s"]);
}
public void ApplyStateJson(JToken json)
{
Health = json["h"].Value<int>();
MaxHealth = json["mh"].Value<int>();
PowerType = GeneralHelpers.ParseEnum<WoWPowerType>(json["t"].Value<int>().ToString());
Power = json["p"].Value<int>();
MaxPower = json["mp"].Value<int>();
}
public void ApplyAuraJson(JToken json, bool buffs)
{
if (buffs)
{
lock (Buffs)
{
Buffs.Clear();
foreach (var auraJson in json.Children())
{
var aura = new WoWAura();
aura.ApplyJson(auraJson);
Buffs.Add(aura);
}
}
}
else
{
lock (Debuffs)
{
Debuffs.Clear();
foreach (var auraJson in json.Children())
{
var aura = new WoWAura();
aura.ApplyJson(auraJson);
Debuffs.Add(aura);
}
}
}
}
public void AddInstantCast(WoWSpell spell)
{
lock (_currentFrameCasts)
{
_currentFrameCasts.Add(spell);
}
}
public void ClearInstantCasts()
{
lock (_currentFrameCasts)
{
// Remove all casts that weren't cast in the after the last frame
RecentIntantCasts.Clear();
RecentIntantCasts.AddRange(_currentFrameCasts);
// Clear the that were after the last frame so that they are removed next frame when this method is called again
_currentFrameCasts.Clear();
}
}
public void Update()
{
CastBar.UpdateProgress();
ClearInstantCasts();
lock (Buffs)
{
foreach (var buff in Buffs)
buff.UpdateProgress();
}
lock (Debuffs)
{
foreach (var debuff in Debuffs)
debuff.UpdateProgress();
}
}
}
}

View File

@ -0,0 +1,9 @@
## Interface: 70300
## Title: Artemis
## Notes: Transmits ingame data to Artemis
## Author: SpoinkyNL
## Version: 1.0.0
embeds.xml
Core.lua

View File

@ -0,0 +1,398 @@
Artemis = LibStub("AceAddon-3.0"):NewAddon("Artemis", "AceConsole-3.0", "AceEvent-3.0", "AceTimer-3.0", "AceComm-3.0")
json = LibStub("json")
-- Hook onto logout because it seems PLAYER_LOGOUT fires on reload as well
local _Logout = Logout
local debugging = false
local lastLine = {}
local channeling = {}
local unitUpdates = {}
local lastTransmitMessage
local lastTransmitTime
local lastBuffs = "";
local lastDebuffs = "";
local prefixCounts = {}
channeling["player"] = false
channeling["target"] = false
function Artemis:OnEnable()
-- Register all the various events that Artemis will want to know about
Artemis:RegisterEvent("PLAYER_ENTERING_WORLD")
Artemis:RegisterEvent("PLAYER_LEVEL_UP")
Artemis:RegisterEvent("PLAYER_FLAGS_CHANGED")
Artemis:RegisterEvent("ACHIEVEMENT_EARNED")
Artemis:RegisterEvent("ACTIVE_TALENT_GROUP_CHANGED")
Artemis:RegisterEvent("UNIT_TARGET")
Artemis:RegisterEvent("UNIT_HEALTH")
Artemis:RegisterEvent("UNIT_POWER")
Artemis:RegisterEvent("UNIT_AURA")
Artemis:RegisterEvent("UNIT_SPELLCAST_START")
Artemis:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED")
Artemis:RegisterEvent("UNIT_SPELLCAST_FAILED")
Artemis:RegisterEvent("UNIT_SPELLCAST_DELAYED")
Artemis:RegisterEvent("UNIT_SPELLCAST_CHANNEL_START")
Artemis:RegisterEvent("UNIT_SPELLCAST_CHANNEL_STOP")
Artemis:RegisterEvent("UNIT_SPELLCAST_CHANNEL_UPDATE")
Artemis:RegisterEvent("UNIT_SPELLCAST_INTERRUPTED")
Artemis:RegisterEvent("ZONE_CHANGED")
Artemis:RegisterEvent("ZONE_CHANGED_NEW_AREA")
-- Register the chat command /artemis <something>
Artemis:RegisterChatCommand("artemis", "HandleChatCommand")
-- Create a loop that'll periodically send character data in case of an Artemis restart/late start
Artemis:ScheduleRepeatingTimer("PeriodicUpdate", 30)
end
function Artemis:HandleChatCommand(input)
if input == "debug" then
debugging = not (debugging)
if debugging then
Artemis:Print("Debugging enabled.")
else
Artemis:Print("Debugging disabled.")
end
end
if input == "rc" then
prefixCounts = {}
Artemis:Print("Reset the send counters.")
end
if input == nill or input == "" or input == "help" then
Artemis:Print("Available chat commands:")
Artemis:Printf("|cffb7b7b7/artemis debug|r: Toggle debugging")
Artemis:Printf("|cffb7b7b7/artemis rc|r: Reset the debug counters")
end
end
function Artemis:Transmit(prefix, data, prio)
local msg = "artemis(".. prefix .. "|" .. json.encode(data) ..")"
-- If the message is the same as the previous, make sure it wasn't sent less than 250ms ago
if msg == lastTransmitMessage then
if not (lastTransmitTime == nil) then
local diff = GetTime() - lastTransmitTime;
if (diff < 0.25) then
return
end
end
end
lastTransmitTime = GetTime()
if debugging == true then
if prefixCounts[prefix] == nill then
prefixCounts[prefix] = 0
end
prefixCounts[prefix] = prefixCounts[prefix] + 1
end
if debugging == true then
Artemis:Printf("Transmitting with prefix |cfffdff71" .. prefix .. "|r (" .. prefixCounts[prefix] .. ").")
Artemis:Print(msg)
end
Artemis:SendCommMessage("(artemis)", msg, "WHISPER", UnitName("player"), prio)
end
function Artemis:TransmitUnitState(unit, ignoreThrottle)
if not ignoreThrottle then
if not (unitUpdates[unit] == nil) then
local diff = GetTime() - unitUpdates[unit]
if (diff < 0.5) then
return
end
end
end
local table = {
h = UnitHealth(unit),
mh = UnitHealthMax(unit),
p = UnitPower(unit),
mp = UnitPowerMax(unit),
t = UnitPowerType(unit)
};
unitUpdates[unit] = GetTime()
Artemis:Transmit(unit .. "State", table)
end
function Artemis:GetUnitDetails(unit)
return {
n = UnitName(unit),
c = UnitClass(unit),
l = UnitLevel(unit),
r = UnitRace(unit),
g = UnitSex(unit),
f = UnitFactionGroup(unit)
};
end
function Artemis:GetPlayerDetails()
local details = Artemis:GetUnitDetails("player")
local id, name, _, _, role = GetSpecializationInfo(GetSpecialization())
details.realm = GetRealmName()
details.achievementPoints = GetTotalAchievementPoints(false)
details.s = {id = id, n = name, r = role}
return details
end
function Artemis:GetUnitAuras(unit, filter)
local auras = {};
for index = 1, 40 do
local name, _, _, count, _, duration, expires, caster, _, _, spellID = UnitAura(unit, index, filter);
if not (name == nil) then
local buffTable = {n = name, id = spellID}
-- Leave these values out if they are 0 to save some space
if count > 0 then
buffTable["c"] = count
end
if duration > 0 then
buffTable["d"] = duration
end
if expires > 0 then
buffTable["e"] = expires
end
table.insert(auras, buffTable)
end
end
return auras
end
function Artemis:PeriodicUpdate()
-- Don't do this in combat, enough data going out at that time already
if InCombatLockdown() then
return
end
Artemis:Transmit("player", Artemis:GetPlayerDetails())
Artemis:TransmitUnitState("player", true);
end
function Artemis:PLAYER_ENTERING_WORLD(...)
Artemis:Transmit("player", Artemis:GetPlayerDetails())
Artemis:TransmitUnitState("player", true);
end
function Logout()
Artemis:Transmit("gameState", "loggedOut")
return _Logout()
end
function Artemis:PLAYER_LEVEL_UP(...)
Artemis:Transmit("player", Artemis:GetPlayerDetails())
end
function Artemis:PLAYER_FLAGS_CHANGED(...)
local _, unitID = ...
if unitID == "player" then
-- AFK overwrites DND
if UnitIsAFK("player") then
Artemis:Transmit("gameState", "afk")
return
end
if UnitIsDND("player") then
Artemis:Transmit("gameState", "dnd")
return
end
Artemis:Transmit("gameState", "ingame")
end
end
function Artemis:ACHIEVEMENT_EARNED(...)
Artemis:Transmit("player", Artemis:GetPlayerDetails())
end
function Artemis:ACTIVE_TALENT_GROUP_CHANGED(...)
Artemis:Transmit("player", Artemis:GetPlayerDetails())
end
function Artemis:UNIT_TARGET(...)
local _, source = ...
if not (source == "player") then
return
end
local details = Artemis:GetUnitDetails("target")
channeling["target"] = false
Artemis:Transmit("target", details)
Artemis:TransmitUnitState("target", true);
end
function Artemis:UNIT_HEALTH(...)
local _, source = ...
if not (source == "player") and not (source == "target") then
return
end
Artemis:TransmitUnitState(source, false);
end
function Artemis:UNIT_POWER(...)
local _, source = ...
if not (source == "player") and not (source == "target") then
return
end
Artemis:TransmitUnitState(source, false);
end
function Artemis:UNIT_AURA(...)
local _, source = ...
if not (source == "player") then
return
end
local buffs = Artemis:GetUnitAuras(source, "PLAYER|HELPFUL")
local debuffs = Artemis:GetUnitAuras(source, "PLAYER|HARMFUL")
local newBuffs = json.encode(buffs)
local newDebuffs = json.encode(debuffs)
if not (lastBuffs == newBuffs) then
Artemis:Transmit("buffs", buffs)
end
if not (lastDebuffs == newDebuffs) then
Artemis:Transmit("debuffs", debuffs)
end
lastBuffs = newBuffs
lastDebuffs = newDebuffs
end
-- Detect non-instant spell casts
function Artemis:UNIT_SPELLCAST_START(...)
local _, unitID, spell, rank, lineID, spellID = ...
if not (unitID == "player") and not (unitID == "target") then
return
end
local name, _, _, _, startTime, endTime, _, _, notInterruptible = UnitCastingInfo(unitID)
local table = {uid = unitID, n = name, sid = spellID, s = startTime, e = endTime, ni = notInterruptible}
lastLine[unitID] = lineID
Artemis:Transmit("spellCast", table, "ALERT")
end
-- Detect instant spell casts
function Artemis:UNIT_SPELLCAST_SUCCEEDED (...)
local _, unitID, spell, rank, lineID, spellID = ...
if not (unitID == "player") and not (unitID == "target") then
return
end
if channeling[unitID] == true then
return
end
-- Many spells are irrelevant system spells, don't transmit these
if unitID == "player" and not (IsPlayerSpell(spellID)) then
return
end
local name, subText, text, texture, startTime, endTime, isTradeSkill, castID, notInterruptible = UnitCastingInfo(unitID)
-- Don't trigger on the success of a non instant cast
if not (lastLine[unitID] == nil) and lastLine[unitID] == lineID then
return
end
-- Set back the last line to what is currently being cast (Fireblast during Fireball per example)
if not (name == nil) then
lastLine[unitID] = castID
else
lastLine[unitID] = nil
end
local table = {uid = unitID, n = spell, sid = spellID}
Artemis:Transmit("instantSpellCast", table, "ALERT")
end
-- Detect falure of non instant casts
function Artemis:UNIT_SPELLCAST_FAILED (...)
local source, unitID, _, _, lineID = ...
if not (unitID == "player") and not (unitID == "target") then
return
end
if lastLine[unitID] == nil or not (lastLine[unitID] == lineID) then
return
end
lastLine[unitID] = nil
Artemis:Transmit("spellCastFailed", unitID, "ALERT")
end
-- Detect falure of non instant casts
function Artemis:UNIT_SPELLCAST_DELAYED (...)
local _, unitID, spell, rank, lineID, spellID = ...
if not (unitID == "player") and not (unitID == "target") then
return
end
local name, _, _, _, startTime, endTime, _, _, notInterruptible = UnitCastingInfo(unitID)
local table = {uid = unitID, n = name, sid = spellID, s = startTime, e = endTime, ni = notInterruptible}
Artemis:Transmit("spellCast", table, "ALERT")
end
-- Detect cancellation of non instant casts
function Artemis:UNIT_SPELLCAST_INTERRUPTED (...)
local source, unitID, _, _, lineID = ...
if not (unitID == "player") and not (unitID == "target") then
return
end
if lastLine[unitID] == nil or not (lastLine[unitID] == lineID) then
return
end
lastLine[unitID] = nil
Artemis:Transmit("spellCastInterrupted", unitID, "ALERT")
end
-- Detect spell channels
function Artemis:UNIT_SPELLCAST_CHANNEL_START(...)
local _, unitID, spell, rank, lineID, spellID = ...
if not (unitID == "player") and not (unitID == "target") then
return
end
channeling[unitID] = true
local name, _, _, _, startTime, endTime, _, notInterruptible = UnitChannelInfo(unitID)
local table = {uid = unitID, n = name, sid = spellID, s = startTime, e = endTime, ni = notInterruptible}
Artemis:Transmit("spellChannel", table, "ALERT")
end
function Artemis:UNIT_SPELLCAST_CHANNEL_UPDATE (...)
local _, unitID, spell, rank, lineID, spellID = ...
if not (unitID == "player") and not (unitID == "target") then
return
end
local name, _, _, _, startTime, endTime, _, notInterruptible = UnitChannelInfo(unitID)
local table = {uid = unitID, n = name, sid = spellID, s = startTime, e = endTime, ni = notInterruptible}
Artemis:Transmit("spellChannel", table, "ALERT")
end
-- Detect cancellation of channels
function Artemis:UNIT_SPELLCAST_CHANNEL_STOP (...)
local source, unitID, _, _, lineID = ...
if not (unitID == "player") and not (unitID == "target") then
return
end
channeling[unitID] = false
Artemis:Transmit("spellChannelInterrupted", unitID, "ALERT")
end
function Artemis:ZONE_CHANGED_NEW_AREA (...)
local pvpType, isSubZonePVP, factionName = GetZonePVPInfo()
Artemis:Transmit("zone", {z = GetRealZoneText(), s = GetSubZoneText(), t = pvpType, p = isSubZonePVP, f = factionName})
end
function Artemis:ZONE_CHANGED (...)
local pvpType, isSubZonePVP, factionName = GetZonePVPInfo()
Artemis:Transmit("zone", {z = GetRealZoneText(), s = GetSubZoneText(), t = pvpType, p = isSubZonePVP, f = factionName})
end

View File

@ -0,0 +1,674 @@
--- **AceAddon-3.0** provides a template for creating addon objects.
-- It'll provide you with a set of callback functions that allow you to simplify the loading
-- process of your addon.\\
-- Callbacks provided are:\\
-- * **OnInitialize**, which is called directly after the addon is fully loaded.
-- * **OnEnable** which gets called during the PLAYER_LOGIN event, when most of the data provided by the game is already present.
-- * **OnDisable**, which is only called when your addon is manually being disabled.
-- @usage
-- -- A small (but complete) addon, that doesn't do anything,
-- -- but shows usage of the callbacks.
-- local MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon")
--
-- function MyAddon:OnInitialize()
-- -- do init tasks here, like loading the Saved Variables,
-- -- or setting up slash commands.
-- end
--
-- function MyAddon:OnEnable()
-- -- Do more initialization here, that really enables the use of your addon.
-- -- Register Events, Hook functions, Create Frames, Get information from
-- -- the game that wasn't available in OnInitialize
-- end
--
-- function MyAddon:OnDisable()
-- -- Unhook, Unregister Events, Hide frames that you created.
-- -- You would probably only use an OnDisable if you want to
-- -- build a "standby" mode, or be able to toggle modules on/off.
-- end
-- @class file
-- @name AceAddon-3.0.lua
-- @release $Id: AceAddon-3.0.lua 1084 2013-04-27 20:14:11Z nevcairiel $
local MAJOR, MINOR = "AceAddon-3.0", 12
local AceAddon, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
if not AceAddon then return end -- No Upgrade needed.
AceAddon.frame = AceAddon.frame or CreateFrame("Frame", "AceAddon30Frame") -- Our very own frame
AceAddon.addons = AceAddon.addons or {} -- addons in general
AceAddon.statuses = AceAddon.statuses or {} -- statuses of addon.
AceAddon.initializequeue = AceAddon.initializequeue or {} -- addons that are new and not initialized
AceAddon.enablequeue = AceAddon.enablequeue or {} -- addons that are initialized and waiting to be enabled
AceAddon.embeds = AceAddon.embeds or setmetatable({}, {__index = function(tbl, key) tbl[key] = {} return tbl[key] end }) -- contains a list of libraries embedded in an addon
-- Lua APIs
local tinsert, tconcat, tremove = table.insert, table.concat, table.remove
local fmt, tostring = string.format, tostring
local select, pairs, next, type, unpack = select, pairs, next, type, unpack
local loadstring, assert, error = loadstring, assert, error
local setmetatable, getmetatable, rawset, rawget = setmetatable, getmetatable, rawset, rawget
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
-- List them here for Mikk's FindGlobals script
-- GLOBALS: LibStub, IsLoggedIn, geterrorhandler
--[[
xpcall safecall implementation
]]
local xpcall = xpcall
local function errorhandler(err)
return geterrorhandler()(err)
end
local function CreateDispatcher(argCount)
local code = [[
local xpcall, eh = ...
local method, ARGS
local function call() return method(ARGS) end
local function dispatch(func, ...)
method = func
if not method then return end
ARGS = ...
return xpcall(call, eh)
end
return dispatch
]]
local ARGS = {}
for i = 1, argCount do ARGS[i] = "arg"..i end
code = code:gsub("ARGS", tconcat(ARGS, ", "))
return assert(loadstring(code, "safecall Dispatcher["..argCount.."]"))(xpcall, errorhandler)
end
local Dispatchers = setmetatable({}, {__index=function(self, argCount)
local dispatcher = CreateDispatcher(argCount)
rawset(self, argCount, dispatcher)
return dispatcher
end})
Dispatchers[0] = function(func)
return xpcall(func, errorhandler)
end
local function safecall(func, ...)
-- we check to see if the func is passed is actually a function here and don't error when it isn't
-- this safecall is used for optional functions like OnInitialize OnEnable etc. When they are not
-- present execution should continue without hinderance
if type(func) == "function" then
return Dispatchers[select('#', ...)](func, ...)
end
end
-- local functions that will be implemented further down
local Enable, Disable, EnableModule, DisableModule, Embed, NewModule, GetModule, GetName, SetDefaultModuleState, SetDefaultModuleLibraries, SetEnabledState, SetDefaultModulePrototype
-- used in the addon metatable
local function addontostring( self ) return self.name end
-- Check if the addon is queued for initialization
local function queuedForInitialization(addon)
for i = 1, #AceAddon.initializequeue do
if AceAddon.initializequeue[i] == addon then
return true
end
end
return false
end
--- Create a new AceAddon-3.0 addon.
-- Any libraries you specified will be embeded, and the addon will be scheduled for
-- its OnInitialize and OnEnable callbacks.
-- The final addon object, with all libraries embeded, will be returned.
-- @paramsig [object ,]name[, lib, ...]
-- @param object Table to use as a base for the addon (optional)
-- @param name Name of the addon object to create
-- @param lib List of libraries to embed into the addon
-- @usage
-- -- Create a simple addon object
-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon", "AceEvent-3.0")
--
-- -- Create a Addon object based on the table of a frame
-- local MyFrame = CreateFrame("Frame")
-- MyAddon = LibStub("AceAddon-3.0"):NewAddon(MyFrame, "MyAddon", "AceEvent-3.0")
function AceAddon:NewAddon(objectorname, ...)
local object,name
local i=1
if type(objectorname)=="table" then
object=objectorname
name=...
i=2
else
name=objectorname
end
if type(name)~="string" then
error(("Usage: NewAddon([object,] name, [lib, lib, lib, ...]): 'name' - string expected got '%s'."):format(type(name)), 2)
end
if self.addons[name] then
error(("Usage: NewAddon([object,] name, [lib, lib, lib, ...]): 'name' - Addon '%s' already exists."):format(name), 2)
end
object = object or {}
object.name = name
local addonmeta = {}
local oldmeta = getmetatable(object)
if oldmeta then
for k, v in pairs(oldmeta) do addonmeta[k] = v end
end
addonmeta.__tostring = addontostring
setmetatable( object, addonmeta )
self.addons[name] = object
object.modules = {}
object.orderedModules = {}
object.defaultModuleLibraries = {}
Embed( object ) -- embed NewModule, GetModule methods
self:EmbedLibraries(object, select(i,...))
-- add to queue of addons to be initialized upon ADDON_LOADED
tinsert(self.initializequeue, object)
return object
end
--- Get the addon object by its name from the internal AceAddon registry.
-- Throws an error if the addon object cannot be found (except if silent is set).
-- @param name unique name of the addon object
-- @param silent if true, the addon is optional, silently return nil if its not found
-- @usage
-- -- Get the Addon
-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon")
function AceAddon:GetAddon(name, silent)
if not silent and not self.addons[name] then
error(("Usage: GetAddon(name): 'name' - Cannot find an AceAddon '%s'."):format(tostring(name)), 2)
end
return self.addons[name]
end
-- - Embed a list of libraries into the specified addon.
-- This function will try to embed all of the listed libraries into the addon
-- and error if a single one fails.
--
-- **Note:** This function is for internal use by :NewAddon/:NewModule
-- @paramsig addon, [lib, ...]
-- @param addon addon object to embed the libs in
-- @param lib List of libraries to embed into the addon
function AceAddon:EmbedLibraries(addon, ...)
for i=1,select("#", ... ) do
local libname = select(i, ...)
self:EmbedLibrary(addon, libname, false, 4)
end
end
-- - Embed a library into the addon object.
-- This function will check if the specified library is registered with LibStub
-- and if it has a :Embed function to call. It'll error if any of those conditions
-- fails.
--
-- **Note:** This function is for internal use by :EmbedLibraries
-- @paramsig addon, libname[, silent[, offset]]
-- @param addon addon object to embed the library in
-- @param libname name of the library to embed
-- @param silent marks an embed to fail silently if the library doesn't exist (optional)
-- @param offset will push the error messages back to said offset, defaults to 2 (optional)
function AceAddon:EmbedLibrary(addon, libname, silent, offset)
local lib = LibStub:GetLibrary(libname, true)
if not lib and not silent then
error(("Usage: EmbedLibrary(addon, libname, silent, offset): 'libname' - Cannot find a library instance of %q."):format(tostring(libname)), offset or 2)
elseif lib and type(lib.Embed) == "function" then
lib:Embed(addon)
tinsert(self.embeds[addon], libname)
return true
elseif lib then
error(("Usage: EmbedLibrary(addon, libname, silent, offset): 'libname' - Library '%s' is not Embed capable"):format(libname), offset or 2)
end
end
--- Return the specified module from an addon object.
-- Throws an error if the addon object cannot be found (except if silent is set)
-- @name //addon//:GetModule
-- @paramsig name[, silent]
-- @param name unique name of the module
-- @param silent if true, the module is optional, silently return nil if its not found (optional)
-- @usage
-- -- Get the Addon
-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon")
-- -- Get the Module
-- MyModule = MyAddon:GetModule("MyModule")
function GetModule(self, name, silent)
if not self.modules[name] and not silent then
error(("Usage: GetModule(name, silent): 'name' - Cannot find module '%s'."):format(tostring(name)), 2)
end
return self.modules[name]
end
local function IsModuleTrue(self) return true end
--- Create a new module for the addon.
-- The new module can have its own embeded libraries and/or use a module prototype to be mixed into the module.\\
-- A module has the same functionality as a real addon, it can have modules of its own, and has the same API as
-- an addon object.
-- @name //addon//:NewModule
-- @paramsig name[, prototype|lib[, lib, ...]]
-- @param name unique name of the module
-- @param prototype object to derive this module from, methods and values from this table will be mixed into the module (optional)
-- @param lib List of libraries to embed into the addon
-- @usage
-- -- Create a module with some embeded libraries
-- MyModule = MyAddon:NewModule("MyModule", "AceEvent-3.0", "AceHook-3.0")
--
-- -- Create a module with a prototype
-- local prototype = { OnEnable = function(self) print("OnEnable called!") end }
-- MyModule = MyAddon:NewModule("MyModule", prototype, "AceEvent-3.0", "AceHook-3.0")
function NewModule(self, name, prototype, ...)
if type(name) ~= "string" then error(("Usage: NewModule(name, [prototype, [lib, lib, lib, ...]): 'name' - string expected got '%s'."):format(type(name)), 2) end
if type(prototype) ~= "string" and type(prototype) ~= "table" and type(prototype) ~= "nil" then error(("Usage: NewModule(name, [prototype, [lib, lib, lib, ...]): 'prototype' - table (prototype), string (lib) or nil expected got '%s'."):format(type(prototype)), 2) end
if self.modules[name] then error(("Usage: NewModule(name, [prototype, [lib, lib, lib, ...]): 'name' - Module '%s' already exists."):format(name), 2) end
-- modules are basically addons. We treat them as such. They will be added to the initializequeue properly as well.
-- NewModule can only be called after the parent addon is present thus the modules will be initialized after their parent is.
local module = AceAddon:NewAddon(fmt("%s_%s", self.name or tostring(self), name))
module.IsModule = IsModuleTrue
module:SetEnabledState(self.defaultModuleState)
module.moduleName = name
if type(prototype) == "string" then
AceAddon:EmbedLibraries(module, prototype, ...)
else
AceAddon:EmbedLibraries(module, ...)
end
AceAddon:EmbedLibraries(module, unpack(self.defaultModuleLibraries))
if not prototype or type(prototype) == "string" then
prototype = self.defaultModulePrototype or nil
end
if type(prototype) == "table" then
local mt = getmetatable(module)
mt.__index = prototype
setmetatable(module, mt) -- More of a Base class type feel.
end
safecall(self.OnModuleCreated, self, module) -- Was in Ace2 and I think it could be a cool thing to have handy.
self.modules[name] = module
tinsert(self.orderedModules, module)
return module
end
--- Returns the real name of the addon or module, without any prefix.
-- @name //addon//:GetName
-- @paramsig
-- @usage
-- print(MyAddon:GetName())
-- -- prints "MyAddon"
function GetName(self)
return self.moduleName or self.name
end
--- Enables the Addon, if possible, return true or false depending on success.
-- This internally calls AceAddon:EnableAddon(), thus dispatching a OnEnable callback
-- and enabling all modules of the addon (unless explicitly disabled).\\
-- :Enable() also sets the internal `enableState` variable to true
-- @name //addon//:Enable
-- @paramsig
-- @usage
-- -- Enable MyModule
-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon")
-- MyModule = MyAddon:GetModule("MyModule")
-- MyModule:Enable()
function Enable(self)
self:SetEnabledState(true)
-- nevcairiel 2013-04-27: don't enable an addon/module if its queued for init still
-- it'll be enabled after the init process
if not queuedForInitialization(self) then
return AceAddon:EnableAddon(self)
end
end
--- Disables the Addon, if possible, return true or false depending on success.
-- This internally calls AceAddon:DisableAddon(), thus dispatching a OnDisable callback
-- and disabling all modules of the addon.\\
-- :Disable() also sets the internal `enableState` variable to false
-- @name //addon//:Disable
-- @paramsig
-- @usage
-- -- Disable MyAddon
-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon")
-- MyAddon:Disable()
function Disable(self)
self:SetEnabledState(false)
return AceAddon:DisableAddon(self)
end
--- Enables the Module, if possible, return true or false depending on success.
-- Short-hand function that retrieves the module via `:GetModule` and calls `:Enable` on the module object.
-- @name //addon//:EnableModule
-- @paramsig name
-- @usage
-- -- Enable MyModule using :GetModule
-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon")
-- MyModule = MyAddon:GetModule("MyModule")
-- MyModule:Enable()
--
-- -- Enable MyModule using the short-hand
-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon")
-- MyAddon:EnableModule("MyModule")
function EnableModule(self, name)
local module = self:GetModule( name )
return module:Enable()
end
--- Disables the Module, if possible, return true or false depending on success.
-- Short-hand function that retrieves the module via `:GetModule` and calls `:Disable` on the module object.
-- @name //addon//:DisableModule
-- @paramsig name
-- @usage
-- -- Disable MyModule using :GetModule
-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon")
-- MyModule = MyAddon:GetModule("MyModule")
-- MyModule:Disable()
--
-- -- Disable MyModule using the short-hand
-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon")
-- MyAddon:DisableModule("MyModule")
function DisableModule(self, name)
local module = self:GetModule( name )
return module:Disable()
end
--- Set the default libraries to be mixed into all modules created by this object.
-- Note that you can only change the default module libraries before any module is created.
-- @name //addon//:SetDefaultModuleLibraries
-- @paramsig lib[, lib, ...]
-- @param lib List of libraries to embed into the addon
-- @usage
-- -- Create the addon object
-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon")
-- -- Configure default libraries for modules (all modules need AceEvent-3.0)
-- MyAddon:SetDefaultModuleLibraries("AceEvent-3.0")
-- -- Create a module
-- MyModule = MyAddon:NewModule("MyModule")
function SetDefaultModuleLibraries(self, ...)
if next(self.modules) then
error("Usage: SetDefaultModuleLibraries(...): cannot change the module defaults after a module has been registered.", 2)
end
self.defaultModuleLibraries = {...}
end
--- Set the default state in which new modules are being created.
-- Note that you can only change the default state before any module is created.
-- @name //addon//:SetDefaultModuleState
-- @paramsig state
-- @param state Default state for new modules, true for enabled, false for disabled
-- @usage
-- -- Create the addon object
-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon")
-- -- Set the default state to "disabled"
-- MyAddon:SetDefaultModuleState(false)
-- -- Create a module and explicilty enable it
-- MyModule = MyAddon:NewModule("MyModule")
-- MyModule:Enable()
function SetDefaultModuleState(self, state)
if next(self.modules) then
error("Usage: SetDefaultModuleState(state): cannot change the module defaults after a module has been registered.", 2)
end
self.defaultModuleState = state
end
--- Set the default prototype to use for new modules on creation.
-- Note that you can only change the default prototype before any module is created.
-- @name //addon//:SetDefaultModulePrototype
-- @paramsig prototype
-- @param prototype Default prototype for the new modules (table)
-- @usage
-- -- Define a prototype
-- local prototype = { OnEnable = function(self) print("OnEnable called!") end }
-- -- Set the default prototype
-- MyAddon:SetDefaultModulePrototype(prototype)
-- -- Create a module and explicitly Enable it
-- MyModule = MyAddon:NewModule("MyModule")
-- MyModule:Enable()
-- -- should print "OnEnable called!" now
-- @see NewModule
function SetDefaultModulePrototype(self, prototype)
if next(self.modules) then
error("Usage: SetDefaultModulePrototype(prototype): cannot change the module defaults after a module has been registered.", 2)
end
if type(prototype) ~= "table" then
error(("Usage: SetDefaultModulePrototype(prototype): 'prototype' - table expected got '%s'."):format(type(prototype)), 2)
end
self.defaultModulePrototype = prototype
end
--- Set the state of an addon or module
-- This should only be called before any enabling actually happend, e.g. in/before OnInitialize.
-- @name //addon//:SetEnabledState
-- @paramsig state
-- @param state the state of an addon or module (enabled=true, disabled=false)
function SetEnabledState(self, state)
self.enabledState = state
end
--- Return an iterator of all modules associated to the addon.
-- @name //addon//:IterateModules
-- @paramsig
-- @usage
-- -- Enable all modules
-- for name, module in MyAddon:IterateModules() do
-- module:Enable()
-- end
local function IterateModules(self) return pairs(self.modules) end
-- Returns an iterator of all embeds in the addon
-- @name //addon//:IterateEmbeds
-- @paramsig
local function IterateEmbeds(self) return pairs(AceAddon.embeds[self]) end
--- Query the enabledState of an addon.
-- @name //addon//:IsEnabled
-- @paramsig
-- @usage
-- if MyAddon:IsEnabled() then
-- MyAddon:Disable()
-- end
local function IsEnabled(self) return self.enabledState end
local mixins = {
NewModule = NewModule,
GetModule = GetModule,
Enable = Enable,
Disable = Disable,
EnableModule = EnableModule,
DisableModule = DisableModule,
IsEnabled = IsEnabled,
SetDefaultModuleLibraries = SetDefaultModuleLibraries,
SetDefaultModuleState = SetDefaultModuleState,
SetDefaultModulePrototype = SetDefaultModulePrototype,
SetEnabledState = SetEnabledState,
IterateModules = IterateModules,
IterateEmbeds = IterateEmbeds,
GetName = GetName,
}
local function IsModule(self) return false end
local pmixins = {
defaultModuleState = true,
enabledState = true,
IsModule = IsModule,
}
-- Embed( target )
-- target (object) - target object to embed aceaddon in
--
-- this is a local function specifically since it's meant to be only called internally
function Embed(target, skipPMixins)
for k, v in pairs(mixins) do
target[k] = v
end
if not skipPMixins then
for k, v in pairs(pmixins) do
target[k] = target[k] or v
end
end
end
-- - Initialize the addon after creation.
-- This function is only used internally during the ADDON_LOADED event
-- It will call the **OnInitialize** function on the addon object (if present),
-- and the **OnEmbedInitialize** function on all embeded libraries.
--
-- **Note:** Do not call this function manually, unless you're absolutely sure that you know what you are doing.
-- @param addon addon object to intialize
function AceAddon:InitializeAddon(addon)
safecall(addon.OnInitialize, addon)
local embeds = self.embeds[addon]
for i = 1, #embeds do
local lib = LibStub:GetLibrary(embeds[i], true)
if lib then safecall(lib.OnEmbedInitialize, lib, addon) end
end
-- we don't call InitializeAddon on modules specifically, this is handled
-- from the event handler and only done _once_
end
-- - Enable the addon after creation.
-- Note: This function is only used internally during the PLAYER_LOGIN event, or during ADDON_LOADED,
-- if IsLoggedIn() already returns true at that point, e.g. for LoD Addons.
-- It will call the **OnEnable** function on the addon object (if present),
-- and the **OnEmbedEnable** function on all embeded libraries.\\
-- This function does not toggle the enable state of the addon itself, and will return early if the addon is disabled.
--
-- **Note:** Do not call this function manually, unless you're absolutely sure that you know what you are doing.
-- Use :Enable on the addon itself instead.
-- @param addon addon object to enable
function AceAddon:EnableAddon(addon)
if type(addon) == "string" then addon = AceAddon:GetAddon(addon) end
if self.statuses[addon.name] or not addon.enabledState then return false end
-- set the statuses first, before calling the OnEnable. this allows for Disabling of the addon in OnEnable.
self.statuses[addon.name] = true
safecall(addon.OnEnable, addon)
-- make sure we're still enabled before continueing
if self.statuses[addon.name] then
local embeds = self.embeds[addon]
for i = 1, #embeds do
local lib = LibStub:GetLibrary(embeds[i], true)
if lib then safecall(lib.OnEmbedEnable, lib, addon) end
end
-- enable possible modules.
local modules = addon.orderedModules
for i = 1, #modules do
self:EnableAddon(modules[i])
end
end
return self.statuses[addon.name] -- return true if we're disabled
end
-- - Disable the addon
-- Note: This function is only used internally.
-- It will call the **OnDisable** function on the addon object (if present),
-- and the **OnEmbedDisable** function on all embeded libraries.\\
-- This function does not toggle the enable state of the addon itself, and will return early if the addon is still enabled.
--
-- **Note:** Do not call this function manually, unless you're absolutely sure that you know what you are doing.
-- Use :Disable on the addon itself instead.
-- @param addon addon object to enable
function AceAddon:DisableAddon(addon)
if type(addon) == "string" then addon = AceAddon:GetAddon(addon) end
if not self.statuses[addon.name] then return false end
-- set statuses first before calling OnDisable, this allows for aborting the disable in OnDisable.
self.statuses[addon.name] = false
safecall( addon.OnDisable, addon )
-- make sure we're still disabling...
if not self.statuses[addon.name] then
local embeds = self.embeds[addon]
for i = 1, #embeds do
local lib = LibStub:GetLibrary(embeds[i], true)
if lib then safecall(lib.OnEmbedDisable, lib, addon) end
end
-- disable possible modules.
local modules = addon.orderedModules
for i = 1, #modules do
self:DisableAddon(modules[i])
end
end
return not self.statuses[addon.name] -- return true if we're disabled
end
--- Get an iterator over all registered addons.
-- @usage
-- -- Print a list of all installed AceAddon's
-- for name, addon in AceAddon:IterateAddons() do
-- print("Addon: " .. name)
-- end
function AceAddon:IterateAddons() return pairs(self.addons) end
--- Get an iterator over the internal status registry.
-- @usage
-- -- Print a list of all enabled addons
-- for name, status in AceAddon:IterateAddonStatus() do
-- if status then
-- print("EnabledAddon: " .. name)
-- end
-- end
function AceAddon:IterateAddonStatus() return pairs(self.statuses) end
-- Following Iterators are deprecated, and their addon specific versions should be used
-- e.g. addon:IterateEmbeds() instead of :IterateEmbedsOnAddon(addon)
function AceAddon:IterateEmbedsOnAddon(addon) return pairs(self.embeds[addon]) end
function AceAddon:IterateModulesOfAddon(addon) return pairs(addon.modules) end
-- Event Handling
local function onEvent(this, event, arg1)
-- 2011-08-17 nevcairiel - ignore the load event of Blizzard_DebugTools, so a potential startup error isn't swallowed up
if (event == "ADDON_LOADED" and arg1 ~= "Blizzard_DebugTools") or event == "PLAYER_LOGIN" then
-- if a addon loads another addon, recursion could happen here, so we need to validate the table on every iteration
while(#AceAddon.initializequeue > 0) do
local addon = tremove(AceAddon.initializequeue, 1)
-- this might be an issue with recursion - TODO: validate
if event == "ADDON_LOADED" then addon.baseName = arg1 end
AceAddon:InitializeAddon(addon)
tinsert(AceAddon.enablequeue, addon)
end
if IsLoggedIn() then
while(#AceAddon.enablequeue > 0) do
local addon = tremove(AceAddon.enablequeue, 1)
AceAddon:EnableAddon(addon)
end
end
end
end
AceAddon.frame:RegisterEvent("ADDON_LOADED")
AceAddon.frame:RegisterEvent("PLAYER_LOGIN")
AceAddon.frame:SetScript("OnEvent", onEvent)
-- upgrade embeded
for name, addon in pairs(AceAddon.addons) do
Embed(addon, true)
end
-- 2010-10-27 nevcairiel - add new "orderedModules" table
if oldminor and oldminor < 10 then
for name, addon in pairs(AceAddon.addons) do
addon.orderedModules = {}
for module_name, module in pairs(addon.modules) do
tinsert(addon.orderedModules, module)
end
end
end

View File

@ -0,0 +1,4 @@
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
..\FrameXML\UI.xsd">
<Script file="AceAddon-3.0.lua" />
</Ui>

View File

@ -0,0 +1,301 @@
--- **AceComm-3.0** allows you to send messages of unlimited length over the addon comm channels.
-- It'll automatically split the messages into multiple parts and rebuild them on the receiving end.\\
-- **ChatThrottleLib** is of course being used to avoid being disconnected by the server.
--
-- **AceComm-3.0** can be embeded into your addon, either explicitly by calling AceComm:Embed(MyAddon) or by
-- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object
-- and can be accessed directly, without having to explicitly call AceComm itself.\\
-- It is recommended to embed AceComm, otherwise you'll have to specify a custom `self` on all calls you
-- make into AceComm.
-- @class file
-- @name AceComm-3.0
-- @release $Id: AceComm-3.0.lua 1161 2017-08-12 14:30:16Z funkydude $
--[[ AceComm-3.0
TODO: Time out old data rotting around from dead senders? Not a HUGE deal since the number of possible sender names is somewhat limited.
]]
local CallbackHandler = LibStub("CallbackHandler-1.0")
local CTL = assert(ChatThrottleLib, "AceComm-3.0 requires ChatThrottleLib")
local MAJOR, MINOR = "AceComm-3.0", 10
local AceComm,oldminor = LibStub:NewLibrary(MAJOR, MINOR)
if not AceComm then return end
-- Lua APIs
local type, next, pairs, tostring = type, next, pairs, tostring
local strsub, strfind = string.sub, string.find
local match = string.match
local tinsert, tconcat = table.insert, table.concat
local error, assert = error, assert
-- WoW APIs
local Ambiguate = Ambiguate
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
-- List them here for Mikk's FindGlobals script
-- GLOBALS: LibStub, DEFAULT_CHAT_FRAME, geterrorhandler, RegisterAddonMessagePrefix
AceComm.embeds = AceComm.embeds or {}
-- for my sanity and yours, let's give the message type bytes some names
local MSG_MULTI_FIRST = "\001"
local MSG_MULTI_NEXT = "\002"
local MSG_MULTI_LAST = "\003"
local MSG_ESCAPE = "\004"
-- remove old structures (pre WoW 4.0)
AceComm.multipart_origprefixes = nil
AceComm.multipart_reassemblers = nil
-- the multipart message spool: indexed by a combination of sender+distribution+
AceComm.multipart_spool = AceComm.multipart_spool or {}
--- Register for Addon Traffic on a specified prefix
-- @param prefix A printable character (\032-\255) classification of the message (typically AddonName or AddonNameEvent), max 16 characters
-- @param method Callback to call on message reception: Function reference, or method name (string) to call on self. Defaults to "OnCommReceived"
function AceComm:RegisterComm(prefix, method)
if method == nil then
method = "OnCommReceived"
end
if #prefix > 16 then -- TODO: 15?
error("AceComm:RegisterComm(prefix,method): prefix length is limited to 16 characters")
end
RegisterAddonMessagePrefix(prefix)
return AceComm._RegisterComm(self, prefix, method) -- created by CallbackHandler
end
local warnedPrefix=false
--- Send a message over the Addon Channel
-- @param prefix A printable character (\032-\255) classification of the message (typically AddonName or AddonNameEvent)
-- @param text Data to send, nils (\000) not allowed. Any length.
-- @param distribution Addon channel, e.g. "RAID", "GUILD", etc; see SendAddonMessage API
-- @param target Destination for some distributions; see SendAddonMessage API
-- @param prio OPTIONAL: ChatThrottleLib priority, "BULK", "NORMAL" or "ALERT". Defaults to "NORMAL".
-- @param callbackFn OPTIONAL: callback function to be called as each chunk is sent. receives 3 args: the user supplied arg (see next), the number of bytes sent so far, and the number of bytes total to send.
-- @param callbackArg: OPTIONAL: first arg to the callback function. nil will be passed if not specified.
function AceComm:SendCommMessage(prefix, text, distribution, target, prio, callbackFn, callbackArg)
prio = prio or "NORMAL" -- pasta's reference implementation had different prio for singlepart and multipart, but that's a very bad idea since that can easily lead to out-of-sequence delivery!
if not( type(prefix)=="string" and
type(text)=="string" and
type(distribution)=="string" and
(target==nil or type(target)=="string") and
(prio=="BULK" or prio=="NORMAL" or prio=="ALERT")
) then
error('Usage: SendCommMessage(addon, "prefix", "text", "distribution"[, "target"[, "prio"[, callbackFn, callbackarg]]])', 2)
end
local textlen = #text
local maxtextlen = 255 -- Yes, the max is 255 even if the dev post said 256. I tested. Char 256+ get silently truncated. /Mikk, 20110327
local queueName = prefix..distribution..(target or "")
local ctlCallback = nil
if callbackFn then
ctlCallback = function(sent)
return callbackFn(callbackArg, sent, textlen)
end
end
local forceMultipart
if match(text, "^[\001-\009]") then -- 4.1+: see if the first character is a control character
-- we need to escape the first character with a \004
if textlen+1 > maxtextlen then -- would we go over the size limit?
forceMultipart = true -- just make it multipart, no escape problems then
else
text = "\004" .. text
end
end
if not forceMultipart and textlen <= maxtextlen then
-- fits all in one message
CTL:SendAddonMessage(prio, prefix, text, distribution, target, queueName, ctlCallback, textlen)
else
maxtextlen = maxtextlen - 1 -- 1 extra byte for part indicator in prefix(4.0)/start of message(4.1)
-- first part
local chunk = strsub(text, 1, maxtextlen)
CTL:SendAddonMessage(prio, prefix, MSG_MULTI_FIRST..chunk, distribution, target, queueName, ctlCallback, maxtextlen)
-- continuation
local pos = 1+maxtextlen
while pos+maxtextlen <= textlen do
chunk = strsub(text, pos, pos+maxtextlen-1)
CTL:SendAddonMessage(prio, prefix, MSG_MULTI_NEXT..chunk, distribution, target, queueName, ctlCallback, pos+maxtextlen-1)
pos = pos + maxtextlen
end
-- final part
chunk = strsub(text, pos)
CTL:SendAddonMessage(prio, prefix, MSG_MULTI_LAST..chunk, distribution, target, queueName, ctlCallback, textlen)
end
end
----------------------------------------
-- Message receiving
----------------------------------------
do
local compost = setmetatable({}, {__mode = "k"})
local function new()
local t = next(compost)
if t then
compost[t]=nil
for i=#t,3,-1 do -- faster than pairs loop. don't even nil out 1/2 since they'll be overwritten
t[i]=nil
end
return t
end
return {}
end
local function lostdatawarning(prefix,sender,where)
DEFAULT_CHAT_FRAME:AddMessage(MAJOR..": Warning: lost network data regarding '"..tostring(prefix).."' from '"..tostring(sender).."' (in "..where..")")
end
function AceComm:OnReceiveMultipartFirst(prefix, message, distribution, sender)
local key = prefix.."\t"..distribution.."\t"..sender -- a unique stream is defined by the prefix + distribution + sender
local spool = AceComm.multipart_spool
--[[
if spool[key] then
lostdatawarning(prefix,sender,"First")
-- continue and overwrite
end
--]]
spool[key] = message -- plain string for now
end
function AceComm:OnReceiveMultipartNext(prefix, message, distribution, sender)
local key = prefix.."\t"..distribution.."\t"..sender -- a unique stream is defined by the prefix + distribution + sender
local spool = AceComm.multipart_spool
local olddata = spool[key]
if not olddata then
--lostdatawarning(prefix,sender,"Next")
return
end
if type(olddata)~="table" then
-- ... but what we have is not a table. So make it one. (Pull a composted one if available)
local t = new()
t[1] = olddata -- add old data as first string
t[2] = message -- and new message as second string
spool[key] = t -- and put the table in the spool instead of the old string
else
tinsert(olddata, message)
end
end
function AceComm:OnReceiveMultipartLast(prefix, message, distribution, sender)
local key = prefix.."\t"..distribution.."\t"..sender -- a unique stream is defined by the prefix + distribution + sender
local spool = AceComm.multipart_spool
local olddata = spool[key]
if not olddata then
--lostdatawarning(prefix,sender,"End")
return
end
spool[key] = nil
if type(olddata) == "table" then
-- if we've received a "next", the spooled data will be a table for rapid & garbage-free tconcat
tinsert(olddata, message)
AceComm.callbacks:Fire(prefix, tconcat(olddata, ""), distribution, sender)
compost[olddata] = true
else
-- if we've only received a "first", the spooled data will still only be a string
AceComm.callbacks:Fire(prefix, olddata..message, distribution, sender)
end
end
end
----------------------------------------
-- Embed CallbackHandler
----------------------------------------
if not AceComm.callbacks then
AceComm.callbacks = CallbackHandler:New(AceComm,
"_RegisterComm",
"UnregisterComm",
"UnregisterAllComm")
end
AceComm.callbacks.OnUsed = nil
AceComm.callbacks.OnUnused = nil
local function OnEvent(self, event, prefix, message, distribution, sender)
if event == "CHAT_MSG_ADDON" then
sender = Ambiguate(sender, "none")
local control, rest = match(message, "^([\001-\009])(.*)")
if control then
if control==MSG_MULTI_FIRST then
AceComm:OnReceiveMultipartFirst(prefix, rest, distribution, sender)
elseif control==MSG_MULTI_NEXT then
AceComm:OnReceiveMultipartNext(prefix, rest, distribution, sender)
elseif control==MSG_MULTI_LAST then
AceComm:OnReceiveMultipartLast(prefix, rest, distribution, sender)
elseif control==MSG_ESCAPE then
AceComm.callbacks:Fire(prefix, rest, distribution, sender)
else
-- unknown control character, ignore SILENTLY (dont warn unnecessarily about future extensions!)
end
else
-- single part: fire it off immediately and let CallbackHandler decide if it's registered or not
AceComm.callbacks:Fire(prefix, message, distribution, sender)
end
else
assert(false, "Received "..tostring(event).." event?!")
end
end
AceComm.frame = AceComm.frame or CreateFrame("Frame", "AceComm30Frame")
AceComm.frame:SetScript("OnEvent", OnEvent)
AceComm.frame:UnregisterAllEvents()
AceComm.frame:RegisterEvent("CHAT_MSG_ADDON")
----------------------------------------
-- Base library stuff
----------------------------------------
local mixins = {
"RegisterComm",
"UnregisterComm",
"UnregisterAllComm",
"SendCommMessage",
}
-- Embeds AceComm-3.0 into the target object making the functions from the mixins list available on target:..
-- @param target target object to embed AceComm-3.0 in
function AceComm:Embed(target)
for k, v in pairs(mixins) do
target[v] = self[v]
end
self.embeds[target] = true
return target
end
function AceComm:OnEmbedDisable(target)
target:UnregisterAllComm()
end
-- Update embeds
for target, v in pairs(AceComm.embeds) do
AceComm:Embed(target)
end

View File

@ -0,0 +1,5 @@
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
..\FrameXML\UI.xsd">
<Script file="ChatThrottleLib.lua" />
<Script file="AceComm-3.0.lua" />
</Ui>

View File

@ -0,0 +1,524 @@
--
-- ChatThrottleLib by Mikk
--
-- Manages AddOn chat output to keep player from getting kicked off.
--
-- ChatThrottleLib:SendChatMessage/:SendAddonMessage functions that accept
-- a Priority ("BULK", "NORMAL", "ALERT") as well as prefix for SendChatMessage.
--
-- Priorities get an equal share of available bandwidth when fully loaded.
-- Communication channels are separated on extension+chattype+destination and
-- get round-robinned. (Destination only matters for whispers and channels,
-- obviously)
--
-- Will install hooks for SendChatMessage and SendAddonMessage to measure
-- bandwidth bypassing the library and use less bandwidth itself.
--
--
-- Fully embeddable library. Just copy this file into your addon directory,
-- add it to the .toc, and it's done.
--
-- Can run as a standalone addon also, but, really, just embed it! :-)
--
-- LICENSE: ChatThrottleLib is released into the Public Domain
--
local CTL_VERSION = 23
local _G = _G
if _G.ChatThrottleLib then
if _G.ChatThrottleLib.version >= CTL_VERSION then
-- There's already a newer (or same) version loaded. Buh-bye.
return
elseif not _G.ChatThrottleLib.securelyHooked then
print("ChatThrottleLib: Warning: There's an ANCIENT ChatThrottleLib.lua (pre-wow 2.0, <v16) in an addon somewhere. Get the addon updated or copy in a newer ChatThrottleLib.lua (>=v16) in it!")
-- ATTEMPT to unhook; this'll behave badly if someone else has hooked...
-- ... and if someone has securehooked, they can kiss that goodbye too... >.<
_G.SendChatMessage = _G.ChatThrottleLib.ORIG_SendChatMessage
if _G.ChatThrottleLib.ORIG_SendAddonMessage then
_G.SendAddonMessage = _G.ChatThrottleLib.ORIG_SendAddonMessage
end
end
_G.ChatThrottleLib.ORIG_SendChatMessage = nil
_G.ChatThrottleLib.ORIG_SendAddonMessage = nil
end
if not _G.ChatThrottleLib then
_G.ChatThrottleLib = {}
end
ChatThrottleLib = _G.ChatThrottleLib -- in case some addon does "local ChatThrottleLib" above us and we're copypasted (AceComm-2, sigh)
local ChatThrottleLib = _G.ChatThrottleLib
ChatThrottleLib.version = CTL_VERSION
------------------ TWEAKABLES -----------------
ChatThrottleLib.MAX_CPS = 800 -- 2000 seems to be safe if NOTHING ELSE is happening. let's call it 800.
ChatThrottleLib.MSG_OVERHEAD = 40 -- Guesstimate overhead for sending a message; source+dest+chattype+protocolstuff
ChatThrottleLib.BURST = 4000 -- WoW's server buffer seems to be about 32KB. 8KB should be safe, but seen disconnects on _some_ servers. Using 4KB now.
ChatThrottleLib.MIN_FPS = 20 -- Reduce output CPS to half (and don't burst) if FPS drops below this value
local setmetatable = setmetatable
local table_remove = table.remove
local tostring = tostring
local GetTime = GetTime
local math_min = math.min
local math_max = math.max
local next = next
local strlen = string.len
local GetFramerate = GetFramerate
local strlower = string.lower
local unpack,type,pairs,wipe = unpack,type,pairs,wipe
local UnitInRaid,UnitInParty = UnitInRaid,UnitInParty
-----------------------------------------------------------------------
-- Double-linked ring implementation
local Ring = {}
local RingMeta = { __index = Ring }
function Ring:New()
local ret = {}
setmetatable(ret, RingMeta)
return ret
end
function Ring:Add(obj) -- Append at the "far end" of the ring (aka just before the current position)
if self.pos then
obj.prev = self.pos.prev
obj.prev.next = obj
obj.next = self.pos
obj.next.prev = obj
else
obj.next = obj
obj.prev = obj
self.pos = obj
end
end
function Ring:Remove(obj)
obj.next.prev = obj.prev
obj.prev.next = obj.next
if self.pos == obj then
self.pos = obj.next
if self.pos == obj then
self.pos = nil
end
end
end
-----------------------------------------------------------------------
-- Recycling bin for pipes
-- A pipe is a plain integer-indexed queue of messages
-- Pipes normally live in Rings of pipes (3 rings total, one per priority)
ChatThrottleLib.PipeBin = nil -- pre-v19, drastically different
local PipeBin = setmetatable({}, {__mode="k"})
local function DelPipe(pipe)
PipeBin[pipe] = true
end
local function NewPipe()
local pipe = next(PipeBin)
if pipe then
wipe(pipe)
PipeBin[pipe] = nil
return pipe
end
return {}
end
-----------------------------------------------------------------------
-- Recycling bin for messages
ChatThrottleLib.MsgBin = nil -- pre-v19, drastically different
local MsgBin = setmetatable({}, {__mode="k"})
local function DelMsg(msg)
msg[1] = nil
-- there's more parameters, but they're very repetetive so the string pool doesn't suffer really, and it's faster to just not delete them.
MsgBin[msg] = true
end
local function NewMsg()
local msg = next(MsgBin)
if msg then
MsgBin[msg] = nil
return msg
end
return {}
end
-----------------------------------------------------------------------
-- ChatThrottleLib:Init
-- Initialize queues, set up frame for OnUpdate, etc
function ChatThrottleLib:Init()
-- Set up queues
if not self.Prio then
self.Prio = {}
self.Prio["ALERT"] = { ByName = {}, Ring = Ring:New(), avail = 0 }
self.Prio["NORMAL"] = { ByName = {}, Ring = Ring:New(), avail = 0 }
self.Prio["BULK"] = { ByName = {}, Ring = Ring:New(), avail = 0 }
end
-- v4: total send counters per priority
for _, Prio in pairs(self.Prio) do
Prio.nTotalSent = Prio.nTotalSent or 0
end
if not self.avail then
self.avail = 0 -- v5
end
if not self.nTotalSent then
self.nTotalSent = 0 -- v5
end
-- Set up a frame to get OnUpdate events
if not self.Frame then
self.Frame = CreateFrame("Frame")
self.Frame:Hide()
end
self.Frame:SetScript("OnUpdate", self.OnUpdate)
self.Frame:SetScript("OnEvent", self.OnEvent) -- v11: Monitor P_E_W so we can throttle hard for a few seconds
self.Frame:RegisterEvent("PLAYER_ENTERING_WORLD")
self.OnUpdateDelay = 0
self.LastAvailUpdate = GetTime()
self.HardThrottlingBeginTime = GetTime() -- v11: Throttle hard for a few seconds after startup
-- Hook SendChatMessage and SendAddonMessage so we can measure unpiped traffic and avoid overloads (v7)
if not self.securelyHooked then
-- Use secure hooks as of v16. Old regular hook support yanked out in v21.
self.securelyHooked = true
--SendChatMessage
hooksecurefunc("SendChatMessage", function(...)
return ChatThrottleLib.Hook_SendChatMessage(...)
end)
--SendAddonMessage
hooksecurefunc("SendAddonMessage", function(...)
return ChatThrottleLib.Hook_SendAddonMessage(...)
end)
end
self.nBypass = 0
end
-----------------------------------------------------------------------
-- ChatThrottleLib.Hook_SendChatMessage / .Hook_SendAddonMessage
local bMyTraffic = false
function ChatThrottleLib.Hook_SendChatMessage(text, chattype, language, destination, ...)
if bMyTraffic then
return
end
local self = ChatThrottleLib
local size = strlen(tostring(text or "")) + strlen(tostring(destination or "")) + self.MSG_OVERHEAD
self.avail = self.avail - size
self.nBypass = self.nBypass + size -- just a statistic
end
function ChatThrottleLib.Hook_SendAddonMessage(prefix, text, chattype, destination, ...)
if bMyTraffic then
return
end
local self = ChatThrottleLib
local size = tostring(text or ""):len() + tostring(prefix or ""):len();
size = size + tostring(destination or ""):len() + self.MSG_OVERHEAD
self.avail = self.avail - size
self.nBypass = self.nBypass + size -- just a statistic
end
-----------------------------------------------------------------------
-- ChatThrottleLib:UpdateAvail
-- Update self.avail with how much bandwidth is currently available
function ChatThrottleLib:UpdateAvail()
local now = GetTime()
local MAX_CPS = self.MAX_CPS;
local newavail = MAX_CPS * (now - self.LastAvailUpdate)
local avail = self.avail
if now - self.HardThrottlingBeginTime < 5 then
-- First 5 seconds after startup/zoning: VERY hard clamping to avoid irritating the server rate limiter, it seems very cranky then
avail = math_min(avail + (newavail*0.1), MAX_CPS*0.5)
self.bChoking = true
elseif GetFramerate() < self.MIN_FPS then -- GetFrameRate call takes ~0.002 secs
avail = math_min(MAX_CPS, avail + newavail*0.5)
self.bChoking = true -- just a statistic
else
avail = math_min(self.BURST, avail + newavail)
self.bChoking = false
end
avail = math_max(avail, 0-(MAX_CPS*2)) -- Can go negative when someone is eating bandwidth past the lib. but we refuse to stay silent for more than 2 seconds; if they can do it, we can.
self.avail = avail
self.LastAvailUpdate = now
return avail
end
-----------------------------------------------------------------------
-- Despooling logic
-- Reminder:
-- - We have 3 Priorities, each containing a "Ring" construct ...
-- - ... made up of N "Pipe"s (1 for each destination/pipename)
-- - and each pipe contains messages
function ChatThrottleLib:Despool(Prio)
local ring = Prio.Ring
while ring.pos and Prio.avail > ring.pos[1].nSize do
local msg = table_remove(ring.pos, 1)
if not ring.pos[1] then -- did we remove last msg in this pipe?
local pipe = Prio.Ring.pos
Prio.Ring:Remove(pipe)
Prio.ByName[pipe.name] = nil
DelPipe(pipe)
else
Prio.Ring.pos = Prio.Ring.pos.next
end
local didSend=false
local lowerDest = strlower(msg[3] or "")
if lowerDest == "raid" and not UnitInRaid("player") then
-- do nothing
elseif lowerDest == "party" and not UnitInParty("player") then
-- do nothing
else
Prio.avail = Prio.avail - msg.nSize
bMyTraffic = true
msg.f(unpack(msg, 1, msg.n))
bMyTraffic = false
Prio.nTotalSent = Prio.nTotalSent + msg.nSize
DelMsg(msg)
didSend = true
end
-- notify caller of delivery (even if we didn't send it)
if msg.callbackFn then
msg.callbackFn (msg.callbackArg, didSend)
end
-- USER CALLBACK MAY ERROR
end
end
function ChatThrottleLib.OnEvent(this,event)
-- v11: We know that the rate limiter is touchy after login. Assume that it's touchy after zoning, too.
local self = ChatThrottleLib
if event == "PLAYER_ENTERING_WORLD" then
self.HardThrottlingBeginTime = GetTime() -- Throttle hard for a few seconds after zoning
self.avail = 0
end
end
function ChatThrottleLib.OnUpdate(this,delay)
local self = ChatThrottleLib
self.OnUpdateDelay = self.OnUpdateDelay + delay
if self.OnUpdateDelay < 0.08 then
return
end
self.OnUpdateDelay = 0
self:UpdateAvail()
if self.avail < 0 then
return -- argh. some bastard is spewing stuff past the lib. just bail early to save cpu.
end
-- See how many of our priorities have queued messages (we only have 3, don't worry about the loop)
local n = 0
for prioname,Prio in pairs(self.Prio) do
if Prio.Ring.pos or Prio.avail < 0 then
n = n + 1
end
end
-- Anything queued still?
if n<1 then
-- Nope. Move spillover bandwidth to global availability gauge and clear self.bQueueing
for prioname, Prio in pairs(self.Prio) do
self.avail = self.avail + Prio.avail
Prio.avail = 0
end
self.bQueueing = false
self.Frame:Hide()
return
end
-- There's stuff queued. Hand out available bandwidth to priorities as needed and despool their queues
local avail = self.avail/n
self.avail = 0
for prioname, Prio in pairs(self.Prio) do
if Prio.Ring.pos or Prio.avail < 0 then
Prio.avail = Prio.avail + avail
if Prio.Ring.pos and Prio.avail > Prio.Ring.pos[1].nSize then
self:Despool(Prio)
-- Note: We might not get here if the user-supplied callback function errors out! Take care!
end
end
end
end
-----------------------------------------------------------------------
-- Spooling logic
function ChatThrottleLib:Enqueue(prioname, pipename, msg)
local Prio = self.Prio[prioname]
local pipe = Prio.ByName[pipename]
if not pipe then
self.Frame:Show()
pipe = NewPipe()
pipe.name = pipename
Prio.ByName[pipename] = pipe
Prio.Ring:Add(pipe)
end
pipe[#pipe + 1] = msg
self.bQueueing = true
end
function ChatThrottleLib:SendChatMessage(prio, prefix, text, chattype, language, destination, queueName, callbackFn, callbackArg)
if not self or not prio or not prefix or not text or not self.Prio[prio] then
error('Usage: ChatThrottleLib:SendChatMessage("{BULK||NORMAL||ALERT}", "prefix", "text"[, "chattype"[, "language"[, "destination"]]]', 2)
end
if callbackFn and type(callbackFn)~="function" then
error('ChatThrottleLib:ChatMessage(): callbackFn: expected function, got '..type(callbackFn), 2)
end
local nSize = text:len()
if nSize>255 then
error("ChatThrottleLib:SendChatMessage(): message length cannot exceed 255 bytes", 2)
end
nSize = nSize + self.MSG_OVERHEAD
-- Check if there's room in the global available bandwidth gauge to send directly
if not self.bQueueing and nSize < self:UpdateAvail() then
self.avail = self.avail - nSize
bMyTraffic = true
_G.SendChatMessage(text, chattype, language, destination)
bMyTraffic = false
self.Prio[prio].nTotalSent = self.Prio[prio].nTotalSent + nSize
if callbackFn then
callbackFn (callbackArg, true)
end
-- USER CALLBACK MAY ERROR
return
end
-- Message needs to be queued
local msg = NewMsg()
msg.f = _G.SendChatMessage
msg[1] = text
msg[2] = chattype or "SAY"
msg[3] = language
msg[4] = destination
msg.n = 4
msg.nSize = nSize
msg.callbackFn = callbackFn
msg.callbackArg = callbackArg
self:Enqueue(prio, queueName or (prefix..(chattype or "SAY")..(destination or "")), msg)
end
function ChatThrottleLib:SendAddonMessage(prio, prefix, text, chattype, target, queueName, callbackFn, callbackArg)
if not self or not prio or not prefix or not text or not chattype or not self.Prio[prio] then
error('Usage: ChatThrottleLib:SendAddonMessage("{BULK||NORMAL||ALERT}", "prefix", "text", "chattype"[, "target"])', 2)
end
if callbackFn and type(callbackFn)~="function" then
error('ChatThrottleLib:SendAddonMessage(): callbackFn: expected function, got '..type(callbackFn), 2)
end
local nSize = text:len();
if RegisterAddonMessagePrefix then
if nSize>255 then
error("ChatThrottleLib:SendAddonMessage(): message length cannot exceed 255 bytes", 2)
end
else
nSize = nSize + prefix:len() + 1
if nSize>255 then
error("ChatThrottleLib:SendAddonMessage(): prefix + message length cannot exceed 254 bytes", 2)
end
end
nSize = nSize + self.MSG_OVERHEAD;
-- Check if there's room in the global available bandwidth gauge to send directly
if not self.bQueueing and nSize < self:UpdateAvail() then
self.avail = self.avail - nSize
bMyTraffic = true
_G.SendAddonMessage(prefix, text, chattype, target)
bMyTraffic = false
self.Prio[prio].nTotalSent = self.Prio[prio].nTotalSent + nSize
if callbackFn then
callbackFn (callbackArg, true)
end
-- USER CALLBACK MAY ERROR
return
end
-- Message needs to be queued
local msg = NewMsg()
msg.f = _G.SendAddonMessage
msg[1] = prefix
msg[2] = text
msg[3] = chattype
msg[4] = target
msg.n = (target~=nil) and 4 or 3;
msg.nSize = nSize
msg.callbackFn = callbackFn
msg.callbackArg = callbackArg
self:Enqueue(prio, queueName or (prefix..chattype..(target or "")), msg)
end
-----------------------------------------------------------------------
-- Get the ball rolling!
ChatThrottleLib:Init()
--[[ WoWBench debugging snippet
if(WOWB_VER) then
local function SayTimer()
print("SAY: "..GetTime().." "..arg1)
end
ChatThrottleLib.Frame:SetScript("OnEvent", SayTimer)
ChatThrottleLib.Frame:RegisterEvent("CHAT_MSG_SAY")
end
]]

View File

@ -0,0 +1,250 @@
--- **AceConsole-3.0** provides registration facilities for slash commands.
-- You can register slash commands to your custom functions and use the `GetArgs` function to parse them
-- to your addons individual needs.
--
-- **AceConsole-3.0** can be embeded into your addon, either explicitly by calling AceConsole:Embed(MyAddon) or by
-- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object
-- and can be accessed directly, without having to explicitly call AceConsole itself.\\
-- It is recommended to embed AceConsole, otherwise you'll have to specify a custom `self` on all calls you
-- make into AceConsole.
-- @class file
-- @name AceConsole-3.0
-- @release $Id: AceConsole-3.0.lua 1143 2016-07-11 08:52:03Z nevcairiel $
local MAJOR,MINOR = "AceConsole-3.0", 7
local AceConsole, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
if not AceConsole then return end -- No upgrade needed
AceConsole.embeds = AceConsole.embeds or {} -- table containing objects AceConsole is embedded in.
AceConsole.commands = AceConsole.commands or {} -- table containing commands registered
AceConsole.weakcommands = AceConsole.weakcommands or {} -- table containing self, command => func references for weak commands that don't persist through enable/disable
-- Lua APIs
local tconcat, tostring, select = table.concat, tostring, select
local type, pairs, error = type, pairs, error
local format, strfind, strsub = string.format, string.find, string.sub
local max = math.max
-- WoW APIs
local _G = _G
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
-- List them here for Mikk's FindGlobals script
-- GLOBALS: DEFAULT_CHAT_FRAME, SlashCmdList, hash_SlashCmdList
local tmp={}
local function Print(self,frame,...)
local n=0
if self ~= AceConsole then
n=n+1
tmp[n] = "|cff33ff99"..tostring( self ).."|r:"
end
for i=1, select("#", ...) do
n=n+1
tmp[n] = tostring(select(i, ...))
end
frame:AddMessage( tconcat(tmp," ",1,n) )
end
--- Print to DEFAULT_CHAT_FRAME or given ChatFrame (anything with an .AddMessage function)
-- @paramsig [chatframe ,] ...
-- @param chatframe Custom ChatFrame to print to (or any frame with an .AddMessage function)
-- @param ... List of any values to be printed
function AceConsole:Print(...)
local frame = ...
if type(frame) == "table" and frame.AddMessage then -- Is first argument something with an .AddMessage member?
return Print(self, frame, select(2,...))
else
return Print(self, DEFAULT_CHAT_FRAME, ...)
end
end
--- Formatted (using format()) print to DEFAULT_CHAT_FRAME or given ChatFrame (anything with an .AddMessage function)
-- @paramsig [chatframe ,] "format"[, ...]
-- @param chatframe Custom ChatFrame to print to (or any frame with an .AddMessage function)
-- @param format Format string - same syntax as standard Lua format()
-- @param ... Arguments to the format string
function AceConsole:Printf(...)
local frame = ...
if type(frame) == "table" and frame.AddMessage then -- Is first argument something with an .AddMessage member?
return Print(self, frame, format(select(2,...)))
else
return Print(self, DEFAULT_CHAT_FRAME, format(...))
end
end
--- Register a simple chat command
-- @param command Chat command to be registered WITHOUT leading "/"
-- @param func Function to call when the slash command is being used (funcref or methodname)
-- @param persist if false, the command will be soft disabled/enabled when aceconsole is used as a mixin (default: true)
function AceConsole:RegisterChatCommand( command, func, persist )
if type(command)~="string" then error([[Usage: AceConsole:RegisterChatCommand( "command", func[, persist ]): 'command' - expected a string]], 2) end
if persist==nil then persist=true end -- I'd rather have my addon's "/addon enable" around if the author screws up. Having some extra slash regged when it shouldnt be isn't as destructive. True is a better default. /Mikk
local name = "ACECONSOLE_"..command:upper()
if type( func ) == "string" then
SlashCmdList[name] = function(input, editBox)
self[func](self, input, editBox)
end
else
SlashCmdList[name] = func
end
_G["SLASH_"..name.."1"] = "/"..command:lower()
AceConsole.commands[command] = name
-- non-persisting commands are registered for enabling disabling
if not persist then
if not AceConsole.weakcommands[self] then AceConsole.weakcommands[self] = {} end
AceConsole.weakcommands[self][command] = func
end
return true
end
--- Unregister a chatcommand
-- @param command Chat command to be unregistered WITHOUT leading "/"
function AceConsole:UnregisterChatCommand( command )
local name = AceConsole.commands[command]
if name then
SlashCmdList[name] = nil
_G["SLASH_" .. name .. "1"] = nil
hash_SlashCmdList["/" .. command:upper()] = nil
AceConsole.commands[command] = nil
end
end
--- Get an iterator over all Chat Commands registered with AceConsole
-- @return Iterator (pairs) over all commands
function AceConsole:IterateChatCommands() return pairs(AceConsole.commands) end
local function nils(n, ...)
if n>1 then
return nil, nils(n-1, ...)
elseif n==1 then
return nil, ...
else
return ...
end
end
--- Retreive one or more space-separated arguments from a string.
-- Treats quoted strings and itemlinks as non-spaced.
-- @param str The raw argument string
-- @param numargs How many arguments to get (default 1)
-- @param startpos Where in the string to start scanning (default 1)
-- @return Returns arg1, arg2, ..., nextposition\\
-- Missing arguments will be returned as nils. 'nextposition' is returned as 1e9 at the end of the string.
function AceConsole:GetArgs(str, numargs, startpos)
numargs = numargs or 1
startpos = max(startpos or 1, 1)
local pos=startpos
-- find start of new arg
pos = strfind(str, "[^ ]", pos)
if not pos then -- whoops, end of string
return nils(numargs, 1e9)
end
if numargs<1 then
return pos
end
-- quoted or space separated? find out which pattern to use
local delim_or_pipe
local ch = strsub(str, pos, pos)
if ch=='"' then
pos = pos + 1
delim_or_pipe='([|"])'
elseif ch=="'" then
pos = pos + 1
delim_or_pipe="([|'])"
else
delim_or_pipe="([| ])"
end
startpos = pos
while true do
-- find delimiter or hyperlink
local ch,_
pos,_,ch = strfind(str, delim_or_pipe, pos)
if not pos then break end
if ch=="|" then
-- some kind of escape
if strsub(str,pos,pos+1)=="|H" then
-- It's a |H....|hhyper link!|h
pos=strfind(str, "|h", pos+2) -- first |h
if not pos then break end
pos=strfind(str, "|h", pos+2) -- second |h
if not pos then break end
elseif strsub(str,pos, pos+1) == "|T" then
-- It's a |T....|t texture
pos=strfind(str, "|t", pos+2)
if not pos then break end
end
pos=pos+2 -- skip past this escape (last |h if it was a hyperlink)
else
-- found delimiter, done with this arg
return strsub(str, startpos, pos-1), AceConsole:GetArgs(str, numargs-1, pos+1)
end
end
-- search aborted, we hit end of string. return it all as one argument. (yes, even if it's an unterminated quote or hyperlink)
return strsub(str, startpos), nils(numargs-1, 1e9)
end
--- embedding and embed handling
local mixins = {
"Print",
"Printf",
"RegisterChatCommand",
"UnregisterChatCommand",
"GetArgs",
}
-- Embeds AceConsole into the target object making the functions from the mixins list available on target:..
-- @param target target object to embed AceBucket in
function AceConsole:Embed( target )
for k, v in pairs( mixins ) do
target[v] = self[v]
end
self.embeds[target] = true
return target
end
function AceConsole:OnEmbedEnable( target )
if AceConsole.weakcommands[target] then
for command, func in pairs( AceConsole.weakcommands[target] ) do
target:RegisterChatCommand( command, func, false, true ) -- nonpersisting and silent registry
end
end
end
function AceConsole:OnEmbedDisable( target )
if AceConsole.weakcommands[target] then
for command, func in pairs( AceConsole.weakcommands[target] ) do
target:UnregisterChatCommand( command ) -- TODO: this could potentially unregister a command from another application in case of command conflicts. Do we care?
end
end
end
for addon in pairs(AceConsole.embeds) do
AceConsole:Embed(addon)
end

View File

@ -0,0 +1,4 @@
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
..\FrameXML\UI.xsd">
<Script file="AceConsole-3.0.lua" />
</Ui>

View File

@ -0,0 +1,126 @@
--- AceEvent-3.0 provides event registration and secure dispatching.
-- All dispatching is done using **CallbackHandler-1.0**. AceEvent is a simple wrapper around
-- CallbackHandler, and dispatches all game events or addon message to the registrees.
--
-- **AceEvent-3.0** can be embeded into your addon, either explicitly by calling AceEvent:Embed(MyAddon) or by
-- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object
-- and can be accessed directly, without having to explicitly call AceEvent itself.\\
-- It is recommended to embed AceEvent, otherwise you'll have to specify a custom `self` on all calls you
-- make into AceEvent.
-- @class file
-- @name AceEvent-3.0
-- @release $Id: AceEvent-3.0.lua 1161 2017-08-12 14:30:16Z funkydude $
local CallbackHandler = LibStub("CallbackHandler-1.0")
local MAJOR, MINOR = "AceEvent-3.0", 4
local AceEvent = LibStub:NewLibrary(MAJOR, MINOR)
if not AceEvent then return end
-- Lua APIs
local pairs = pairs
AceEvent.frame = AceEvent.frame or CreateFrame("Frame", "AceEvent30Frame") -- our event frame
AceEvent.embeds = AceEvent.embeds or {} -- what objects embed this lib
-- APIs and registry for blizzard events, using CallbackHandler lib
if not AceEvent.events then
AceEvent.events = CallbackHandler:New(AceEvent,
"RegisterEvent", "UnregisterEvent", "UnregisterAllEvents")
end
function AceEvent.events:OnUsed(target, eventname)
AceEvent.frame:RegisterEvent(eventname)
end
function AceEvent.events:OnUnused(target, eventname)
AceEvent.frame:UnregisterEvent(eventname)
end
-- APIs and registry for IPC messages, using CallbackHandler lib
if not AceEvent.messages then
AceEvent.messages = CallbackHandler:New(AceEvent,
"RegisterMessage", "UnregisterMessage", "UnregisterAllMessages"
)
AceEvent.SendMessage = AceEvent.messages.Fire
end
--- embedding and embed handling
local mixins = {
"RegisterEvent", "UnregisterEvent",
"RegisterMessage", "UnregisterMessage",
"SendMessage",
"UnregisterAllEvents", "UnregisterAllMessages",
}
--- Register for a Blizzard Event.
-- The callback will be called with the optional `arg` as the first argument (if supplied), and the event name as the second (or first, if no arg was supplied)
-- Any arguments to the event will be passed on after that.
-- @name AceEvent:RegisterEvent
-- @class function
-- @paramsig event[, callback [, arg]]
-- @param event The event to register for
-- @param callback The callback function to call when the event is triggered (funcref or method, defaults to a method with the event name)
-- @param arg An optional argument to pass to the callback function
--- Unregister an event.
-- @name AceEvent:UnregisterEvent
-- @class function
-- @paramsig event
-- @param event The event to unregister
--- Register for a custom AceEvent-internal message.
-- The callback will be called with the optional `arg` as the first argument (if supplied), and the event name as the second (or first, if no arg was supplied)
-- Any arguments to the event will be passed on after that.
-- @name AceEvent:RegisterMessage
-- @class function
-- @paramsig message[, callback [, arg]]
-- @param message The message to register for
-- @param callback The callback function to call when the message is triggered (funcref or method, defaults to a method with the event name)
-- @param arg An optional argument to pass to the callback function
--- Unregister a message
-- @name AceEvent:UnregisterMessage
-- @class function
-- @paramsig message
-- @param message The message to unregister
--- Send a message over the AceEvent-3.0 internal message system to other addons registered for this message.
-- @name AceEvent:SendMessage
-- @class function
-- @paramsig message, ...
-- @param message The message to send
-- @param ... Any arguments to the message
-- Embeds AceEvent into the target object making the functions from the mixins list available on target:..
-- @param target target object to embed AceEvent in
function AceEvent:Embed(target)
for k, v in pairs(mixins) do
target[v] = self[v]
end
self.embeds[target] = true
return target
end
-- AceEvent:OnEmbedDisable( target )
-- target (object) - target object that is being disabled
--
-- Unregister all events messages etc when the target disables.
-- this method should be called by the target manually or by an addon framework
function AceEvent:OnEmbedDisable(target)
target:UnregisterAllEvents()
target:UnregisterAllMessages()
end
-- Script to fire blizzard events into the event listeners
local events = AceEvent.events
AceEvent.frame:SetScript("OnEvent", function(this, event, ...)
events:Fire(event, ...)
end)
--- Finally: upgrade our old embeds
for target, v in pairs(AceEvent.embeds) do
AceEvent:Embed(target)
end

View File

@ -0,0 +1,4 @@
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
..\FrameXML\UI.xsd">
<Script file="AceEvent-3.0.lua" />
</Ui>

View File

@ -0,0 +1,276 @@
--- **AceTimer-3.0** provides a central facility for registering timers.
-- AceTimer supports one-shot timers and repeating timers. All timers are stored in an efficient
-- data structure that allows easy dispatching and fast rescheduling. Timers can be registered
-- or canceled at any time, even from within a running timer, without conflict or large overhead.\\
-- AceTimer is currently limited to firing timers at a frequency of 0.01s as this is what the WoW timer API
-- restricts us to.
--
-- All `:Schedule` functions will return a handle to the current timer, which you will need to store if you
-- need to cancel the timer you just registered.
--
-- **AceTimer-3.0** can be embeded into your addon, either explicitly by calling AceTimer:Embed(MyAddon) or by
-- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object
-- and can be accessed directly, without having to explicitly call AceTimer itself.\\
-- It is recommended to embed AceTimer, otherwise you'll have to specify a custom `self` on all calls you
-- make into AceTimer.
-- @class file
-- @name AceTimer-3.0
-- @release $Id: AceTimer-3.0.lua 1119 2014-10-14 17:23:29Z nevcairiel $
local MAJOR, MINOR = "AceTimer-3.0", 17 -- Bump minor on changes
local AceTimer, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
if not AceTimer then return end -- No upgrade needed
AceTimer.activeTimers = AceTimer.activeTimers or {} -- Active timer list
local activeTimers = AceTimer.activeTimers -- Upvalue our private data
-- Lua APIs
local type, unpack, next, error, select = type, unpack, next, error, select
-- WoW APIs
local GetTime, C_TimerAfter = GetTime, C_Timer.After
local function new(self, loop, func, delay, ...)
if delay < 0.01 then
delay = 0.01 -- Restrict to the lowest time that the C_Timer API allows us
end
local timer = {...}
timer.object = self
timer.func = func
timer.looping = loop
timer.argsCount = select("#", ...)
timer.delay = delay
timer.ends = GetTime() + delay
activeTimers[timer] = timer
-- Create new timer closure to wrap the "timer" object
timer.callback = function()
if not timer.cancelled then
if type(timer.func) == "string" then
-- We manually set the unpack count to prevent issues with an arg set that contains nil and ends with nil
-- e.g. local t = {1, 2, nil, 3, nil} print(#t) will result in 2, instead of 5. This fixes said issue.
timer.object[timer.func](timer.object, unpack(timer, 1, timer.argsCount))
else
timer.func(unpack(timer, 1, timer.argsCount))
end
if timer.looping and not timer.cancelled then
-- Compensate delay to get a perfect average delay, even if individual times don't match up perfectly
-- due to fps differences
local time = GetTime()
local delay = timer.delay - (time - timer.ends)
-- Ensure the delay doesn't go below the threshold
if delay < 0.01 then delay = 0.01 end
C_TimerAfter(delay, timer.callback)
timer.ends = time + delay
else
activeTimers[timer.handle or timer] = nil
end
end
end
C_TimerAfter(delay, timer.callback)
return timer
end
--- Schedule a new one-shot timer.
-- The timer will fire once in `delay` seconds, unless canceled before.
-- @param callback Callback function for the timer pulse (funcref or method name).
-- @param delay Delay for the timer, in seconds.
-- @param ... An optional, unlimited amount of arguments to pass to the callback function.
-- @usage
-- MyAddOn = LibStub("AceAddon-3.0"):NewAddon("MyAddOn", "AceTimer-3.0")
--
-- function MyAddOn:OnEnable()
-- self:ScheduleTimer("TimerFeedback", 5)
-- end
--
-- function MyAddOn:TimerFeedback()
-- print("5 seconds passed")
-- end
function AceTimer:ScheduleTimer(func, delay, ...)
if not func or not delay then
error(MAJOR..": ScheduleTimer(callback, delay, args...): 'callback' and 'delay' must have set values.", 2)
end
if type(func) == "string" then
if type(self) ~= "table" then
error(MAJOR..": ScheduleTimer(callback, delay, args...): 'self' - must be a table.", 2)
elseif not self[func] then
error(MAJOR..": ScheduleTimer(callback, delay, args...): Tried to register '"..func.."' as the callback, but it doesn't exist in the module.", 2)
end
end
return new(self, nil, func, delay, ...)
end
--- Schedule a repeating timer.
-- The timer will fire every `delay` seconds, until canceled.
-- @param callback Callback function for the timer pulse (funcref or method name).
-- @param delay Delay for the timer, in seconds.
-- @param ... An optional, unlimited amount of arguments to pass to the callback function.
-- @usage
-- MyAddOn = LibStub("AceAddon-3.0"):NewAddon("MyAddOn", "AceTimer-3.0")
--
-- function MyAddOn:OnEnable()
-- self.timerCount = 0
-- self.testTimer = self:ScheduleRepeatingTimer("TimerFeedback", 5)
-- end
--
-- function MyAddOn:TimerFeedback()
-- self.timerCount = self.timerCount + 1
-- print(("%d seconds passed"):format(5 * self.timerCount))
-- -- run 30 seconds in total
-- if self.timerCount == 6 then
-- self:CancelTimer(self.testTimer)
-- end
-- end
function AceTimer:ScheduleRepeatingTimer(func, delay, ...)
if not func or not delay then
error(MAJOR..": ScheduleRepeatingTimer(callback, delay, args...): 'callback' and 'delay' must have set values.", 2)
end
if type(func) == "string" then
if type(self) ~= "table" then
error(MAJOR..": ScheduleRepeatingTimer(callback, delay, args...): 'self' - must be a table.", 2)
elseif not self[func] then
error(MAJOR..": ScheduleRepeatingTimer(callback, delay, args...): Tried to register '"..func.."' as the callback, but it doesn't exist in the module.", 2)
end
end
return new(self, true, func, delay, ...)
end
--- Cancels a timer with the given id, registered by the same addon object as used for `:ScheduleTimer`
-- Both one-shot and repeating timers can be canceled with this function, as long as the `id` is valid
-- and the timer has not fired yet or was canceled before.
-- @param id The id of the timer, as returned by `:ScheduleTimer` or `:ScheduleRepeatingTimer`
function AceTimer:CancelTimer(id)
local timer = activeTimers[id]
if not timer then
return false
else
timer.cancelled = true
activeTimers[id] = nil
return true
end
end
--- Cancels all timers registered to the current addon object ('self')
function AceTimer:CancelAllTimers()
for k,v in pairs(activeTimers) do
if v.object == self then
AceTimer.CancelTimer(self, k)
end
end
end
--- Returns the time left for a timer with the given id, registered by the current addon object ('self').
-- This function will return 0 when the id is invalid.
-- @param id The id of the timer, as returned by `:ScheduleTimer` or `:ScheduleRepeatingTimer`
-- @return The time left on the timer.
function AceTimer:TimeLeft(id)
local timer = activeTimers[id]
if not timer then
return 0
else
return timer.ends - GetTime()
end
end
-- ---------------------------------------------------------------------
-- Upgrading
-- Upgrade from old hash-bucket based timers to C_Timer.After timers.
if oldminor and oldminor < 10 then
-- disable old timer logic
AceTimer.frame:SetScript("OnUpdate", nil)
AceTimer.frame:SetScript("OnEvent", nil)
AceTimer.frame:UnregisterAllEvents()
-- convert timers
for object,timers in pairs(AceTimer.selfs) do
for handle,timer in pairs(timers) do
if type(timer) == "table" and timer.callback then
local newTimer
if timer.delay then
newTimer = AceTimer.ScheduleRepeatingTimer(timer.object, timer.callback, timer.delay, timer.arg)
else
newTimer = AceTimer.ScheduleTimer(timer.object, timer.callback, timer.when - GetTime(), timer.arg)
end
-- Use the old handle for old timers
activeTimers[newTimer] = nil
activeTimers[handle] = newTimer
newTimer.handle = handle
end
end
end
AceTimer.selfs = nil
AceTimer.hash = nil
AceTimer.debug = nil
elseif oldminor and oldminor < 17 then
-- Upgrade from old animation based timers to C_Timer.After timers.
AceTimer.inactiveTimers = nil
AceTimer.frame = nil
local oldTimers = AceTimer.activeTimers
-- Clear old timer table and update upvalue
AceTimer.activeTimers = {}
activeTimers = AceTimer.activeTimers
for handle, timer in pairs(oldTimers) do
local newTimer
-- Stop the old timer animation
local duration, elapsed = timer:GetDuration(), timer:GetElapsed()
timer:GetParent():Stop()
if timer.looping then
newTimer = AceTimer.ScheduleRepeatingTimer(timer.object, timer.func, duration, unpack(timer.args, 1, timer.argsCount))
else
newTimer = AceTimer.ScheduleTimer(timer.object, timer.func, duration - elapsed, unpack(timer.args, 1, timer.argsCount))
end
-- Use the old handle for old timers
activeTimers[newTimer] = nil
activeTimers[handle] = newTimer
newTimer.handle = handle
end
-- Migrate transitional handles
if oldminor < 13 and AceTimer.hashCompatTable then
for handle, id in pairs(AceTimer.hashCompatTable) do
local t = activeTimers[id]
if t then
activeTimers[id] = nil
activeTimers[handle] = t
t.handle = handle
end
end
AceTimer.hashCompatTable = nil
end
end
-- ---------------------------------------------------------------------
-- Embed handling
AceTimer.embeds = AceTimer.embeds or {}
local mixins = {
"ScheduleTimer", "ScheduleRepeatingTimer",
"CancelTimer", "CancelAllTimers",
"TimeLeft"
}
function AceTimer:Embed(target)
AceTimer.embeds[target] = true
for _,v in pairs(mixins) do
target[v] = AceTimer[v]
end
return target
end
-- AceTimer:OnEmbedDisable(target)
-- target (object) - target object that AceTimer is embedded in.
--
-- cancel all timers registered for the object
function AceTimer:OnEmbedDisable(target)
target:CancelAllTimers()
end
for addon in pairs(AceTimer.embeds) do
AceTimer:Embed(addon)
end

View File

@ -0,0 +1,4 @@
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
..\FrameXML\UI.xsd">
<Script file="AceTimer-3.0.lua" />
</Ui>

View File

@ -0,0 +1,30 @@
-- LibStub is a simple versioning stub meant for use in Libraries. http://www.wowace.com/wiki/LibStub for more info
-- LibStub is hereby placed in the Public Domain Credits: Kaelten, Cladhaire, ckknight, Mikk, Ammo, Nevcairiel, joshborke
local LIBSTUB_MAJOR, LIBSTUB_MINOR = "LibStub", 2 -- NEVER MAKE THIS AN SVN REVISION! IT NEEDS TO BE USABLE IN ALL REPOS!
local LibStub = _G[LIBSTUB_MAJOR]
if not LibStub or LibStub.minor < LIBSTUB_MINOR then
LibStub = LibStub or {libs = {}, minors = {} }
_G[LIBSTUB_MAJOR] = LibStub
LibStub.minor = LIBSTUB_MINOR
function LibStub:NewLibrary(major, minor)
assert(type(major) == "string", "Bad argument #2 to `NewLibrary' (string expected)")
minor = assert(tonumber(strmatch(minor, "%d+")), "Minor version must either be a number or contain a number.")
local oldminor = self.minors[major]
if oldminor and oldminor >= minor then return nil end
self.minors[major], self.libs[major] = minor, self.libs[major] or {}
return self.libs[major], oldminor
end
function LibStub:GetLibrary(major, silent)
if not self.libs[major] and not silent then
error(("Cannot find a library instance of %q."):format(tostring(major)), 2)
end
return self.libs[major], self.minors[major]
end
function LibStub:IterateLibraries() return pairs(self.libs) end
setmetatable(LibStub, { __call = LibStub.GetLibrary })
end

View File

@ -0,0 +1,382 @@
local json = LibStub:NewLibrary("json", 1)
if not json then return end
--
-- json.lua
--
-- Copyright (c) 2015 rxi
--
-- This library is free software; you can redistribute it and/or modify it
-- under the terms of the MIT license. See LICENSE for details.
--
-------------------------------------------------------------------------------
-- Encode
-------------------------------------------------------------------------------
local encode
local escape_char_map = {
[ "\\" ] = "\\\\",
[ "\"" ] = "\\\"",
[ "\b" ] = "\\b",
[ "\f" ] = "\\f",
[ "\n" ] = "\\n",
[ "\r" ] = "\\r",
[ "\t" ] = "\\t",
}
local escape_char_map_inv = { [ "\\/" ] = "/" }
for k, v in pairs(escape_char_map) do
escape_char_map_inv[v] = k
end
local function escape_char(c)
return escape_char_map[c] or string.format("\\u%04x", c:byte())
end
local function encode_nil(val)
return "null"
end
local function encode_table(val, stack)
local res = {}
stack = stack or {}
-- Circular reference?
if stack[val] then error("circular reference") end
stack[val] = true
if val[1] ~= nil or next(val) == nil then
-- Treat as array -- check keys are valid and it is not sparse
local n = 0
for k in pairs(val) do
if type(k) ~= "number" then
error("invalid table: mixed or invalid key types")
end
n = n + 1
end
if n ~= #val then
error("invalid table: sparse array")
end
-- Encode
for i, v in ipairs(val) do
table.insert(res, encode(v, stack))
end
stack[val] = nil
return "[" .. table.concat(res, ",") .. "]"
else
-- Treat as an object
for k, v in pairs(val) do
if type(k) ~= "string" then
error("invalid table: mixed or invalid key types")
end
table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
end
stack[val] = nil
return "{" .. table.concat(res, ",") .. "}"
end
end
local function encode_string(val)
return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
end
local function encode_number(val)
-- Check for NaN, -inf and inf
if val ~= val or val <= -math.huge or val >= math.huge then
error("unexpected number value '" .. tostring(val) .. "'")
end
return string.format("%.14g", val)
end
local type_func_map = {
[ "nil" ] = encode_nil,
[ "table" ] = encode_table,
[ "string" ] = encode_string,
[ "number" ] = encode_number,
[ "boolean" ] = tostring,
}
encode = function(val, stack)
local t = type(val)
local f = type_func_map[t]
if f then
return f(val, stack)
end
error("unexpected type '" .. t .. "'")
end
function json.encode(val)
return ( encode(val) )
end
-------------------------------------------------------------------------------
-- Decode
-------------------------------------------------------------------------------
local parse
local function create_set(...)
local res = {}
for i = 1, select("#", ...) do
res[ select(i, ...) ] = true
end
return res
end
local space_chars = create_set(" ", "\t", "\r", "\n")
local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
local literals = create_set("true", "false", "null")
local literal_map = {
[ "true" ] = true,
[ "false" ] = false,
[ "null" ] = nil,
}
local function next_char(str, idx, set, negate)
for i = idx, #str do
if set[str:sub(i, i)] ~= negate then
return i
end
end
return #str + 1
end
local function decode_error(str, idx, msg)
local line_count = 1
local col_count = 1
for i = 1, idx - 1 do
col_count = col_count + 1
if str:sub(i, i) == "\n" then
line_count = line_count + 1
col_count = 1
end
end
error( string.format("%s at line %d col %d", msg, line_count, col_count) )
end
local function codepoint_to_utf8(n)
-- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
local f = math.floor
if n <= 0x7f then
return string.char(n)
elseif n <= 0x7ff then
return string.char(f(n / 64) + 192, n % 64 + 128)
elseif n <= 0xffff then
return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)
elseif n <= 0x10ffff then
return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,
f(n % 4096 / 64) + 128, n % 64 + 128)
end
error( string.format("invalid unicode codepoint '%x'", n) )
end
local function parse_unicode_escape(s)
local n1 = tonumber( s:sub(3, 6), 16 )
local n2 = tonumber( s:sub(9, 12), 16 )
-- Surrogate pair?
if n2 then
return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)
else
return codepoint_to_utf8(n1)
end
end
local function parse_string(str, i)
local has_unicode_escape = false
local has_surrogate_escape = false
local has_escape = false
local last
for j = i + 1, #str do
local x = str:byte(j)
if x < 32 then
decode_error(str, j, "control character in string")
end
if last == 92 then -- "\\" (escape char)
if x == 117 then -- "u" (unicode escape sequence)
local hex = str:sub(j + 1, j + 5)
if not hex:find("%x%x%x%x") then
decode_error(str, j, "invalid unicode escape in string")
end
if hex:find("^[dD][89aAbB]") then
has_surrogate_escape = true
else
has_unicode_escape = true
end
else
local c = string.char(x)
if not escape_chars[c] then
decode_error(str, j, "invalid escape char '" .. c .. "' in string")
end
has_escape = true
end
last = nil
elseif x == 34 then -- '"' (end of string)
local s = str:sub(i + 1, j - 1)
if has_surrogate_escape then
s = s:gsub("\\u[dD][89aAbB]..\\u....", parse_unicode_escape)
end
if has_unicode_escape then
s = s:gsub("\\u....", parse_unicode_escape)
end
if has_escape then
s = s:gsub("\\.", escape_char_map_inv)
end
return s, j + 1
else
last = x
end
end
decode_error(str, i, "expected closing quote for string")
end
local function parse_number(str, i)
local x = next_char(str, i, delim_chars)
local s = str:sub(i, x - 1)
local n = tonumber(s)
if not n then
decode_error(str, i, "invalid number '" .. s .. "'")
end
return n, x
end
local function parse_literal(str, i)
local x = next_char(str, i, delim_chars)
local word = str:sub(i, x - 1)
if not literals[word] then
decode_error(str, i, "invalid literal '" .. word .. "'")
end
return literal_map[word], x
end
local function parse_array(str, i)
local res = {}
local n = 1
i = i + 1
while 1 do
local x
i = next_char(str, i, space_chars, true)
-- Empty / end of array?
if str:sub(i, i) == "]" then
i = i + 1
break
end
-- Read token
x, i = parse(str, i)
res[n] = x
n = n + 1
-- Next token
i = next_char(str, i, space_chars, true)
local chr = str:sub(i, i)
i = i + 1
if chr == "]" then break end
if chr ~= "," then decode_error(str, i, "expected ']' or ','") end
end
return res, i
end
local function parse_object(str, i)
local res = {}
i = i + 1
while 1 do
local key, val
i = next_char(str, i, space_chars, true)
-- Empty / end of object?
if str:sub(i, i) == "}" then
i = i + 1
break
end
-- Read key
if str:sub(i, i) ~= '"' then
decode_error(str, i, "expected string for key")
end
key, i = parse(str, i)
-- Read ':' delimiter
i = next_char(str, i, space_chars, true)
if str:sub(i, i) ~= ":" then
decode_error(str, i, "expected ':' after key")
end
i = next_char(str, i + 1, space_chars, true)
-- Read value
val, i = parse(str, i)
-- Set
res[key] = val
-- Next token
i = next_char(str, i, space_chars, true)
local chr = str:sub(i, i)
i = i + 1
if chr == "}" then break end
if chr ~= "," then decode_error(str, i, "expected '}' or ','") end
end
return res, i
end
local char_func_map = {
[ '"' ] = parse_string,
[ "0" ] = parse_number,
[ "1" ] = parse_number,
[ "2" ] = parse_number,
[ "3" ] = parse_number,
[ "4" ] = parse_number,
[ "5" ] = parse_number,
[ "6" ] = parse_number,
[ "7" ] = parse_number,
[ "8" ] = parse_number,
[ "9" ] = parse_number,
[ "-" ] = parse_number,
[ "t" ] = parse_literal,
[ "f" ] = parse_literal,
[ "n" ] = parse_literal,
[ "[" ] = parse_array,
[ "{" ] = parse_object,
}
parse = function(str, idx)
local chr = str:sub(idx, idx)
local f = char_func_map[chr]
if f then
return f(str, idx)
end
decode_error(str, idx, "unexpected character '" .. chr .. "'")
end
function json.decode(str)
if type(str) ~= "string" then
error("expected argument of type string, got " .. type(str))
end
return ( parse(str, next_char(str, 1, space_chars, true)) )
end
return json

View File

@ -0,0 +1,9 @@
<Ui xsi:schemaLocation="http://www.blizzard.com/wow/ui/ ..\FrameXML\UI.xsd">
<Script file="Libs\LibStub\LibStub.lua" />
<Include file="Libs\AceAddon-3.0\AceAddon-3.0.xml" />
<Include file="Libs\AceConsole-3.0\AceConsole-3.0.xml" />
<Include file="Libs\AceEvent-3.0\AceEvent-3.0.xml" />
<Include file="Libs\AceTimer-3.0\AceTimer-3.0.xml" />
<Include file="Libs\AceComm-3.0\AceComm-3.0.xml" />
<Include file="Libs\json.lua" />
</Ui>

View File

@ -1,256 +0,0 @@
namespace Artemis.Modules.Games.WoW
{
public static class WoWAddresses
{
public enum ActivateSettings
{
Activate_Offset = 0x34,
AutoDismount_Activate_Pointer = 0xe56850,
AutoInteract_Activate_Pointer = 0xe56848,
AutoLoot_Activate_Pointer = 0xe56868,
AutoSelfCast_Activate_Pointer = 0xe56874
}
public enum Battleground
{
MaxBattlegroundId = 0xec3fdc,
PvpExitWindow = 0xec4198,
StatPvp = 0xc3c03c
}
public enum Chat
{
chatBufferPos = 0xeb1bf0,
chatBufferStart = 0xe58190,
msgFormatedChat = 0x65,
NextMessage = 0x17e8
}
public enum ClickToMove
{
CTM = 0xddf8f0,
CTM_PUSH = 0xddf8ac,
CTM_X = 0xddf918,
CTM_Y = 0xddf91c,
CTM_Z = 0xddf920
}
public enum CorpsePlayer
{
X = 0xe57894,
Y = 0xe57898,
Z = 0xe5789c
}
public enum DBC
{
FactionTemplate = 0,
ItemClass = 0xd173c0,
ItemSubClass = 0,
Lock = 0,
Map = 0xd291a0,
QuestPOIPoint = 0xd1e950,
ResearchSite = 0xd1d2d0,
SpellCategories = 0,
Unknown = 0xf35428
}
public enum EventsListener
{
BaseEvents = 0xcb2474,
EventOffsetCount = 0x48,
EventOffsetName = 0x18,
EventsCount = 0xcb2470
}
public enum Fishing
{
BobberHasMoved = 0xf8
}
public enum FunctionWow
{
CGUnit_C__InitializeTrackingState = 0x30623b,
CGUnit_C__Interact = 0x524ff,
CGWorldFrame__Intersect = 0x5e46ab,
ClntObjMgrGetActivePlayerObj = 0x816d7,
FrameScript__GetLocalizedText = 0x300b48,
FrameScript_ExecuteBuffer = 0xa6772,
IsOutdoors = 0,
Spell_C_HandleTerrainClick = 0x2b76ff,
strlen = 0x74fcb0,
UnitCanAttack = 0,
WowClientDB2__GetRowPointer = 0x20c775
}
public enum GameInfo
{
AreaId = 0xc32c2c,
buildWoWVersionString = 0xd002a8,
gameState = 0xe56a49,
GetTime = 0xcb2150,
isLoading = 0xca59b0,
LastHardwareAction = 0xd0e090,
MapTextureId = 0xc3bd28,
SubAreaId = 0xc32c24,
subZoneMap = 0xe56a68,
TextBoxActivated = 0xbbe9ac,
zoneMap = 0xe56a64
}
public enum GameObject
{
CachedCastBarCaption = 12,
CachedData0 = 20,
CachedIconName = 8,
CachedName = 180,
CachedQuestItem1 = 0x9c,
CachedSize = 0x98,
DBCacheRow = 620,
GAMEOBJECT_FIELD_X = 0x138,
GAMEOBJECT_FIELD_Y = 0x13c,
GAMEOBJECT_FIELD_Z = 320,
PackedRotationQuaternion = 0x150,
TransformationMatrice = 0x278
}
public enum Hooking
{
DX_DEVICE = 0xcc523c,
DX_DEVICE_IDX = 0x2508,
ENDSCENE_IDX = 0xa8
}
public enum Login
{
realmName = 0xf35e16
}
public enum MovementFlagsOffsets
{
Offset1 = 0x124,
Offset2 = 0x40
}
public enum ObjectManager
{
continentId = 0x108,
firstObject = 0xd8,
localGuid = 0xf8,
nextObject = 0x44,
objectGUID = 0x30,
objectTYPE = 0x10
}
public enum Party
{
NumOfPlayers = 200,
NumOfPlayersSuBGroup = 0xcc,
PartyOffset = 0xeb5458,
PlayerGuid = 0x10
}
public enum PetBattle
{
IsInBattle = 0xba8a10
}
public enum Player
{
LocalPlayerSpellsOnCooldown = 0xd372b8,
petGUID = 0xec7158,
playerName = 0xf35e20,
RetrieveCorpseWindow = 0xe576f4,
RuneStartCooldown = 0xf18aa8,
SkillMaxValue = 0x400,
SkillValue = 0x200
}
public enum PlayerNameStore
{
PlayerNameNextOffset = 20,
PlayerNameStorePtr = 0xd0b4e0,
PlayerNameStringOffset = 0x11
}
public enum PowerIndex
{
Multiplicator = 0x10,
PowerIndexArrays = 0xddf914
}
public enum Quests
{
QuestGiverStatus = 0xf4
}
public enum SpellBook
{
FirstTalentBookPtr = 0xeb52ec,
KnownAllSpells = 0xeb5130,
MountBookMountsPtr = 0xeb5194,
MountBookNumMounts = 0xeb5190,
NextTalentBookPtr = 0xeb52e4,
SpellBookNumSpells = 0xeb5134,
SpellBookSpellsPtr = 0xeb5138,
SpellDBCMaxIndex = 0x30d40,
TalentBookOverrideSpellId = 0x1c,
TalentBookSpellId = 20
}
public enum UnitBaseGetUnitAura
{
AuraSize = 0x58,
AuraStructCasterLevel = 0x3a,
AuraStructCount = 0x39,
AuraStructCreatorGuid = 0x20,
AuraStructDuration = 60,
AuraStructFlag = 0x34,
AuraStructMask = 0x35,
AuraStructSpellEndTime = 0x40,
AuraStructSpellId = 0x30,
AuraStructUnk1 = 0x3b,
AuraStructUnk2 = 0x44,
AuraTable1 = 0x1150,
AuraTable2 = 0x580
}
public enum UnitField
{
CachedIsBoss = 0x60,
CachedModelId1 = 0x6c,
CachedName = 0x80,
CachedQuestItem1 = 60,
CachedSubName = 0,
CachedTypeFlag = 0x24,
CachedUnitClassification = 0x2c,
CanInterrupt = 0xfc4,
CanInterruptOffset = 0xe02ea0,
CanInterruptOffset2 = 0xe02ea4,
CanInterruptOffset3 = 0xe02ea8,
CastingSpellEndTime = 0x108c,
CastingSpellID = 0x1064,
CastingSpellStartTime = 0x1088,
ChannelSpellEndTime = 0x1098,
ChannelSpellID = 0x1090,
ChannelSpellStartTime = 0x1094,
DBCacheRow = 0xc80,
TransportGUID = 0xae8,
UNIT_FIELD_R = 0xb08,
UNIT_FIELD_X = 0xaf8,
UNIT_FIELD_Y = 0xafc,
UNIT_FIELD_Z = 0xb00
}
public enum VMT
{
CGUnit_C__GetFacing = 0x35
}
public class ObjectManagerClass
{
public static uint clientConnection;
public static uint sCurMgr;
}
}
}

View File

@ -1,11 +1,33 @@
using Artemis.Modules.Abstract; using Artemis.Modules.Abstract;
using Artemis.Modules.Games.WoW.Data; using Artemis.Modules.Games.WoW.Models;
using MoonSharp.Interpreter;
namespace Artemis.Modules.Games.WoW namespace Artemis.Modules.Games.WoW
{ {
[MoonSharpUserData]
public class WoWDataModel : ModuleDataModel public class WoWDataModel : ModuleDataModel
{ {
public WoWDataModel()
{
Player = new WoWUnit();
Target = new WoWUnit();
}
public WoWUnit Player { get; set; } public WoWUnit Player { get; set; }
public WoWUnit Target { get; set; } public WoWUnit Target { get; set; }
public string Realm { get; set; }
public string Zone { get; set; }
public string SubZone { get; set; }
public WoWState State { get; set; }
} }
}
public enum WoWState
{
LoggedOut,
Ingame,
Afk,
Dnd
}
}

View File

@ -1,116 +1,261 @@
using System; using Artemis.DAL;
using System.Linq; using Artemis.Managers;
using Artemis.DAL; using Artemis.Modules.Abstract;
using Artemis.Managers; using Artemis.Modules.Games.WoW.Models;
using Artemis.Modules.Abstract; using Artemis.Properties;
using Artemis.Modules.Games.WoW.Data; using Artemis.Services;
using Artemis.Settings; using Artemis.Utilities;
using Artemis.Utilities.Memory; using Microsoft.Win32;
using Process.NET; using Newtonsoft.Json.Linq;
using Process.NET.Memory; using System.IO;
using System.IO.Compression;
namespace Artemis.Modules.Games.WoW
{ namespace Artemis.Modules.Games.WoW
public class WoWModel : ModuleModel {
{ public class WoWModel : ModuleModel
private readonly GamePointersCollection _pointer; {
private ProcessSharp _process; private readonly MetroDialogService _dialogService;
private readonly WowPacketScanner _packetScanner;
public WoWModel(DeviceManager deviceManager, LuaManager luaManager) : base(deviceManager, luaManager) public WoWModel(DeviceManager deviceManager, LuaManager luaManager, WowPacketScanner packetScanner, MetroDialogService dialogService) : base(deviceManager, luaManager)
{ {
Settings = SettingsProvider.Load<WoWSettings>(); Settings = SettingsProvider.Load<WoWSettings>();
DataModel = new WoWDataModel(); DataModel = new WoWDataModel();
ProcessNames.Add("Wow-64"); ProcessNames.Add("Wow-64");
// Currently WoW is locked behind a hidden trigger (obviously not that hidden since you're reading this) _packetScanner = packetScanner;
// It is using memory reading and lets first try to contact Blizzard _dialogService = dialogService;
var settings = SettingsProvider.Load<GeneralSettings>(); _packetScanner.RaiseDataReceived += (sender, args) => HandleGameData(args.Command, args.Data);
Settings.IsEnabled = settings.GamestatePort == 62575 && Settings.IsEnabled;
FindWoW();
_pointer = SettingsProvider.Load<OffsetSettings>().WorldOfWarcraft;
//_pointer = new GamePointersCollection // I simply cannot be sure wether this addon will bring people's accounts in trouble so
//{ // lets remove it whenever Artemis isn't running the WoW module.
// Game = "WorldOfWarcraft", // (This also means the addon isnt left behind should the user uninstall Artemis.)
// GameVersion = "7.0.3.22810", RemoveAddon();
// GameAddresses = new List<GamePointer> }
// {
// new GamePointer public override string Name => "WoW";
// { public override bool IsOverlay => false;
// Description = "ObjectManager", public override bool IsBoundToProcess => true;
// BasePointer = new IntPtr(0x1578070)
// }, public override void Enable()
// new GamePointer {
// { PlaceAddon();
// Description = "LocalPlayer", _packetScanner.Start();
// BasePointer = new IntPtr(0x169DF10) base.Enable();
// }, }
// new GamePointer
// { public override void Dispose()
// Description = "NameCache", {
// BasePointer = new IntPtr(0x151DCE8) RemoveAddon();
// }, _packetScanner.Stop();
// new GamePointer base.Dispose();
// { }
// Description = "TargetGuid",
// BasePointer = new IntPtr(0x179C940) public override void Update()
// } {
// } var dataModel = (WoWDataModel) DataModel;
//};
//var res = JsonConvert.SerializeObject(_pointer, Formatting.Indented); dataModel.Player.Update();
} dataModel.Target.Update();
}
public override string Name => "WoW";
public override bool IsOverlay => false; public void FindWoW()
public override bool IsBoundToProcess => true; {
var gameSettings = Settings as WoWSettings;
public override void Dispose() if (gameSettings == null)
{ return;
base.Dispose();
// If already propertly set up, don't do anything
_process?.Dispose(); if (gameSettings.GameDirectory != null && File.Exists(gameSettings.GameDirectory + @"\Wow.exe"))
_process = null; return;
}
var key = Registry.LocalMachine.OpenSubKey(
public override void Update() @"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\World of Warcraft");
{ var path = key?.GetValue("DisplayIcon")?.ToString();
if (_process == null) if (string.IsNullOrEmpty(path) || !File.Exists(path))
{ return;
var tempProcess = MemoryHelpers.GetProcessIfRunning(ProcessNames[0]);
if (tempProcess == null) gameSettings.GameDirectory = path.Substring(0, path.Length - 8);
return; gameSettings.Save();
}
_process = new ProcessSharp(tempProcess, MemoryType.Remote);
} public void ChangeDirectory(string directory, bool checkExe)
{
if (ProfileModel == null || DataModel == null || _process == null) var settings = (WoWSettings) Settings;
return; if (checkExe && !File.Exists(directory + @"\Wow.exe"))
{
var dataModel = (WoWDataModel) DataModel; _dialogService.ShowErrorMessageBox("Please select a valid WoW directory\n\n" +
@"By default WoW is in C:\Program Files (x86)\World of Warcraft");
var objectManager = new WoWObjectManager(_process,
_pointer.GameAddresses.First(a => a.Description == "ObjectManager").BasePointer); settings.GameDirectory = string.Empty;
var nameCache = new WoWNameCache(_process, settings.Save();
_pointer.GameAddresses.First(a => a.Description == "NameCache").BasePointer); return;
var player = new WoWPlayer(_process, }
_pointer.GameAddresses.First(a => a.Description == "LocalPlayer").BasePointer, settings.GameDirectory = directory;
_pointer.GameAddresses.First(a => a.Description == "TargetGuid").BasePointer, true); settings.Save();
}
dataModel.Player = player;
if (dataModel.Player != null && dataModel.Player.Guid != Guid.Empty) public void PlaceAddon()
{ {
dataModel.Player.UpdateDetails(nameCache); var settings = (WoWSettings) Settings;
var target = player.GetTarget(objectManager); var path = settings.GameDirectory;
if (target == null)
return; if (!File.Exists(path + @"\Wow.exe"))
return;
dataModel.Target = new WoWUnit(target.Process, target.BaseAddress);
dataModel.Target.UpdateDetails(nameCache); // Load the ZIP from resources
} using (var stream = new MemoryStream(Resources.wow_addon))
else {
{ using (var archive = new ZipArchive(stream))
dataModel.Target = null; {
} archive.ExtractToDirectory(settings.GameDirectory + @"\Interface\Addons\Artemis", true);
} }
} }
}
public void RemoveAddon()
{
var settings = (WoWSettings) Settings;
if (Directory.Exists(settings.GameDirectory + @"\Interface\Addons\Artemis"))
Directory.Delete(settings.GameDirectory + @"\Interface\Addons\Artemis", true);
}
private void HandleGameData(string command, string data)
{
JToken json = null;
if (!data.StartsWith("\"") && !data.EndsWith("\""))
json = JToken.Parse(data);
lock (DataModel)
{
var dataModel = (WoWDataModel) DataModel;
switch (command)
{
case "gameState":
ParseGameState(data, dataModel);
break;
case "player":
ParsePlayer(json, dataModel);
break;
case "target":
ParseTarget(json, dataModel);
break;
case "playerState":
ParsePlayerState(json, dataModel);
break;
case "targetState":
ParseTargetState(json, dataModel);
break;
case "buffs":
ParseAuras(json, dataModel, true);
break;
case "debuffs":
ParseAuras(json, dataModel, false);
break;
case "spellCast":
ParseSpellCast(json, dataModel, false);
break;
case "instantSpellCast":
ParseInstantSpellCast(json, dataModel);
break;
case "spellCastFailed":
ParseSpellCastFailed(data, dataModel);
break;
case "spellCastInterrupted":
ParseSpellCastInterrupted(data, dataModel);
break;
case "spellChannel":
ParseSpellCast(json, dataModel, true);
break;
case "spellChannelInterrupted":
ParseSpellCastInterrupted(data, dataModel);
break;
default:
Logger.Warn("The WoW addon sent an unknown command: {0}", command);
break;
}
}
}
private void ParseGameState(string data, WoWDataModel dataModel)
{
if (data == "\"ingame\"")
dataModel.State = WoWState.Ingame;
else if (data == "\"afk\"")
dataModel.State = WoWState.Afk;
else if (data == "\"dnd\"")
dataModel.State = WoWState.Dnd;
else if (data == "\"loggedOut\"")
dataModel.State = WoWState.LoggedOut;
}
private void ParsePlayer(JToken json, WoWDataModel dataModel)
{
dataModel.Player.ApplyJson(json);
// At this point class/race data is available so no point pretending to be logged out
if (dataModel.State == WoWState.LoggedOut)
dataModel.State = WoWState.Ingame;
}
private void ParseTarget(JToken json, WoWDataModel dataModel)
{
dataModel.Target.ApplyJson(json);
}
private void ParsePlayerState(JToken json, WoWDataModel dataModel)
{
dataModel.Player.ApplyStateJson(json);
}
private void ParseTargetState(JToken json, WoWDataModel dataModel)
{
dataModel.Target.ApplyStateJson(json);
}
private void ParseAuras(JToken json, WoWDataModel dataModel, bool buffs)
{
dataModel.Player.ApplyAuraJson(json, buffs);
}
private void ParseSpellCast(JToken json, WoWDataModel dataModel, bool isChannel)
{
if (json["uid"].Value<string>() == "player")
dataModel.Player.CastBar.ApplyJson(json, isChannel);
else if (json["uid"].Value<string>() == "target")
dataModel.Target.CastBar.ApplyJson(json, isChannel);
}
private void ParseInstantSpellCast(JToken json, WoWDataModel dataModel)
{
var spell = new WoWSpell
{
Name = json["n"].Value<string>(),
Id = json["sid"].Value<int>()
};
if (json["uid"].Value<string>() == "player")
dataModel.Player.AddInstantCast(spell);
else if (json["uid"].Value<string>() == "target")
dataModel.Target.AddInstantCast(spell);
}
private void ParseSpellCastFailed(string data, WoWDataModel dataModel)
{
if (data == "\"player\"")
dataModel.Player.CastBar.Clear();
else if (data == "\"target\"")
dataModel.Target.CastBar.Clear();
}
private void ParseSpellCastInterrupted(string data, WoWDataModel dataModel)
{
if (data == "\"player\"")
dataModel.Player.CastBar.Clear();
else if (data == "\"target\"")
dataModel.Target.CastBar.Clear();
}
}
} }

View File

@ -0,0 +1,127 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Ninject.Extensions.Logging;
using PcapDotNet.Core;
using PcapDotNet.Packets;
namespace Artemis.Modules.Games.WoW
{
public class WowPacketScanner
{
private const string MsgStart = "\u0001";
private const string MsgNext = "\u0002";
private const string MsgLast = "\u0003";
private PacketCommunicator _communicator;
private string _dataParts;
public WowPacketScanner(ILogger logger)
{
Logger = logger;
}
public ILogger Logger { get; }
public event EventHandler<WowDataReceivedEventArgs> RaiseDataReceived;
public void Start()
{
// Start scanning WoW packets
// Retrieve the device list from the local machine
IList<LivePacketDevice> allDevices = LivePacketDevice.AllLocalMachine;
if (allDevices.Count == 0)
{
Logger.Warn("No interfaces found! Can't scan WoW packets.");
return;
}
// Take the selected adapter
PacketDevice selectedDevice = allDevices.First();
// Open the device
_communicator = selectedDevice.Open(65536, PacketDeviceOpenAttributes.Promiscuous, 40);
Logger.Debug("Listening on " + selectedDevice.Description + " for WoW packets");
// Compile the filter
using (var filter = _communicator.CreateFilter("tcp"))
{
// Set the filter
_communicator.SetFilter(filter);
}
Task.Run(() => ReceivePackets());
}
public void Stop()
{
_communicator?.Break();
_communicator?.Dispose();
_communicator = null;
}
private void ReceivePackets()
{
// start the capture
try
{
_communicator.ReceivePackets(0, PacketHandler);
}
catch (InvalidOperationException)
{
// ignored, happens on shutdown
}
}
// Callback function invoked by Pcap.Net for every incoming packet
private void PacketHandler(Packet packet)
{
var str = Encoding.Default.GetString(packet.Buffer);
if (!str.ToLower().Contains("artemis"))
return;
// Split the string at the prefix
var parts = str.Split(new[] {"(artemis)"}, StringSplitOptions.None);
if (parts.Length < 2)
return;
var msg = parts[1];
// Start escape char
if (msg.StartsWith(MsgStart))
_dataParts = msg.Substring(1);
else if (msg.StartsWith(MsgNext))
_dataParts = _dataParts + msg.Substring(1);
else if (msg.StartsWith(MsgLast))
{
_dataParts = _dataParts + msg.Substring(1);
var dataParts = _dataParts.Split('|');
// Data is wrapped in artemis(), take this off
OnRaiseDataReceived(dataParts[0].Substring(8), dataParts[1].Substring(0, dataParts[1].Length - 1));
}
else
{
var dataParts = msg.Split('|');
// Data is wrapped in artemis(), take this off
OnRaiseDataReceived(dataParts[0].Substring(8), dataParts[1].Substring(0, dataParts[1].Length - 1));
}
}
private void OnRaiseDataReceived(string command, string data)
{
RaiseDataReceived?.Invoke(this, new WowDataReceivedEventArgs(command, data));
}
public class WowDataReceivedEventArgs : EventArgs
{
public WowDataReceivedEventArgs(string command, string data)
{
Command = command;
Data = data;
}
public string Command { get; }
public string Data { get; set; }
}
}
}

View File

@ -4,5 +4,6 @@ namespace Artemis.Modules.Games.WoW
{ {
public class WoWSettings : ModuleSettings public class WoWSettings : ModuleSettings
{ {
public string GameDirectory { get; set; }
} }
} }

View File

@ -1,19 +1,13 @@
<UserControl x:Class="Artemis.Modules.Games.WoW.WoWView" <UserControl x:Class="Artemis.Modules.Games.WoW.WoWView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:cal="http://www.caliburnproject.org" xmlns:controls="http://metro.mahapps.com/winfx/xaml/controls" mc:Ignorable="d" d:DesignHeight="476.986" d:DesignWidth="538.772">
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
xmlns:cal="http://www.caliburnproject.org"
xmlns:controls="http://metro.mahapps.com/winfx/xaml/controls"
mc:Ignorable="d"
d:DesignHeight="476.986" d:DesignWidth="538.772">
<Grid> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="*" /> <RowDefinition Height="*" />
@ -23,7 +17,7 @@
<!-- Header --> <!-- Header -->
<Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" FontSize="20" HorizontalAlignment="Left"> <Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" FontSize="20" HorizontalAlignment="Left">
<Label.Content> <Label.Content>
<AccessText TextWrapping="Wrap" Text="Fight the Legion in style with reactive lighting." /> <AccessText TextWrapping="Wrap" Text="There is currently no default profile available for World of Warcraft." />
</Label.Content> </Label.Content>
</Label> </Label>
@ -39,11 +33,20 @@
Style="{StaticResource MahApps.Metro.Styles.ToggleSwitchButton.Win10}" ToolTip="Note: You can't enable an module when Artemis is disabled" /> Style="{StaticResource MahApps.Metro.Styles.ToggleSwitchButton.Win10}" ToolTip="Note: You can't enable an module when Artemis is disabled" />
</StackPanel> </StackPanel>
<!-- Game directory -->
<StackPanel Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" Margin="0,0,1,0">
<Label FontSize="20" HorizontalAlignment="Left" Content="World of Warcraft directory" />
<Grid>
<TextBox x:Name="GameDirectory" Height="23" TextWrapping="Wrap" Margin="0,0,30,0" Text="{Binding Path=Settings.GameDirectory, Mode=TwoWay}" cal:Message.Attach="[Event LostFocus] = [Action PlaceAddon]" />
<Button x:Name="BrowseDirectory" Content="..." RenderTransformOrigin="-0.039,-0.944" HorizontalAlignment="Right" Width="25" Style="{DynamicResource SquareButtonStyle}" Height="26" />
</Grid>
</StackPanel>
<!-- Profile editor --> <!-- Profile editor -->
<ContentControl Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" x:Name="ProfileEditor" /> <ContentControl Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2" x:Name="ProfileEditor" />
<!-- Buttons --> <!-- Buttons -->
<StackPanel Grid.Column="0" Grid.Row="3" Orientation="Horizontal" VerticalAlignment="Bottom"> <StackPanel Grid.Row="4" Grid.Column="0" Orientation="Horizontal" VerticalAlignment="Bottom">
<Button x:Name="ResetSettings" Content="Reset effect" VerticalAlignment="Top" Width="100" Style="{DynamicResource SquareButtonStyle}" /> <Button x:Name="ResetSettings" Content="Reset effect" VerticalAlignment="Top" Width="100" Style="{DynamicResource SquareButtonStyle}" />
<Button x:Name="SaveSettings" Content="Save changes" VerticalAlignment="Top" Width="100" Margin="10,0,0,0" Style="{DynamicResource SquareButtonStyle}" /> <Button x:Name="SaveSettings" Content="Save changes" VerticalAlignment="Top" Width="100" Margin="10,0,0,0" Style="{DynamicResource SquareButtonStyle}" />
</StackPanel> </StackPanel>

View File

@ -12,4 +12,4 @@ namespace Artemis.Modules.Games.WoW
InitializeComponent(); InitializeComponent();
} }
} }
} }

View File

@ -1,4 +1,5 @@
using Artemis.Managers; using System.Windows.Forms;
using Artemis.Managers;
using Artemis.Modules.Abstract; using Artemis.Modules.Abstract;
using Ninject; using Ninject;
@ -13,5 +14,23 @@ namespace Artemis.Modules.Games.WoW
} }
public override bool UsesProfileEditor => true; public override bool UsesProfileEditor => true;
public void PlaceAddon()
{
((WoWModel) ModuleModel).PlaceAddon();
}
public void BrowseDirectory()
{
var dialog = new FolderBrowserDialog {SelectedPath = ((WoWSettings) Settings).GameDirectory};
var result = dialog.ShowDialog();
if (result != DialogResult.OK)
return;
((WoWSettings) Settings).GameDirectory = dialog.SelectedPath;
((WoWModel) ModuleModel).PlaceAddon();
Settings.Save();
NotifyOfPropertyChange(() => Settings);
}
} }
} }

View File

@ -12,6 +12,7 @@ using Artemis.Managers;
using Artemis.Modules.Abstract; using Artemis.Modules.Abstract;
using Artemis.Utilities; using Artemis.Utilities;
using Artemis.Utilities.ActiveWindowDetection; using Artemis.Utilities.ActiveWindowDetection;
using Betwixt;
using CSCore.CoreAudioAPI; using CSCore.CoreAudioAPI;
using Newtonsoft.Json; using Newtonsoft.Json;
using SpotifyAPI.Local; using SpotifyAPI.Local;
@ -28,7 +29,7 @@ namespace Artemis.Modules.General.GeneralProfile
AudioCaptureManager audioCaptureManager) : base(deviceManager, luaManager) AudioCaptureManager audioCaptureManager) : base(deviceManager, luaManager)
{ {
_lastMusicUpdate = DateTime.Now; _lastMusicUpdate = DateTime.Now;
Settings = SettingsProvider.Load<GeneralProfileSettings>(); Settings = SettingsProvider.Load<GeneralProfileSettings>();
DataModel = new GeneralProfileDataModel(); DataModel = new GeneralProfileDataModel();

View File

@ -164,6 +164,7 @@
<xs:element name="whenEqual" type="whenEqual" /> <xs:element name="whenEqual" type="whenEqual" />
<xs:element name="whenNotContains" type="whenNotContains" /> <xs:element name="whenNotContains" type="whenNotContains" />
<xs:element name="whenNotEqual" type="whenNotEqual" /> <xs:element name="whenNotEqual" type="whenNotEqual" />
<xs:element name="whenRepeated" type="whenRepeated" />
</xs:choice> </xs:choice>
</xs:complexType> </xs:complexType>
<xs:simpleType name="NLogLevel"> <xs:simpleType name="NLogLevel">
@ -326,6 +327,7 @@
<xs:extension base="WrapperTargetBase"> <xs:extension base="WrapperTargetBase">
<xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="name" minOccurs="0" maxOccurs="1" type="xs:string" /> <xs:element name="name" minOccurs="0" maxOccurs="1" type="xs:string" />
<xs:element name="asyncFlush" minOccurs="0" maxOccurs="1" type="xs:boolean" />
<xs:element name="condition" minOccurs="0" maxOccurs="1" type="Condition" /> <xs:element name="condition" minOccurs="0" maxOccurs="1" type="Condition" />
<xs:element name="optimizeBufferReuse" minOccurs="0" maxOccurs="1" type="xs:boolean" /> <xs:element name="optimizeBufferReuse" minOccurs="0" maxOccurs="1" type="xs:boolean" />
</xs:choice> </xs:choice>
@ -334,6 +336,11 @@
<xs:documentation>Name of the target.</xs:documentation> <xs:documentation>Name of the target.</xs:documentation>
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
<xs:attribute name="asyncFlush" type="xs:boolean">
<xs:annotation>
<xs:documentation>Delay the flush until the LogEvent has been confirmed as written</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="condition" type="Condition"> <xs:attribute name="condition" type="Condition">
<xs:annotation> <xs:annotation>
<xs:documentation>Condition expression. Log events who meet this condition will cause a flush on the wrapped target.</xs:documentation> <xs:documentation>Condition expression. Log events who meet this condition will cause a flush on the wrapped target.</xs:documentation>
@ -403,13 +410,14 @@
<xs:element name="address" minOccurs="0" maxOccurs="1" type="Layout" /> <xs:element name="address" minOccurs="0" maxOccurs="1" type="Layout" />
<xs:element name="maxQueueSize" minOccurs="0" maxOccurs="1" type="xs:integer" /> <xs:element name="maxQueueSize" minOccurs="0" maxOccurs="1" type="xs:integer" />
<xs:element name="parameter" minOccurs="0" maxOccurs="unbounded" type="NLog.Targets.NLogViewerParameterInfo" /> <xs:element name="parameter" minOccurs="0" maxOccurs="unbounded" type="NLog.Targets.NLogViewerParameterInfo" />
<xs:element name="includeMdc" minOccurs="0" maxOccurs="1" type="xs:boolean" /> <xs:element name="includeNdc" minOccurs="0" maxOccurs="1" type="xs:boolean" />
<xs:element name="includeSourceInfo" minOccurs="0" maxOccurs="1" type="xs:boolean" /> <xs:element name="includeSourceInfo" minOccurs="0" maxOccurs="1" type="xs:boolean" />
<xs:element name="includeNLogData" minOccurs="0" maxOccurs="1" type="xs:boolean" /> <xs:element name="includeNLogData" minOccurs="0" maxOccurs="1" type="xs:boolean" />
<xs:element name="includeNdc" minOccurs="0" maxOccurs="1" type="xs:boolean" /> <xs:element name="includeMdc" minOccurs="0" maxOccurs="1" type="xs:boolean" />
<xs:element name="includeCallSite" minOccurs="0" maxOccurs="1" type="xs:boolean" /> <xs:element name="includeCallSite" minOccurs="0" maxOccurs="1" type="xs:boolean" />
<xs:element name="appInfo" minOccurs="0" maxOccurs="1" type="xs:string" /> <xs:element name="appInfo" minOccurs="0" maxOccurs="1" type="xs:string" />
<xs:element name="ndcItemSeparator" minOccurs="0" maxOccurs="1" type="xs:string" /> <xs:element name="ndcItemSeparator" minOccurs="0" maxOccurs="1" type="xs:string" />
<xs:element name="includeMdlc" minOccurs="0" maxOccurs="1" type="xs:boolean" />
<xs:element name="optimizeBufferReuse" minOccurs="0" maxOccurs="1" type="xs:boolean" /> <xs:element name="optimizeBufferReuse" minOccurs="0" maxOccurs="1" type="xs:boolean" />
</xs:choice> </xs:choice>
<xs:attribute name="name" type="xs:string"> <xs:attribute name="name" type="xs:string">
@ -477,9 +485,9 @@
<xs:documentation>Maximum queue size.</xs:documentation> <xs:documentation>Maximum queue size.</xs:documentation>
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
<xs:attribute name="includeMdc" type="xs:boolean"> <xs:attribute name="includeNdc" type="xs:boolean">
<xs:annotation> <xs:annotation>
<xs:documentation>Indicates whether to include dictionary contents.</xs:documentation> <xs:documentation>Indicates whether to include stack contents.</xs:documentation>
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
<xs:attribute name="includeSourceInfo" type="xs:boolean"> <xs:attribute name="includeSourceInfo" type="xs:boolean">
@ -492,9 +500,9 @@
<xs:documentation>Indicates whether to include NLog-specific extensions to log4j schema.</xs:documentation> <xs:documentation>Indicates whether to include NLog-specific extensions to log4j schema.</xs:documentation>
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
<xs:attribute name="includeNdc" type="xs:boolean"> <xs:attribute name="includeMdc" type="xs:boolean">
<xs:annotation> <xs:annotation>
<xs:documentation>Indicates whether to include stack contents.</xs:documentation> <xs:documentation>Indicates whether to include dictionary contents.</xs:documentation>
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
<xs:attribute name="includeCallSite" type="xs:boolean"> <xs:attribute name="includeCallSite" type="xs:boolean">
@ -512,6 +520,11 @@
<xs:documentation>NDC item separator.</xs:documentation> <xs:documentation>NDC item separator.</xs:documentation>
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
<xs:attribute name="includeMdlc" type="xs:boolean">
<xs:annotation>
<xs:documentation>Indicates whether to include dictionary contents.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="optimizeBufferReuse" type="xs:boolean"> <xs:attribute name="optimizeBufferReuse" type="xs:boolean">
<xs:annotation> <xs:annotation>
<xs:documentation>Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit</xs:documentation> <xs:documentation>Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit</xs:documentation>
@ -1001,6 +1014,7 @@
<xs:element name="source" minOccurs="0" maxOccurs="1" type="Layout" /> <xs:element name="source" minOccurs="0" maxOccurs="1" type="Layout" />
<xs:element name="onOverflow" minOccurs="0" maxOccurs="1" type="NLog.Targets.EventLogTargetOverflowAction" /> <xs:element name="onOverflow" minOccurs="0" maxOccurs="1" type="NLog.Targets.EventLogTargetOverflowAction" />
<xs:element name="entryType" minOccurs="0" maxOccurs="1" type="Layout" /> <xs:element name="entryType" minOccurs="0" maxOccurs="1" type="Layout" />
<xs:element name="maxKilobytes" minOccurs="0" maxOccurs="1" type="xs:long" />
<xs:element name="maxMessageLength" minOccurs="0" maxOccurs="1" type="xs:integer" /> <xs:element name="maxMessageLength" minOccurs="0" maxOccurs="1" type="xs:integer" />
<xs:element name="optimizeBufferReuse" minOccurs="0" maxOccurs="1" type="xs:boolean" /> <xs:element name="optimizeBufferReuse" minOccurs="0" maxOccurs="1" type="xs:boolean" />
</xs:choice> </xs:choice>
@ -1049,6 +1063,11 @@
<xs:documentation>Optional entrytype. When not set, or when not convertable to then determined by </xs:documentation> <xs:documentation>Optional entrytype. When not set, or when not convertable to then determined by </xs:documentation>
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
<xs:attribute name="maxKilobytes" type="xs:long">
<xs:annotation>
<xs:documentation>Maximum Event log size in kilobytes. If null, the value won't be set. Default is 512 Kilobytes as specified by Eventlog API</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="maxMessageLength" type="xs:integer"> <xs:attribute name="maxMessageLength" type="xs:integer">
<xs:annotation> <xs:annotation>
<xs:documentation>Message length limit to write to the Event Log.</xs:documentation> <xs:documentation>Message length limit to write to the Event Log.</xs:documentation>
@ -1117,18 +1136,19 @@
<xs:element name="discardAll" minOccurs="0" maxOccurs="1" type="xs:boolean" /> <xs:element name="discardAll" minOccurs="0" maxOccurs="1" type="xs:boolean" />
<xs:element name="fileNameKind" minOccurs="0" maxOccurs="1" type="NLog.Targets.FilePathKind" /> <xs:element name="fileNameKind" minOccurs="0" maxOccurs="1" type="NLog.Targets.FilePathKind" />
<xs:element name="forceMutexConcurrentWrites" minOccurs="0" maxOccurs="1" type="xs:boolean" /> <xs:element name="forceMutexConcurrentWrites" minOccurs="0" maxOccurs="1" type="xs:boolean" />
<xs:element name="maxLogFilenames" minOccurs="0" maxOccurs="1" type="xs:integer" />
<xs:element name="writeFooterOnArchivingOnly" minOccurs="0" maxOccurs="1" type="xs:boolean" /> <xs:element name="writeFooterOnArchivingOnly" minOccurs="0" maxOccurs="1" type="xs:boolean" />
<xs:element name="fileName" minOccurs="0" maxOccurs="1" type="Layout" /> <xs:element name="fileName" minOccurs="0" maxOccurs="1" type="Layout" />
<xs:element name="archiveDateFormat" minOccurs="0" maxOccurs="1" type="xs:string" /> <xs:element name="archiveDateFormat" minOccurs="0" maxOccurs="1" type="xs:string" />
<xs:element name="archiveOldFileOnStartup" minOccurs="0" maxOccurs="1" type="xs:boolean" /> <xs:element name="archiveOldFileOnStartup" minOccurs="0" maxOccurs="1" type="xs:boolean" />
<xs:element name="createDirs" minOccurs="0" maxOccurs="1" type="xs:boolean" /> <xs:element name="createDirs" minOccurs="0" maxOccurs="1" type="xs:boolean" />
<xs:element name="enableFileDelete" minOccurs="0" maxOccurs="1" type="xs:boolean" />
<xs:element name="fileAttributes" minOccurs="0" maxOccurs="1" type="NLog.Targets.Win32FileAttributes" /> <xs:element name="fileAttributes" minOccurs="0" maxOccurs="1" type="NLog.Targets.Win32FileAttributes" />
<xs:element name="deleteOldFileOnStartup" minOccurs="0" maxOccurs="1" type="xs:boolean" /> <xs:element name="deleteOldFileOnStartup" minOccurs="0" maxOccurs="1" type="xs:boolean" />
<xs:element name="replaceFileContentsOnEachWrite" minOccurs="0" maxOccurs="1" type="xs:boolean" /> <xs:element name="replaceFileContentsOnEachWrite" minOccurs="0" maxOccurs="1" type="xs:boolean" />
<xs:element name="enableFileDelete" minOccurs="0" maxOccurs="1" type="xs:boolean" />
<xs:element name="concurrentWriteAttempts" minOccurs="0" maxOccurs="1" type="xs:integer" />
<xs:element name="concurrentWrites" minOccurs="0" maxOccurs="1" type="xs:boolean" /> <xs:element name="concurrentWrites" minOccurs="0" maxOccurs="1" type="xs:boolean" />
<xs:element name="keepFileOpen" minOccurs="0" maxOccurs="1" type="xs:boolean" /> <xs:element name="keepFileOpen" minOccurs="0" maxOccurs="1" type="xs:boolean" />
<xs:element name="maxLogFilenames" minOccurs="0" maxOccurs="1" type="xs:integer" />
<xs:element name="networkWrites" minOccurs="0" maxOccurs="1" type="xs:boolean" /> <xs:element name="networkWrites" minOccurs="0" maxOccurs="1" type="xs:boolean" />
<xs:element name="openFileCacheSize" minOccurs="0" maxOccurs="1" type="xs:integer" /> <xs:element name="openFileCacheSize" minOccurs="0" maxOccurs="1" type="xs:integer" />
<xs:element name="openFileCacheTimeout" minOccurs="0" maxOccurs="1" type="xs:integer" /> <xs:element name="openFileCacheTimeout" minOccurs="0" maxOccurs="1" type="xs:integer" />
@ -1136,7 +1156,6 @@
<xs:element name="bufferSize" minOccurs="0" maxOccurs="1" type="xs:integer" /> <xs:element name="bufferSize" minOccurs="0" maxOccurs="1" type="xs:integer" />
<xs:element name="autoFlush" minOccurs="0" maxOccurs="1" type="xs:boolean" /> <xs:element name="autoFlush" minOccurs="0" maxOccurs="1" type="xs:boolean" />
<xs:element name="concurrentWriteAttemptDelay" minOccurs="0" maxOccurs="1" type="xs:integer" /> <xs:element name="concurrentWriteAttemptDelay" minOccurs="0" maxOccurs="1" type="xs:integer" />
<xs:element name="concurrentWriteAttempts" minOccurs="0" maxOccurs="1" type="xs:integer" />
</xs:choice> </xs:choice>
<xs:attribute name="name" type="xs:string"> <xs:attribute name="name" type="xs:string">
<xs:annotation> <xs:annotation>
@ -1228,6 +1247,11 @@
<xs:documentation>Value indicationg whether file creation calls should be synchronized by a system global mutex.</xs:documentation> <xs:documentation>Value indicationg whether file creation calls should be synchronized by a system global mutex.</xs:documentation>
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
<xs:attribute name="maxLogFilenames" type="xs:integer">
<xs:annotation>
<xs:documentation>Maximum number of log filenames that should be stored as existing.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="writeFooterOnArchivingOnly" type="xs:boolean"> <xs:attribute name="writeFooterOnArchivingOnly" type="xs:boolean">
<xs:annotation> <xs:annotation>
<xs:documentation>Indicates whether the footer should be written only when the file is archived.</xs:documentation> <xs:documentation>Indicates whether the footer should be written only when the file is archived.</xs:documentation>
@ -1253,11 +1277,6 @@
<xs:documentation>Indicates whether to create directories if they do not exist.</xs:documentation> <xs:documentation>Indicates whether to create directories if they do not exist.</xs:documentation>
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
<xs:attribute name="enableFileDelete" type="xs:boolean">
<xs:annotation>
<xs:documentation>Indicates whether to enable log file(s) to be deleted.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="fileAttributes" type="NLog.Targets.Win32FileAttributes"> <xs:attribute name="fileAttributes" type="NLog.Targets.Win32FileAttributes">
<xs:annotation> <xs:annotation>
<xs:documentation>File attributes (Windows only).</xs:documentation> <xs:documentation>File attributes (Windows only).</xs:documentation>
@ -1273,6 +1292,16 @@
<xs:documentation>Indicates whether to replace file contents on each write instead of appending log message at the end.</xs:documentation> <xs:documentation>Indicates whether to replace file contents on each write instead of appending log message at the end.</xs:documentation>
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
<xs:attribute name="enableFileDelete" type="xs:boolean">
<xs:annotation>
<xs:documentation>Indicates whether to enable log file(s) to be deleted.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="concurrentWriteAttempts" type="xs:integer">
<xs:annotation>
<xs:documentation>Number of times the write is appended on the file before NLog discards the log message.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="concurrentWrites" type="xs:boolean"> <xs:attribute name="concurrentWrites" type="xs:boolean">
<xs:annotation> <xs:annotation>
<xs:documentation>Indicates whether concurrent writes to the log file by multiple processes on the same host.</xs:documentation> <xs:documentation>Indicates whether concurrent writes to the log file by multiple processes on the same host.</xs:documentation>
@ -1283,11 +1312,6 @@
<xs:documentation>Indicates whether to keep log file open instead of opening and closing it on each logging event.</xs:documentation> <xs:documentation>Indicates whether to keep log file open instead of opening and closing it on each logging event.</xs:documentation>
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
<xs:attribute name="maxLogFilenames" type="xs:integer">
<xs:annotation>
<xs:documentation>Maximum number of log filenames that should be stored as existing.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="networkWrites" type="xs:boolean"> <xs:attribute name="networkWrites" type="xs:boolean">
<xs:annotation> <xs:annotation>
<xs:documentation>Indicates whether concurrent writes to the log file by multiple processes on different network hosts.</xs:documentation> <xs:documentation>Indicates whether concurrent writes to the log file by multiple processes on different network hosts.</xs:documentation>
@ -1323,11 +1347,6 @@
<xs:documentation>Delay in milliseconds to wait before attempting to write to the file again.</xs:documentation> <xs:documentation>Delay in milliseconds to wait before attempting to write to the file again.</xs:documentation>
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
<xs:attribute name="concurrentWriteAttempts" type="xs:integer">
<xs:annotation>
<xs:documentation>Number of times the write is appended on the file before NLog discards the log message.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:extension> </xs:extension>
</xs:complexContent> </xs:complexContent>
</xs:complexType> </xs:complexType>
@ -1587,6 +1606,7 @@
<xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="layout" minOccurs="0" maxOccurs="1" type="Layout" /> <xs:element name="layout" minOccurs="0" maxOccurs="1" type="Layout" />
<xs:element name="name" minOccurs="0" maxOccurs="1" type="xs:string" /> <xs:element name="name" minOccurs="0" maxOccurs="1" type="xs:string" />
<xs:element name="parameterType" minOccurs="0" maxOccurs="1" type="xs:string" />
<xs:element name="type" minOccurs="0" maxOccurs="1" type="xs:string" /> <xs:element name="type" minOccurs="0" maxOccurs="1" type="xs:string" />
</xs:choice> </xs:choice>
<xs:attribute name="layout" type="SimpleLayoutAttribute"> <xs:attribute name="layout" type="SimpleLayoutAttribute">
@ -1599,11 +1619,16 @@
<xs:documentation>Name of the parameter.</xs:documentation> <xs:documentation>Name of the parameter.</xs:documentation>
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
<xs:attribute name="type" type="xs:string"> <xs:attribute name="parameterType" type="xs:string">
<xs:annotation> <xs:annotation>
<xs:documentation>Type of the parameter.</xs:documentation> <xs:documentation>Type of the parameter.</xs:documentation>
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
<xs:attribute name="type" type="xs:string">
<xs:annotation>
<xs:documentation>Type of the parameter. Obsolete alias for </xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType> </xs:complexType>
<xs:complexType name="Mail"> <xs:complexType name="Mail">
<xs:complexContent> <xs:complexContent>
@ -2020,13 +2045,14 @@
<xs:element name="address" minOccurs="0" maxOccurs="1" type="Layout" /> <xs:element name="address" minOccurs="0" maxOccurs="1" type="Layout" />
<xs:element name="maxQueueSize" minOccurs="0" maxOccurs="1" type="xs:integer" /> <xs:element name="maxQueueSize" minOccurs="0" maxOccurs="1" type="xs:integer" />
<xs:element name="parameter" minOccurs="0" maxOccurs="unbounded" type="NLog.Targets.NLogViewerParameterInfo" /> <xs:element name="parameter" minOccurs="0" maxOccurs="unbounded" type="NLog.Targets.NLogViewerParameterInfo" />
<xs:element name="includeMdc" minOccurs="0" maxOccurs="1" type="xs:boolean" /> <xs:element name="includeNdc" minOccurs="0" maxOccurs="1" type="xs:boolean" />
<xs:element name="includeSourceInfo" minOccurs="0" maxOccurs="1" type="xs:boolean" /> <xs:element name="includeSourceInfo" minOccurs="0" maxOccurs="1" type="xs:boolean" />
<xs:element name="includeNLogData" minOccurs="0" maxOccurs="1" type="xs:boolean" /> <xs:element name="includeNLogData" minOccurs="0" maxOccurs="1" type="xs:boolean" />
<xs:element name="includeNdc" minOccurs="0" maxOccurs="1" type="xs:boolean" /> <xs:element name="includeMdc" minOccurs="0" maxOccurs="1" type="xs:boolean" />
<xs:element name="includeCallSite" minOccurs="0" maxOccurs="1" type="xs:boolean" /> <xs:element name="includeCallSite" minOccurs="0" maxOccurs="1" type="xs:boolean" />
<xs:element name="appInfo" minOccurs="0" maxOccurs="1" type="xs:string" /> <xs:element name="appInfo" minOccurs="0" maxOccurs="1" type="xs:string" />
<xs:element name="ndcItemSeparator" minOccurs="0" maxOccurs="1" type="xs:string" /> <xs:element name="ndcItemSeparator" minOccurs="0" maxOccurs="1" type="xs:string" />
<xs:element name="includeMdlc" minOccurs="0" maxOccurs="1" type="xs:boolean" />
<xs:element name="optimizeBufferReuse" minOccurs="0" maxOccurs="1" type="xs:boolean" /> <xs:element name="optimizeBufferReuse" minOccurs="0" maxOccurs="1" type="xs:boolean" />
</xs:choice> </xs:choice>
<xs:attribute name="name" type="xs:string"> <xs:attribute name="name" type="xs:string">
@ -2094,9 +2120,9 @@
<xs:documentation>Maximum queue size.</xs:documentation> <xs:documentation>Maximum queue size.</xs:documentation>
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
<xs:attribute name="includeMdc" type="xs:boolean"> <xs:attribute name="includeNdc" type="xs:boolean">
<xs:annotation> <xs:annotation>
<xs:documentation>Indicates whether to include dictionary contents.</xs:documentation> <xs:documentation>Indicates whether to include stack contents.</xs:documentation>
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
<xs:attribute name="includeSourceInfo" type="xs:boolean"> <xs:attribute name="includeSourceInfo" type="xs:boolean">
@ -2109,9 +2135,9 @@
<xs:documentation>Indicates whether to include NLog-specific extensions to log4j schema.</xs:documentation> <xs:documentation>Indicates whether to include NLog-specific extensions to log4j schema.</xs:documentation>
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
<xs:attribute name="includeNdc" type="xs:boolean"> <xs:attribute name="includeMdc" type="xs:boolean">
<xs:annotation> <xs:annotation>
<xs:documentation>Indicates whether to include stack contents.</xs:documentation> <xs:documentation>Indicates whether to include dictionary contents.</xs:documentation>
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
<xs:attribute name="includeCallSite" type="xs:boolean"> <xs:attribute name="includeCallSite" type="xs:boolean">
@ -2129,6 +2155,11 @@
<xs:documentation>NDC item separator.</xs:documentation> <xs:documentation>NDC item separator.</xs:documentation>
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
<xs:attribute name="includeMdlc" type="xs:boolean">
<xs:annotation>
<xs:documentation>Indicates whether to include dictionary contents.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="optimizeBufferReuse" type="xs:boolean"> <xs:attribute name="optimizeBufferReuse" type="xs:boolean">
<xs:annotation> <xs:annotation>
<xs:documentation>Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit</xs:documentation> <xs:documentation>Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit</xs:documentation>
@ -2487,8 +2518,10 @@
<xs:element name="encoding" minOccurs="0" maxOccurs="1" type="xs:string" /> <xs:element name="encoding" minOccurs="0" maxOccurs="1" type="xs:string" />
<xs:element name="escapeDataNLogLegacy" minOccurs="0" maxOccurs="1" type="xs:boolean" /> <xs:element name="escapeDataNLogLegacy" minOccurs="0" maxOccurs="1" type="xs:boolean" />
<xs:element name="escapeDataRfc3986" minOccurs="0" maxOccurs="1" type="xs:boolean" /> <xs:element name="escapeDataRfc3986" minOccurs="0" maxOccurs="1" type="xs:boolean" />
<xs:element name="header" minOccurs="0" maxOccurs="unbounded" type="NLog.Targets.MethodCallParameter" />
<xs:element name="methodName" minOccurs="0" maxOccurs="1" type="xs:string" /> <xs:element name="methodName" minOccurs="0" maxOccurs="1" type="xs:string" />
<xs:element name="namespace" minOccurs="0" maxOccurs="1" type="xs:string" /> <xs:element name="namespace" minOccurs="0" maxOccurs="1" type="xs:string" />
<xs:element name="preAuthenticate" minOccurs="0" maxOccurs="1" type="xs:boolean" />
<xs:element name="protocol" minOccurs="0" maxOccurs="1" type="NLog.Targets.WebServiceProtocol" /> <xs:element name="protocol" minOccurs="0" maxOccurs="1" type="NLog.Targets.WebServiceProtocol" />
<xs:element name="url" minOccurs="0" maxOccurs="1" type="xs:anyURI" /> <xs:element name="url" minOccurs="0" maxOccurs="1" type="xs:anyURI" />
<xs:element name="xmlRoot" minOccurs="0" maxOccurs="1" type="xs:string" /> <xs:element name="xmlRoot" minOccurs="0" maxOccurs="1" type="xs:string" />
@ -2534,6 +2567,11 @@
<xs:documentation>Web service namespace. Only used with Soap.</xs:documentation> <xs:documentation>Web service namespace. Only used with Soap.</xs:documentation>
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
<xs:attribute name="preAuthenticate" type="xs:boolean">
<xs:annotation>
<xs:documentation>Indicates whether to pre-authenticate the HttpWebRequest (Requires 'Authorization' in parameters)</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="protocol" type="NLog.Targets.WebServiceProtocol"> <xs:attribute name="protocol" type="NLog.Targets.WebServiceProtocol">
<xs:annotation> <xs:annotation>
<xs:documentation>Protocol to be used when calling web service.</xs:documentation> <xs:documentation>Protocol to be used when calling web service.</xs:documentation>
@ -2677,8 +2715,10 @@
<xs:element name="attribute" minOccurs="0" maxOccurs="unbounded" type="NLog.Layouts.JsonAttribute" /> <xs:element name="attribute" minOccurs="0" maxOccurs="unbounded" type="NLog.Layouts.JsonAttribute" />
<xs:element name="excludeProperties" minOccurs="0" maxOccurs="1" type="xs:string" /> <xs:element name="excludeProperties" minOccurs="0" maxOccurs="1" type="xs:string" />
<xs:element name="includeAllProperties" minOccurs="0" maxOccurs="1" type="xs:boolean" /> <xs:element name="includeAllProperties" minOccurs="0" maxOccurs="1" type="xs:boolean" />
<xs:element name="includeMdc" minOccurs="0" maxOccurs="1" type="xs:boolean" />
<xs:element name="renderEmptyObject" minOccurs="0" maxOccurs="1" type="xs:boolean" /> <xs:element name="renderEmptyObject" minOccurs="0" maxOccurs="1" type="xs:boolean" />
<xs:element name="suppressSpaces" minOccurs="0" maxOccurs="1" type="xs:boolean" /> <xs:element name="suppressSpaces" minOccurs="0" maxOccurs="1" type="xs:boolean" />
<xs:element name="includeMdlc" minOccurs="0" maxOccurs="1" type="xs:boolean" />
</xs:choice> </xs:choice>
<xs:attribute name="excludeProperties" type="xs:string"> <xs:attribute name="excludeProperties" type="xs:string">
<xs:annotation> <xs:annotation>
@ -2690,6 +2730,11 @@
<xs:documentation>Option to include all properties from the log events</xs:documentation> <xs:documentation>Option to include all properties from the log events</xs:documentation>
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
<xs:attribute name="includeMdc" type="xs:boolean">
<xs:annotation>
<xs:documentation>Indicates whether to include contents of the dictionary.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="renderEmptyObject" type="xs:boolean"> <xs:attribute name="renderEmptyObject" type="xs:boolean">
<xs:annotation> <xs:annotation>
<xs:documentation>Option to render the empty object value {}</xs:documentation> <xs:documentation>Option to render the empty object value {}</xs:documentation>
@ -2700,12 +2745,18 @@
<xs:documentation>Option to suppress the extra spaces in the output json</xs:documentation> <xs:documentation>Option to suppress the extra spaces in the output json</xs:documentation>
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
<xs:attribute name="includeMdlc" type="xs:boolean">
<xs:annotation>
<xs:documentation>Indicates whether to include contents of the dictionary.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:extension> </xs:extension>
</xs:complexContent> </xs:complexContent>
</xs:complexType> </xs:complexType>
<xs:complexType name="NLog.Layouts.JsonAttribute"> <xs:complexType name="NLog.Layouts.JsonAttribute">
<xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="encode" minOccurs="0" maxOccurs="1" type="xs:boolean" /> <xs:element name="encode" minOccurs="0" maxOccurs="1" type="xs:boolean" />
<xs:element name="escapeUnicode" minOccurs="0" maxOccurs="1" type="xs:boolean" />
<xs:element name="layout" minOccurs="0" maxOccurs="1" type="Layout" /> <xs:element name="layout" minOccurs="0" maxOccurs="1" type="Layout" />
<xs:element name="name" minOccurs="0" maxOccurs="1" type="xs:string" /> <xs:element name="name" minOccurs="0" maxOccurs="1" type="xs:string" />
</xs:choice> </xs:choice>
@ -2714,6 +2765,11 @@
<xs:documentation>Determines wether or not this attribute will be Json encoded.</xs:documentation> <xs:documentation>Determines wether or not this attribute will be Json encoded.</xs:documentation>
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
<xs:attribute name="escapeUnicode" type="xs:boolean">
<xs:annotation>
<xs:documentation>Indicates whether to escape non-ascii characters</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="layout" type="SimpleLayoutAttribute"> <xs:attribute name="layout" type="SimpleLayoutAttribute">
<xs:annotation> <xs:annotation>
<xs:documentation>Layout that will be rendered as the attribute's value.</xs:documentation> <xs:documentation>Layout that will be rendered as the attribute's value.</xs:documentation>
@ -2754,7 +2810,26 @@
<xs:complexType name="Log4JXmlEventLayout"> <xs:complexType name="Log4JXmlEventLayout">
<xs:complexContent> <xs:complexContent>
<xs:extension base="Layout"> <xs:extension base="Layout">
<xs:choice minOccurs="0" maxOccurs="unbounded" /> <xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="includeAllProperties" minOccurs="0" maxOccurs="1" type="xs:boolean" />
<xs:element name="includeMdc" minOccurs="0" maxOccurs="1" type="xs:boolean" />
<xs:element name="includeMdlc" minOccurs="0" maxOccurs="1" type="xs:boolean" />
</xs:choice>
<xs:attribute name="includeAllProperties" type="xs:boolean">
<xs:annotation>
<xs:documentation>Option to include all properties from the log events</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="includeMdc" type="xs:boolean">
<xs:annotation>
<xs:documentation>Indicates whether to include contents of the dictionary.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="includeMdlc" type="xs:boolean">
<xs:annotation>
<xs:documentation>Indicates whether to include contents of the dictionary.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:extension> </xs:extension>
</xs:complexContent> </xs:complexContent>
</xs:complexType> </xs:complexType>
@ -2929,6 +3004,74 @@
</xs:extension> </xs:extension>
</xs:complexContent> </xs:complexContent>
</xs:complexType> </xs:complexType>
<xs:complexType name="whenRepeated">
<xs:complexContent>
<xs:extension base="Filter">
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="action" minOccurs="0" maxOccurs="1" type="FilterResult" />
<xs:element name="layout" minOccurs="0" maxOccurs="1" type="Layout" />
<xs:element name="defaultFilterCacheSize" minOccurs="0" maxOccurs="1" type="xs:integer" />
<xs:element name="filterCountMessageAppendFormat" minOccurs="0" maxOccurs="1" type="xs:string" />
<xs:element name="filterCountPropertyName" minOccurs="0" maxOccurs="1" type="xs:string" />
<xs:element name="maxFilterCacheSize" minOccurs="0" maxOccurs="1" type="xs:integer" />
<xs:element name="maxLength" minOccurs="0" maxOccurs="1" type="xs:integer" />
<xs:element name="optimizeBufferDefaultLength" minOccurs="0" maxOccurs="1" type="xs:integer" />
<xs:element name="optimizeBufferReuse" minOccurs="0" maxOccurs="1" type="xs:boolean" />
<xs:element name="timeoutSeconds" minOccurs="0" maxOccurs="1" type="xs:integer" />
</xs:choice>
<xs:attribute name="action" type="FilterResult">
<xs:annotation>
<xs:documentation>Action to be taken when filter matches.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="layout" type="SimpleLayoutAttribute">
<xs:annotation>
<xs:documentation>Layout to be used to filter log messages.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="defaultFilterCacheSize" type="xs:integer">
<xs:annotation>
<xs:documentation>Default number of unique filter values to expect, will automatically increase if needed</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="filterCountMessageAppendFormat" type="xs:string">
<xs:annotation>
<xs:documentation>Append FilterCount to the when an event is no longer filtered</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="filterCountPropertyName" type="xs:string">
<xs:annotation>
<xs:documentation>Insert FilterCount value into when an event is no longer filtered</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="maxFilterCacheSize" type="xs:integer">
<xs:annotation>
<xs:documentation>Max number of unique filter values to expect simultaneously</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="maxLength" type="xs:integer">
<xs:annotation>
<xs:documentation>Max length of filter values, will truncate if above limit</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="optimizeBufferDefaultLength" type="xs:integer">
<xs:annotation>
<xs:documentation>Default buffer size for the internal buffers</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="optimizeBufferReuse" type="xs:boolean">
<xs:annotation>
<xs:documentation>Reuse internal buffers, and doesn't have to constantly allocate new buffers</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="timeoutSeconds" type="xs:integer">
<xs:annotation>
<xs:documentation>How long before a filter expires, and logging is accepted again</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="AccurateLocal"> <xs:complexType name="AccurateLocal">
<xs:complexContent> <xs:complexContent>
<xs:extension base="TimeSource"> <xs:extension base="TimeSource">

View File

@ -12,6 +12,8 @@ namespace Artemis.Profiles.Layers.Conditions
lock (layerModel.Properties.Conditions) lock (layerModel.Properties.Conditions)
{ {
var checkConditions = layerModel.Properties.Conditions.Where(c => c.Field != null).ToList(); var checkConditions = layerModel.Properties.Conditions.Where(c => c.Field != null).ToList();
if (!checkConditions.Any())
return true;
switch (layerModel.Properties.ConditionType) switch (layerModel.Properties.ConditionType)
{ {
case ConditionType.AnyMet: case ConditionType.AnyMet:

View File

@ -1,118 +1,174 @@
using System; using System;
using System.Globalization; using System.Collections;
using Artemis.Modules.Abstract; using System.Collections.Generic;
using Artemis.Utilities; using System.Globalization;
using DynamicExpresso; using System.Linq;
using MahApps.Metro.Controls; using System.Text.RegularExpressions;
using System.Windows.Documents;
namespace Artemis.Profiles.Layers.Models using Artemis.Modules.Abstract;
{ using Artemis.Utilities;
public class LayerConditionModel using DynamicExpresso;
{ using MahApps.Metro.Controls;
private readonly Interpreter _interpreter;
private object _lastValue; namespace Artemis.Profiles.Layers.Models
{
public LayerConditionModel() public class LayerConditionModel
{ {
_interpreter = new Interpreter(); private readonly Interpreter _interpreter;
} private object _lastValue;
private Regex _rgx;
public string Field { get; set; }
public string Value { get; set; } public LayerConditionModel()
public string Operator { get; set; } {
public string Type { get; set; } _interpreter = new Interpreter();
public HotKey HotKey { get; set; } _rgx = new Regex("\\((.*?)\\)");
}
public bool ConditionMet(ModuleDataModel subject)
{ public string Field { get; set; }
lock (subject) public string Value { get; set; }
{ public string Operator { get; set; }
if (string.IsNullOrEmpty(Field) || string.IsNullOrEmpty(Type)) public string Type { get; set; }
return false; public HotKey HotKey { get; set; }
var inspect = GeneralHelpers.GetPropertyValue(subject, Field); public bool ConditionMet(ModuleDataModel subject)
if (inspect == null) {
{ lock (subject)
_lastValue = null; {
return false; if (string.IsNullOrEmpty(Field) || string.IsNullOrEmpty(Type))
} return false;
bool returnValue; // If the path points to a collection, look inside this collection
if (Operator == "changed" || Operator == "decreased" || Operator == "increased") if (Field.Contains("("))
returnValue = EvaluateEventOperator(subject, inspect); {
else // Find the collection in the field path
returnValue = EvaluateOperator(subject); var collectionField = _rgx.Match(Field).Groups[1].Value;
var collectionInspect = (IEnumerable) GeneralHelpers.GetPropertyValue(subject, collectionField);
_lastValue = inspect; var operatorParts = Operator.Split('|');
return returnValue; var field = Field.Split(')').Last().Substring(1);
}
} _lastValue = collectionInspect;
private bool EvaluateEventOperator(ModuleDataModel subject, object inspect) if (operatorParts[0] == "any")
{ {
// DynamicExpresso doesn't want a null so when it was previously null (should only happen first iteration) var anyMatch = false;
// return false since that would be the only possible outcome foreach (var collectionValue in collectionInspect)
if (_lastValue == null) {
return false; anyMatch = EvaluateOperator(collectionValue, field, operatorParts[1]);
if (anyMatch)
// Assign the right parameter break;
var rightParam = new Parameter("value", _lastValue); }
return anyMatch;
// Come up with the proper operator }
var changeOperator = ""; if (operatorParts[0] == "all")
if (Operator == "changed") {
changeOperator = "!="; var allMatch = true;
else if (Operator == "decreased") foreach (var collectionValue in collectionInspect)
changeOperator = "<"; {
else if (Operator == "increased") allMatch = EvaluateOperator(collectionValue, field, operatorParts[1]);
changeOperator = ">"; if (!allMatch)
break;
// Evaluate the result and store it }
var returnValue = _interpreter.Eval<bool>($"subject.{Field} {changeOperator} value", return allMatch;
new Parameter("subject", subject.GetType(), subject), rightParam); }
if (operatorParts[0] == "none")
// Set the last value to the new value {
_lastValue = inspect; var noneMatch = true;
// Return the evaluated result foreach (var collectionValue in collectionInspect)
return returnValue; {
} noneMatch = !EvaluateOperator(collectionValue, field, operatorParts[1]);
if (!noneMatch)
private bool EvaluateOperator(ModuleDataModel subject) break;
{ }
// Since _lastValue won't be used, rely on Value to not be null return noneMatch;
if (string.IsNullOrEmpty(Value)) }
return false; }
// Put the subject in a list, allowing Dynamic Linq to be used. var inspect = GeneralHelpers.GetPropertyValue(subject, Field);
if (Type == "String") if (inspect == null)
{ {
return _interpreter.Eval<bool>($"subject.{Field}.ToLower(){Operator}(value)", _lastValue = null;
new Parameter("subject", subject.GetType(), subject), return false;
new Parameter("value", Value.ToLower())); }
}
bool returnValue;
Parameter rightParam = null; if (Operator == "changed" || Operator == "decreased" || Operator == "increased")
switch (Type) returnValue = EvaluateEventOperator(subject, inspect);
{ else
case "Enum": returnValue = EvaluateOperator(subject, Field);
var enumType = GeneralHelpers.GetPropertyValue(subject, Field).GetType();
rightParam = new Parameter("value", Enum.Parse(enumType, Value)); _lastValue = inspect;
break; return returnValue;
case "Boolean": }
rightParam = new Parameter("value", bool.Parse(Value)); }
break;
case "Int32": private bool EvaluateEventOperator(ModuleDataModel subject, object inspect)
rightParam = new Parameter("value", int.Parse(Value)); {
break; // DynamicExpresso doesn't want a null so when it was previously null (should only happen first iteration)
case "Single": // return false since that would be the only possible outcome
// Parse commas as decimals if (_lastValue == null)
rightParam = new Parameter("value", float.Parse(Value.Replace(",", "."), return false;
CultureInfo.InvariantCulture));
break; // Assign the right parameter
} var rightParam = new Parameter("value", _lastValue);
return _interpreter.Eval<bool>($"subject.{Field} {Operator} value", // Come up with the proper operator
new Parameter("subject", subject.GetType(), subject), rightParam); var changeOperator = "";
} if (Operator == "changed")
} changeOperator = "!=";
} else if (Operator == "decreased")
changeOperator = "<";
else if (Operator == "increased")
changeOperator = ">";
// Evaluate the result and store it
var returnValue = _interpreter.Eval<bool>($"subject.{Field} {changeOperator} value", new Parameter("subject", subject.GetType(), subject), rightParam);
// Set the last value to the new value
_lastValue = inspect;
// Return the evaluated result
return returnValue;
}
private bool EvaluateOperator(object subject, string field, string operatorOverwrite = null)
{
// Since _lastValue won't be used, rely on Value to not be null
if (string.IsNullOrEmpty(Value))
return false;
if (Type == "String")
{
var stringExpressionText = operatorOverwrite == null
? $"subject.{field}.ToLower(){Operator}(value)"
: $"subject.{field}.ToLower(){operatorOverwrite}(value)";
return _interpreter.Eval<bool>(stringExpressionText, new Parameter("subject", subject.GetType(), subject), new Parameter("value", Value.ToLower()));
}
Parameter rightParam = null;
switch (Type)
{
case "Enum":
var enumType = GeneralHelpers.GetPropertyValue(subject, field).GetType();
rightParam = new Parameter("value", Enum.Parse(enumType, Value));
break;
case "Boolean":
rightParam = new Parameter("value", bool.Parse(Value));
break;
case "Int32":
rightParam = new Parameter("value", int.Parse(Value));
break;
case "Single":
// Parse commas as decimals
rightParam = new Parameter("value", float.Parse(Value.Replace(",", "."),
CultureInfo.InvariantCulture));
break;
}
var expressionText = operatorOverwrite == null
? $"subject.{field} {Operator} value"
: $"subject.{field} {operatorOverwrite} value";
return _interpreter.Eval<bool>(expressionText, new Parameter("subject", subject.GetType(), subject), rightParam);
}
}
}

View File

@ -11,6 +11,7 @@ using Artemis.Profiles.Layers.Interfaces;
using Artemis.Profiles.Layers.Types.Keyboard; using Artemis.Profiles.Layers.Types.Keyboard;
using Artemis.Utilities; using Artemis.Utilities;
using Artemis.Utilities.ParentChild; using Artemis.Utilities.ParentChild;
using Betwixt;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace Artemis.Profiles.Layers.Models namespace Artemis.Profiles.Layers.Models
@ -26,8 +27,14 @@ namespace Artemis.Profiles.Layers.Models
var model = Properties as KeyboardPropertiesModel; var model = Properties as KeyboardPropertiesModel;
if (model != null) if (model != null)
GifImage = new GifImage(model.GifFile, Properties.AnimationSpeed); GifImage = new GifImage(model.GifFile, Properties.AnimationSpeed);
LayerConditionsMet += OnLayerConditionsMet;
LayerConditionsUnmet += OnLayerConditionsUnmet;
} }
public event EventHandler<EventArgs> LayerConditionsMet;
public event EventHandler<EventArgs> LayerConditionsUnmet;
[JsonIgnore] [JsonIgnore]
public ImageSource LayerImage => LayerType.DrawThumbnail(this); public ImageSource LayerImage => LayerType.DrawThumbnail(this);
@ -43,7 +50,18 @@ namespace Artemis.Profiles.Layers.Models
public bool AreConditionsMet(ModuleDataModel dataModel) public bool AreConditionsMet(ModuleDataModel dataModel)
{ {
// Conditions are not even checked if the layer isn't enabled // Conditions are not even checked if the layer isn't enabled
return Enabled && LayerCondition.ConditionsMet(this, dataModel); if (!Enabled)
return false;
FadeTweener?.Update(40);
var conditionsMet = LayerCondition.ConditionsMet(this, dataModel);
if (conditionsMet && !_conditionsMetLastFrame)
OnLayerConditionsMet();
if (!conditionsMet && _conditionsMetLastFrame)
OnLayerConditionsUnmet();
_conditionsMetLastFrame = conditionsMet;
return FadeTweener != null && FadeTweener.Running || conditionsMet;
} }
/// <summary> /// <summary>
@ -96,7 +114,37 @@ namespace Artemis.Profiles.Layers.Models
if (Brush == null || !preview && !RenderAllowed) if (Brush == null || !preview && !RenderAllowed)
return; return;
ApplyHierarchyOpacity(c);
LayerType.Draw(this, c); LayerType.Draw(this, c);
PopHierarchyOpacity(c);
}
private void ApplyHierarchyOpacity(DrawingContext c)
{
if (FadeTweener != null && FadeTweener.Running)
c.PushOpacity(FadeTweener.Value);
var current = this;
while (current.Parent != null)
{
current = current.Parent;
if (current.FadeTweener != null && current.FadeTweener.Running)
c.PushOpacity(current.FadeTweener.Value);
}
}
private void PopHierarchyOpacity(DrawingContext c)
{
if (FadeTweener != null && FadeTweener.Running)
c.Pop();
var current = this;
while (current.Parent != null)
{
current = current.Parent;
if (current.FadeTweener != null && current.FadeTweener.Running)
c.Pop();
}
} }
/// <summary> /// <summary>
@ -303,6 +351,29 @@ namespace Artemis.Profiles.Layers.Models
keybindModel.Unregister(); keybindModel.Unregister();
} }
private void OnLayerConditionsMet(object sender, EventArgs eventArgs)
{
if (FadeInTime <= 0)
return;
if (FadeTweener != null && FadeTweener.Running)
FadeTweener = new Tweener<float>(FadeTweener.Value, 1, FadeInTime, Ease.Quint.Out, TweenModel.LerpFuncFloat);
else
FadeTweener = new Tweener<float>(0, 1, FadeInTime, Ease.Quint.Out, TweenModel.LerpFuncFloat);
FadeTweener.Start();
}
private void OnLayerConditionsUnmet(object sender, EventArgs eventArgs)
{
if (FadeOutTime <= 0)
return;
if (FadeTweener != null && FadeTweener.Running)
FadeTweener = new Tweener<float>(FadeTweener.Value, 0, FadeOutTime, Ease.Quint.In, TweenModel.LerpFuncFloat);
else
FadeTweener = new Tweener<float>(1, 0, FadeOutTime, Ease.Quint.In, TweenModel.LerpFuncFloat);
FadeTweener.Start();
}
#region Properties #region Properties
#region Layer type properties #region Layer type properties
@ -321,6 +392,8 @@ namespace Artemis.Profiles.Layers.Models
public bool RenderAllowed { get; set; } public bool RenderAllowed { get; set; }
public bool Expanded { get; set; } public bool Expanded { get; set; }
public bool IsEvent { get; set; } public bool IsEvent { get; set; }
public double FadeInTime { get; set; }
public double FadeOutTime { get; set; }
public LayerPropertiesModel Properties { get; set; } public LayerPropertiesModel Properties { get; set; }
public EventPropertiesModel EventProperties { get; set; } public EventPropertiesModel EventProperties { get; set; }
@ -341,6 +414,10 @@ namespace Artemis.Profiles.Layers.Models
#region Render properties #region Render properties
[JsonIgnore] private Brush _brush; [JsonIgnore] private Brush _brush;
[JsonIgnore] private bool _conditionsMetLastFrame;
[JsonIgnore]
public Tweener<float> FadeTweener { get; set; }
[JsonIgnore] [JsonIgnore]
public double X { get; set; } public double X { get; set; }
@ -416,5 +493,15 @@ namespace Artemis.Profiles.Layers.Models
{ {
return $"{nameof(Name)}: {Name}, {nameof(Order)}: {Order}, {nameof(X)}: {X}, {nameof(Y)}: {Y}, {nameof(Width)}: {Width}, {nameof(Height)}: {Height}"; return $"{nameof(Name)}: {Name}, {nameof(Order)}: {Order}, {nameof(X)}: {X}, {nameof(Y)}: {Y}, {nameof(Width)}: {Width}, {nameof(Height)}: {Height}";
} }
protected virtual void OnLayerConditionsMet()
{
LayerConditionsMet?.Invoke(this, EventArgs.Empty);
}
protected virtual void OnLayerConditionsUnmet()
{
LayerConditionsUnmet?.Invoke(this, EventArgs.Empty);
}
} }
} }

View File

@ -42,7 +42,6 @@ namespace Artemis.Profiles.Layers.Models
public double Width { get; set; } public double Width { get; set; }
public double Height { get; set; } public double Height { get; set; }
public bool Contain { get; set; } public bool Contain { get; set; }
public double Opacity { get; set; }
public double AnimationSpeed { get; set; } public double AnimationSpeed { get; set; }
public double OpacityEaseTime { get; set; } public double OpacityEaseTime { get; set; }
public double HeightEaseTime { get; set; } public double HeightEaseTime { get; set; }
@ -55,6 +54,10 @@ namespace Artemis.Profiles.Layers.Models
public List<LayerKeybindModel> LayerKeybindModels { get; set; } = new List<LayerKeybindModel>(); public List<LayerKeybindModel> LayerKeybindModels { get; set; } = new List<LayerKeybindModel>();
public List<DynamicPropertiesModel> DynamicProperties { get; set; } = new List<DynamicPropertiesModel>(); public List<DynamicPropertiesModel> DynamicProperties { get; set; } = new List<DynamicPropertiesModel>();
// Opacity isn't saved since it's only accesable by LUA
[JsonIgnore]
public double Opacity { get; set; } = 1;
[JsonConverter(typeof(BrushJsonConverter))] [JsonConverter(typeof(BrushJsonConverter))]
public Brush Brush public Brush Brush
{ {

View File

@ -136,7 +136,7 @@ namespace Artemis.Profiles.Layers.Models
} }
} }
private static float LerpFuncFloat(float start, float end, float percent) public static float LerpFuncFloat(float start, float end, float percent)
{ {
return start + (end - start) * percent; return start + (end - start) * percent;
} }

View File

@ -0,0 +1,57 @@
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using Artemis.Managers;
using MoonSharp.Interpreter;
using MoonSharp.Interpreter.Interop;
namespace Artemis.Profiles.Lua.Modules.Gui
{
[MoonSharpUserData]
public class LuaSlider
{
private readonly LuaManager _luaManager;
public LuaSlider(LuaManager luaManager, int interval, double intialValue, double minimum, double maximum, double x, double y, double? width, double? height)
{
_luaManager = luaManager;
Slider = new Slider { Value = intialValue, TickFrequency = interval, Minimum = minimum, Maximum = maximum, TickPlacement = TickPlacement.BottomRight, IsSnapToTickEnabled = true};
if (width != null)
Slider.Width = width.Value;
if (height != null)
Slider.Height = height.Value;
Slider.ValueChanged += SliderOnValueChanged;
}
[MoonSharpVisible(false)]
public Slider Slider { get; }
public double Value
{
get => Slider.Dispatcher.Invoke(() => (double) Slider.Value);
set => Slider.Dispatcher.Invoke(() => Slider.Value = value);
}
public int Interval
{
get => Slider.Dispatcher.Invoke(() => (int) Slider.Interval);
set => Slider.Dispatcher.Invoke(() => Slider.Interval = value);
}
private void SliderOnValueChanged(object sender, RoutedPropertyChangedEventArgs<double> routedPropertyChangedEventArgs)
{
_luaManager.EventsModule.LuaInvoke(_luaManager.ProfileModel, () => OnValueChanged(this));
}
public event EventHandler<EventArgs> ValueChanged;
protected virtual void OnValueChanged(LuaSlider slider)
{
ValueChanged?.Invoke(slider, null);
}
}
}

View File

@ -83,6 +83,18 @@ namespace Artemis.Profiles.Lua.Modules.Gui
return element; return element;
} }
public LuaSlider CreateSlider(int interval, double initialValue, double minimum, double maximum, double x, double y, double? width = 200.0, double? height = 20.0)
{
LuaSlider element = null;
Execute.OnUIThread(() =>
{
element = new LuaSlider(_luaManager, interval, initialValue, minimum, maximum, x, y, width, height);
AddControl(element.Slider, x, y);
});
return element;
}
private void AddControl(UIElement uiElement, double x, double y) private void AddControl(UIElement uiElement, double x, double y)
{ {
Canvas.Children.Add(uiElement); Canvas.Children.Add(uiElement);

View File

@ -53,7 +53,7 @@ using System.Windows;
// by using the '*' as shown below: // by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.10.0.0")] [assembly: AssemblyVersion("1.11.0.0")]
[assembly: AssemblyFileVersion("1.10.0.0")] [assembly: AssemblyFileVersion("1.11.0.0")]
[assembly: AssemblyMetadata("SquirrelAwareVersion", "1")]
[assembly: InternalsVisibleTo("Artemis.Explorables")] [assembly: InternalsVisibleTo("Artemis.Explorables")]

View File

@ -19,7 +19,7 @@ namespace Artemis.Properties {
// class via a tool like ResGen or Visual Studio. // class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen // To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project. // with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources { internal class Resources {
@ -70,6 +70,29 @@ namespace Artemis.Properties {
} }
} }
/// <summary>
/// Looks up a localized string similar to &lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-16&quot;?&gt;
///&lt;Task version=&quot;1.4&quot; xmlns=&quot;http://schemas.microsoft.com/windows/2004/02/mit/task&quot;&gt;
/// &lt;RegistrationInfo&gt;
/// &lt;Date&gt;2017-11-12T17:45:45.4993602&lt;/Date&gt;
/// &lt;Author&gt;SpoinkyNL&lt;/Author&gt;
/// &lt;Description&gt;Task to run Artemis on PC startup without showing a UAC dialog&lt;/Description&gt;
/// &lt;URI&gt;\Artemis autorun&lt;/URI&gt;
/// &lt;/RegistrationInfo&gt;
/// &lt;Triggers&gt;
/// &lt;LogonTrigger&gt;
/// &lt;Enabled&gt;true&lt;/Enabled&gt;
/// &lt;Delay&gt;PT30S&lt;/Delay&gt;
/// &lt;/LogonTrigger&gt;
/// &lt;/Triggers&gt;
/// &lt;Set [rest of string was truncated]&quot;;.
/// </summary>
internal static string Artemis_autorun {
get {
return ResourceManager.GetString("Artemis_autorun", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap. /// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary> /// </summary>
@ -105,6 +128,7 @@ namespace Artemis.Properties {
///{ ///{
/// &quot;uri&quot; &quot;http://localhost:{{port}}/csgo_game_event&quot; /// &quot;uri&quot; &quot;http://localhost:{{port}}/csgo_game_event&quot;
/// &quot;timeout&quot; &quot;0.1&quot; /// &quot;timeout&quot; &quot;0.1&quot;
/// &quot;heartbeat&quot; &quot;0.1&quot;
/// &quot;data&quot; /// &quot;data&quot;
/// { /// {
/// &quot;provider&quot; &quot;1&quot; /// &quot;provider&quot; &quot;1&quot;
@ -437,5 +461,15 @@ namespace Artemis.Properties {
return ((byte[])(obj)); return ((byte[])(obj));
} }
} }
/// <summary>
/// Looks up a localized resource of type System.Byte[].
/// </summary>
internal static byte[] wow_addon {
get {
object obj = ResourceManager.GetObject("wow_addon", resourceCulture);
return ((byte[])(obj));
}
}
} }
} }

View File

@ -223,4 +223,10 @@
<data name="k95_platinum" type="System.Resources.ResXFileRef, System.Windows.Forms"> <data name="k95_platinum" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Keyboards\k95-platinum.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value> <value>..\Resources\Keyboards\k95-platinum.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data> </data>
<data name="wow_addon" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Modules\Games\WoW\Resources\wow-addon.zip;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="Artemis_autorun" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Artemis autorun.xml;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-16</value>
</data>
</root> </root>

Binary file not shown.

View File

@ -15,39 +15,39 @@
-- This event is raised after every profile update, before drawing. -- This event is raised after every profile update, before drawing.
function updateHandler(profile, eventArgs) function updateHandler(profile, eventArgs)
-- Don't do anything when previewing (this means the editor is open) -- Don't do anything when previewing (this means the editor is open)
if eventArgs.Preview then if eventArgs.Preview then
return return
end end
-- In this example we only want to update once per frame when the keyboard is -- In this example we only want to update once per frame when the keyboard is
-- updated. If you don't do this the updateHandler will trigger on every -- updated. If you don't do this the updateHandler will trigger on every
-- device's update. -- device's update.
if eventArgs.DeviceType != "keyboard" then if not (eventArgs.DeviceType == "keyboard") then
return return
end end
-- Custom update code here -- Custom update code here
end end
-- This event is raised after every profile draw, after updating. -- This event is raised after every profile draw, after updating.
function drawHandler(profile, eventArgs) function drawHandler(profile, eventArgs)
-- Don't do anything when previewing (this means the editor is open) -- Don't do anything when previewing (this means the editor is open)
if eventArgs.Preview then if eventArgs.Preview then
return return
end end
-- In this example we only want to draw to the keyboard. Each device has it's -- In this example we only want to draw to the keyboard. Each device has it's
-- own drawing event -- own drawing event
if eventArgs.DeviceType != "keyboard" then if not (eventArgs.DeviceType == "keyboard") then
return return
end end
-- Custom draw code here -- Custom draw code here
end end
-- Register the default events, you can rename/remove these if you so desire. -- Register the default events, you can rename/remove these if you so desire.
-- These events are raised every 40 ms (25 times a second). -- These events are raised every 40 ms (25 times a second).
Events.DeviceUpdating.add(updateHandler); Events.DeviceUpdating.add(updateHandler)
Events.DeviceDrawing.add(drawHandler); Events.DeviceDrawing.add(drawHandler)

View File

@ -4,10 +4,12 @@ using System.IO;
using System.Windows; using System.Windows;
using Artemis.DAL; using Artemis.DAL;
using Artemis.Profiles.Layers.Types.AmbientLight.ScreenCapturing; using Artemis.Profiles.Layers.Types.AmbientLight.ScreenCapturing;
using Artemis.Properties;
using Artemis.Utilities; using Artemis.Utilities;
using Artemis.Utilities.ActiveWindowDetection; using Artemis.Utilities.ActiveWindowDetection;
using Caliburn.Micro; using Caliburn.Micro;
using MahApps.Metro; using MahApps.Metro;
using Microsoft.Win32.TaskScheduler;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Converters; using Newtonsoft.Json.Converters;
using Squirrel; using Squirrel;
@ -111,10 +113,8 @@ namespace Artemis.Settings
{ {
try try
{ {
if (Autorun) // Clean up the shortcut used by the old method
mgr.CreateShortcutsForExecutable("Artemis.exe", ShortcutLocation.Startup, false, "--autorun"); mgr.RemoveShortcutsForExecutable("Artemis.exe", ShortcutLocation.Startup);
else
mgr.RemoveShortcutsForExecutable("Artemis.exe", ShortcutLocation.Startup);
} }
catch (FileNotFoundException) catch (FileNotFoundException)
{ {
@ -125,6 +125,25 @@ namespace Artemis.Settings
// Ignored, only happens when running from VS // Ignored, only happens when running from VS
} }
using (var ts = new TaskService())
{
var existing = ts.FindTask("Artemis autorun");
if (Autorun)
{
// Overwrite any existing tasks in case the installation folder changed
var path = Path.GetTempFileName();
var xml = Resources.Artemis_autorun.Replace("{{executablePath}}", mgr.RootAppDirectory + "\\Update.exe");
File.WriteAllText(path, xml);
ts.RootFolder.ImportTask(null, path);
File.Delete(path);
}
else if (existing != null)
{
// Remove the task if it is present
ts.RootFolder.DeleteTask("Artemis autorun");
}
}
} }
} }
@ -159,4 +178,4 @@ namespace Artemis.Settings
ScreenCaptureManager.UpdateRate = 1.0 / ScreenCaptureFPS; ScreenCaptureManager.UpdateRate = 1.0 / ScreenCaptureFPS;
} }
} }
} }

View File

@ -5,7 +5,6 @@ using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using System.Windows;
using Microsoft.Win32; using Microsoft.Win32;
using Newtonsoft.Json; using Newtonsoft.Json;
using static System.String; using static System.String;
@ -42,10 +41,12 @@ namespace Artemis.Utilities
return GetPropertyValue(value, path.Replace(propertyNames[0] + ".", "")); return GetPropertyValue(value, path.Replace(propertyNames[0] + ".", ""));
} }
public static List<PropertyCollection> GenerateTypeMap(object o) => GenerateTypeMap(o.GetType().GetProperties()); public static List<PropertyCollection> GenerateTypeMap(object o)
{
return GenerateTypeMap(o.GetType().GetProperties());
}
private static List<PropertyCollection> GenerateTypeMap(IEnumerable<PropertyInfo> getProperties, private static List<PropertyCollection> GenerateTypeMap(IEnumerable<PropertyInfo> getProperties, string path = "", bool inList = false)
string path = "")
{ {
var list = new List<PropertyCollection>(); var list = new List<PropertyCollection>();
foreach (var propInfo in getProperties) foreach (var propInfo in getProperties)
@ -62,13 +63,28 @@ namespace Artemis.Utilities
if (propInfo.PropertyType.BaseType?.Name == "Enum") if (propInfo.PropertyType.BaseType?.Name == "Enum")
friendlyName = "(Choice)"; friendlyName = "(Choice)";
var parent = new PropertyCollection // At this point the loop is in the item type contained in the list
PropertyCollection parent;
if (path.Contains("Item") && inList)
{ {
Type = propInfo.PropertyType.Name, parent = new PropertyCollection
DisplayType = friendlyName, {
Display = $"{path.Replace(".", " ")}{propInfo.Name}", Type = propInfo.PropertyType.Name,
Path = $"{path}{propInfo.Name}" DisplayType = friendlyName,
}; Display = $"{path.Replace("Item.", "").Replace(".", " ")}{propInfo.Name}",
Path = $"{path.Replace("Item.", "")}{propInfo.Name}"
};
}
else
{
parent = new PropertyCollection
{
Type = propInfo.PropertyType.Name,
DisplayType = friendlyName,
Display = $"{path.Replace(".", " ")}{propInfo.Name}",
Path = $"{path}{propInfo.Name}"
};
}
if (propInfo.PropertyType.BaseType?.Name == "Enum") if (propInfo.PropertyType.BaseType?.Name == "Enum")
{ {
@ -80,10 +96,19 @@ namespace Artemis.Utilities
list.Add(parent); list.Add(parent);
// Don't go into Strings, DateTimes or anything with JsonIgnore on it // Don't go into Strings, DateTimes or anything with JsonIgnore on it
if (propInfo.PropertyType.Name != "String" && if (propInfo.PropertyType.Name != "String" &&
propInfo.PropertyType.Name != "DateTime" && propInfo.PropertyType.Name != "DateTime" &&
propInfo.CustomAttributes.All(a => a.AttributeType != typeof(JsonIgnoreAttribute))) propInfo.CustomAttributes.All(a => a.AttributeType != typeof(JsonIgnoreAttribute)))
list.AddRange(GenerateTypeMap(propInfo.PropertyType.GetProperties(), path + $"{propInfo.Name}.")); {
var newPath = $"{path}{propInfo.Name}.";
var toInList = propInfo.PropertyType.Name == "List`1";
if (toInList)
{
inList = true;
newPath = $"({path}{propInfo.Name}).";
}
list.AddRange(GenerateTypeMap(propInfo.PropertyType.GetProperties(), newPath, inList));
}
} }
return list; return list;
} }
@ -114,6 +139,21 @@ namespace Artemis.Utilities
return null; return null;
} }
public static void ExecuteSta(Action action)
{
var thread = new Thread(action.Invoke);
thread.SetApartmentState(ApartmentState.STA); //Set the thread to STA
thread.Start();
thread.Join();
}
public static T ParseEnum<T>(string value, bool ignoreCase = true, bool stripWhitespaces = true)
{
if (stripWhitespaces)
value = value.Replace(" ", "");
return (T) Enum.Parse(typeof(T), value, true);
}
public struct PropertyCollection public struct PropertyCollection
{ {
public string Display { get; set; } public string Display { get; set; }
@ -128,13 +168,5 @@ namespace Artemis.Utilities
public List<PropertyCollection> Children { get; set; } public List<PropertyCollection> Children { get; set; }
public string DisplayType { get; set; } public string DisplayType { get; set; }
} }
public static void ExecuteSta(Action action)
{
var thread = new Thread(action.Invoke);
thread.SetApartmentState(ApartmentState.STA); //Set the thread to STA
thread.Start();
thread.Join();
}
} }
} }

View File

@ -1,9 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Artemis.DAL;
using Artemis.Managers;
using Artemis.Modules.Abstract; using Artemis.Modules.Abstract;
using Artemis.Settings;
using Artemis.ViewModels.Abstract; using Artemis.ViewModels.Abstract;
namespace Artemis.ViewModels namespace Artemis.ViewModels
@ -16,18 +13,7 @@ namespace Artemis.ViewModels
{ {
DisplayName = "Games"; DisplayName = "Games";
// Currently WoW is locked behind a hidden trigger (obviously not that hidden since you're reading this) _vms = moduleViewModels.Where(m => m.ModuleModel.IsBoundToProcess).OrderBy(g => g.DisplayName).ToList();
// It is using memory reading and lets first try to contact Blizzard
if (SettingsProvider.Load<GeneralSettings>().GamestatePort == 62575)
{
_vms = moduleViewModels.Where(m => m.ModuleModel.IsBoundToProcess)
.OrderBy(g => g.DisplayName).ToList();
}
else
{
_vms = moduleViewModels.Where(m => m.ModuleModel.IsBoundToProcess && m.DisplayName != "WoW")
.OrderBy(g => g.DisplayName).ToList();
}
} }
protected override void OnActivate() protected override void OnActivate()
@ -37,4 +23,4 @@ namespace Artemis.ViewModels
Items.AddRange(_vms); Items.AddRange(_vms);
} }
} }
} }

View File

@ -26,7 +26,6 @@ using Artemis.Styles.DropTargetAdorners;
using Artemis.Utilities; using Artemis.Utilities;
using Artemis.Utilities.ActiveWindowDetection; using Artemis.Utilities.ActiveWindowDetection;
using Caliburn.Micro; using Caliburn.Micro;
using Castle.Components.DictionaryAdapter;
using GongSolutions.Wpf.DragDrop; using GongSolutions.Wpf.DragDrop;
using MahApps.Metro; using MahApps.Metro;
using MahApps.Metro.Controls; using MahApps.Metro.Controls;
@ -406,8 +405,6 @@ namespace Artemis.ViewModels
ProfileNames.Clear(); ProfileNames.Clear();
if (_moduleModel != null && _deviceManager.ActiveKeyboard != null) if (_moduleModel != null && _deviceManager.ActiveKeyboard != null)
ProfileNames.AddRange(ProfileProvider.GetProfileNames(_deviceManager.ActiveKeyboard, _moduleModel)); ProfileNames.AddRange(ProfileProvider.GetProfileNames(_deviceManager.ActiveKeyboard, _moduleModel));
NotifyOfPropertyChange(() => SelectedProfile);
}); });
} }
@ -444,7 +441,7 @@ namespace Artemis.ViewModels
return; return;
LoadProfiles(); LoadProfiles();
_moduleModel.ChangeProfile(profile); SelectedProfileName = profile.Name;
} }
public async void RenameProfile() public async void RenameProfile()
@ -456,7 +453,8 @@ namespace Artemis.ViewModels
await ProfileEditorModel.RenameProfile(SelectedProfile); await ProfileEditorModel.RenameProfile(SelectedProfile);
LoadProfiles(); LoadProfiles();
_moduleModel.ChangeProfile(renameProfile); SelectedProfileName = "Default";
SelectedProfileName = renameProfile.Name;
} }
public async void DuplicateProfile() public async void DuplicateProfile()
@ -469,7 +467,7 @@ namespace Artemis.ViewModels
return; return;
LoadProfiles(); LoadProfiles();
_moduleModel.ChangeProfile(newProfle); SelectedProfileName = newProfle.Name;
} }
public async void DeleteProfile() public async void DeleteProfile()
@ -477,12 +475,17 @@ namespace Artemis.ViewModels
if (SelectedProfile == null) if (SelectedProfile == null)
return; return;
var confirmed = await ProfileEditorModel.DeleteProfile(SelectedProfile, _moduleModel); var confirmed = await ProfileEditorModel.ConfirmDeleteProfile(SelectedProfile, _moduleModel);
if (!confirmed) if (!confirmed)
return; return;
var deleteProfile = SelectedProfile;
_moduleModel.ChangeProfile(null);
ProfileProvider.DeleteProfile(deleteProfile);
LoadProfiles(); LoadProfiles();
ProfileEditorModel.ChangeProfileByName(_moduleModel, null); SelectedProfileName = "Default";
} }
public async void ImportProfile() public async void ImportProfile()
@ -499,7 +502,7 @@ namespace Artemis.ViewModels
return; return;
LoadProfiles(); LoadProfiles();
_moduleModel.ChangeProfile(importProfile); SelectedProfileName = importProfile.Name;
} }
public void ExportProfile() public void ExportProfile()
@ -633,7 +636,7 @@ namespace Artemis.ViewModels
return SelectedProfile.GetRenderLayers(null, false, true); return SelectedProfile.GetRenderLayers(null, false, true);
if (SelectedLayer == null || !SelectedLayer.Enabled) if (SelectedLayer == null || !SelectedLayer.Enabled)
return new EditableList<LayerModel>(); return new List<LayerModel>();
if (SelectedLayer.LayerType is FolderType) if (SelectedLayer.LayerType is FolderType)
drawLayers = SelectedLayer.GetRenderLayers(null, false, true); drawLayers = SelectedLayer.GetRenderLayers(null, false, true);

View File

@ -1,4 +1,5 @@
using System.ComponentModel; using System.Collections.Generic;
using System.ComponentModel;
using System.Linq; using System.Linq;
using Artemis.Profiles.Layers.Models; using Artemis.Profiles.Layers.Models;
using Artemis.Utilities; using Artemis.Utilities;
@ -41,6 +42,13 @@ namespace Artemis.ViewModels.Profiles
new NamedOperator("Ends with", ".EndsWith") new NamedOperator("Ends with", ".EndsWith")
}; };
private readonly NamedOperator[] _listOperatorsPrefixes =
{
new NamedOperator("Any", "any"),
new NamedOperator("All", "all"),
new NamedOperator("None", "none")
};
private HotKey _hotKey; private HotKey _hotKey;
private bool _keybindIsVisible; private bool _keybindIsVisible;
private GeneralHelpers.PropertyCollection _selectedDataModelProp; private GeneralHelpers.PropertyCollection _selectedDataModelProp;
@ -205,7 +213,6 @@ namespace Artemis.ViewModels.Profiles
{ {
Operators.Clear(); Operators.Clear();
DropdownValues.Clear(); DropdownValues.Clear();
switch (SelectedDataModelProp.Type) switch (SelectedDataModelProp.Type)
{ {
case "Int32": case "Int32":
@ -228,7 +235,16 @@ namespace Artemis.ViewModels.Profiles
UserValueIsVisible = true; UserValueIsVisible = true;
break; break;
} }
// If the selected value is in a list, prefix all choices with list choices
if (SelectedDataModelProp.Path != null && SelectedDataModelProp.Path.Contains("("))
{
var listOperators = new List<NamedOperator>();
foreach (var o in Operators)
listOperators.AddRange(_listOperatorsPrefixes.Select(p => new NamedOperator(p.Display + " " + o.Display.ToLower(), p.Value + "|" + o.Value)));
Operators.Clear();
Operators.AddRange(listOperators);
}
// Add Changed operator is the type is event // Add Changed operator is the type is event
if (_editorViewModel.ProposedLayer.IsEvent) if (_editorViewModel.ProposedLayer.IsEvent)
{ {

View File

@ -3,7 +3,6 @@ using System.Linq;
using Artemis.Profiles.Layers.Models; using Artemis.Profiles.Layers.Models;
using Artemis.Utilities; using Artemis.Utilities;
using Caliburn.Micro; using Caliburn.Micro;
using Castle.Core.Internal;
namespace Artemis.ViewModels.Profiles namespace Artemis.ViewModels.Profiles
{ {
@ -219,7 +218,7 @@ namespace Artemis.ViewModels.Profiles
.DynamicProperties .DynamicProperties
.Where(p => p.LayerProperty != _property).ToList(); .Where(p => p.LayerProperty != _property).ToList();
if (!Proposed.GameProperty.IsNullOrEmpty()) if (!string.IsNullOrEmpty(Proposed.GameProperty))
proposedProperties.DynamicProperties.Add(Proposed); proposedProperties.DynamicProperties.Add(Proposed);
} }
} }

View File

@ -46,6 +46,7 @@
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<!-- Layer name --> <!-- Layer name -->
@ -66,6 +67,14 @@
<Label Grid.Row="0" Grid.Column="4" Content="Is event: " VerticalAlignment="Center" /> <Label Grid.Row="0" Grid.Column="4" Content="Is event: " VerticalAlignment="Center" />
<controls:ToggleSwitchButton Grid.Row="0" Grid.Column="5" VerticalAlignment="Center" IsChecked="{Binding Path=ProposedLayer.IsEvent, Mode=TwoWay}" cal:Message.Attach="[Event Click] = [Action ToggleIsEvent]" <controls:ToggleSwitchButton Grid.Row="0" Grid.Column="5" VerticalAlignment="Center" IsChecked="{Binding Path=ProposedLayer.IsEvent, Mode=TwoWay}" cal:Message.Attach="[Event Click] = [Action ToggleIsEvent]"
Style="{StaticResource MahApps.Metro.Styles.ToggleSwitchButton.Win10}" HorizontalAlignment="Left" Margin="10" /> Style="{StaticResource MahApps.Metro.Styles.ToggleSwitchButton.Win10}" HorizontalAlignment="Left" Margin="10" />
<!-- Fade in -->
<Label Grid.Row="1" Grid.Column="0" Content="Fade in:" VerticalAlignment="Center" />
<controls:NumericUpDown Grid.Row="1" Grid.Column="1" Margin="10" Height="22" Value="{Binding Path=ProposedLayer.FadeInTime}" VerticalAlignment="Center" />
<!-- Fade out -->
<Label Grid.Row="1" Grid.Column="2" Content="Fade out:" VerticalAlignment="Center" />
<controls:NumericUpDown Grid.Row="1" Grid.Column="3" Margin="10" Height="22" Value="{Binding Path=ProposedLayer.FadeOutTime}" VerticalAlignment="Center" />
</Grid> </Grid>
<!-- Advanced --> <!-- Advanced -->

Binary file not shown.

View File

@ -1,14 +1,13 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="Betwixt" version="1.4.1" targetFramework="net461" /> <package id="Betwixt" version="1.4.1" targetFramework="net461" />
<package id="Caliburn.Micro" version="3.0.3" targetFramework="net461" /> <package id="Caliburn.Micro" version="3.1.0" targetFramework="net461" />
<package id="Caliburn.Micro.Core" version="3.0.3" targetFramework="net461" /> <package id="Caliburn.Micro.Core" version="3.1.0" targetFramework="net461" />
<package id="Castle.Core" version="4.0.0" targetFramework="net461" />
<package id="Colore" version="5.1.0" targetFramework="net461" /> <package id="Colore" version="5.1.0" targetFramework="net461" />
<package id="CSCore" version="1.2.0" targetFramework="net461" /> <package id="CSCore" version="1.2.1.2" targetFramework="net461" />
<package id="CUE.NET" version="1.1.3.0" targetFramework="net461" /> <package id="CUE.NET" version="1.1.3.1" targetFramework="net461" />
<package id="DeltaCompressionDotNet" version="1.1.0" targetFramework="net461" /> <package id="DeltaCompressionDotNet" version="1.1.0" targetFramework="net461" />
<package id="DynamicExpresso.Core" version="1.3.3.5" targetFramework="net461" /> <package id="DynamicExpresso.Core" version="1.3.4.7" targetFramework="net461" />
<package id="gong-wpf-dragdrop" version="0.1.4.3" targetFramework="net461" /> <package id="gong-wpf-dragdrop" version="0.1.4.3" targetFramework="net461" />
<package id="Hardcodet.NotifyIcon.Wpf" version="1.0.8" targetFramework="net452" /> <package id="Hardcodet.NotifyIcon.Wpf" version="1.0.8" targetFramework="net452" />
<package id="log4net" version="2.0.8" targetFramework="net461" /> <package id="log4net" version="2.0.8" targetFramework="net461" />
@ -17,18 +16,20 @@
<package id="Mono.Cecil" version="0.9.6.4" targetFramework="net461" /> <package id="Mono.Cecil" version="0.9.6.4" targetFramework="net461" />
<package id="MoonSharp" version="2.0.0.0" targetFramework="net461" /> <package id="MoonSharp" version="2.0.0.0" targetFramework="net461" />
<package id="MouseKeyHook" version="5.4.0" targetFramework="net461" /> <package id="MouseKeyHook" version="5.4.0" targetFramework="net461" />
<package id="Newtonsoft.Json" version="9.0.1" targetFramework="net461" /> <package id="Newtonsoft.Json" version="10.0.3" targetFramework="net461" />
<package id="Ninject" version="3.2.2.0" targetFramework="net452" /> <package id="Ninject" version="3.2.2.0" targetFramework="net452" />
<package id="Ninject.Extensions.Conventions" version="3.2.0.0" targetFramework="net461" /> <package id="Ninject.Extensions.Conventions" version="3.2.0.0" targetFramework="net461" />
<package id="Ninject.Extensions.Logging" version="3.2.3.0" targetFramework="net452" /> <package id="Ninject.Extensions.Logging" version="3.2.3.0" targetFramework="net452" />
<package id="Ninject.Extensions.Logging.nlog4" version="3.2.3.0" targetFramework="net452" /> <package id="Ninject.Extensions.Logging.nlog4" version="3.2.3.0" targetFramework="net452" />
<package id="NLog" version="4.4.4" targetFramework="net461" /> <package id="NLog" version="4.4.12" targetFramework="net461" />
<package id="NLog.Schema" version="4.4.4" targetFramework="net461" /> <package id="NLog.Schema" version="4.4.12" targetFramework="net461" />
<package id="Pcap.Net.x64" version="1.0.4.1" targetFramework="net461" />
<package id="Process.NET" version="1.0.8" targetFramework="net461" /> <package id="Process.NET" version="1.0.8" targetFramework="net461" />
<package id="SharpDX" version="3.1.1" targetFramework="net461" /> <package id="SharpDX" version="4.0.1" targetFramework="net461" />
<package id="SharpDX.Direct3D9" version="3.1.1" targetFramework="net461" /> <package id="SharpDX.Direct3D9" version="4.0.1" targetFramework="net461" />
<package id="Splat" version="2.0.0" targetFramework="net461" /> <package id="Splat" version="2.0.0" targetFramework="net461" />
<package id="SpotifyAPI-NET" version="2.13.1" targetFramework="net461" /> <package id="SpotifyAPI-NET" version="2.16.1" targetFramework="net461" />
<package id="squirrel.windows" version="1.4.4" targetFramework="net461" /> <package id="squirrel.windows" version="1.4.4" targetFramework="net461" />
<package id="TaskScheduler" version="2.6.5" targetFramework="net461" />
<package id="WpfExceptionViewer" version="1.0.0.0" targetFramework="net452" /> <package id="WpfExceptionViewer" version="1.0.0.0" targetFramework="net452" />
</packages> </packages>

Some files were not shown because too many files have changed in this diff Show More