Skip to content

Commit

Permalink
Version 1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
RedHatter committed Nov 30, 2016
1 parent e77db9b commit c860058
Show file tree
Hide file tree
Showing 17 changed files with 846 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.atom-build.yml
85 changes: 85 additions & 0 deletions Graveyard.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
<ProjectGuid>{59c8cca4-7f9b-4592-87a8-a23aded7759e}</ProjectGuid>
<OutputType>Library</OutputType>
<RootNamespace>Graveyard</RootNamespace>
<AssemblyName>Graveyard</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
<Reference Include="System" />
<Reference Include="System.Xaml" />
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="WindowsBase" />
<Reference Include="HearthDb">
<HintPath>lib\HearthDb.dll</HintPath>
</Reference>
<Reference Include="HearthstoneDeckTracker">
<HintPath>lib\HearthstoneDeckTracker.exe</HintPath>
</Reference>
<Reference Include="MahApps.Metro, Version=1.1.2.0, Culture=neutral, PublicKeyToken=f4fb5a3c4d1e5b4f, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>lib\MahApps.Metro.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="src\GraveyardPlugin.cs" />
<Compile Include="src\AnyfinCalculator.cs" />
<Compile Include="src\AnyfinView.cs" />
<Compile Include="src\NormalView.cs" />
<Compile Include="src\ResurrectView.cs" />
<Compile Include="src\NZothView.cs" />
<Compile Include="src\Graveyard.cs" />
<Compile Include="src\SettingsView.xaml.cs">
<DependentUpon>SettingsView.xaml</DependentUpon>
</Compile>
<Page Include="src\SettingsView.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Compile Include="src\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
<DependentUpon>Settings.settings</DependentUpon>
</Compile>
<None Include="src\Settings.settings">
<Generator>PublicSettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="AfterBuild" Condition=" '$(Configuration)' == 'Debug' ">
<Copy SourceFiles="bin/Debug/Graveyard.dll"
DestinationFolder="$(AppData)\HearthstoneDeckTracker\Plugins"/>
</Target>
</Project>
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Graveyard
Graveyard is a plugin for the [Hearthstone Deck Tracker](https://github.com/HearthSim/Hearthstone-Deck-Tracker).

Graveyard displays minions that have died this game. In addition Graveyard will display specialized views for decks containing certain cards.

* **Anyfin Can Happen**
Displays Murlocs killed by both the player and opponent as well as a damage calculator (thanks to [AnyfinCalculator](https://github.com/ericBG/AnyfinCalculator)).

* **N'Zoth the Corruptor**
Displays deathrattle minions.

* **Resurect** or **Onix Bishop**
Displays resurect chance next to each minion that has died.

## Installing
Extract `graveyard.dll` from the archive and move it to `%AppData%\HearthstoneDeckTracker\Plugins`. You will need to enable Graveyard in the Hearthstone Deck Tracker client under `Options > Tracker > Plugins > Graveyard`.
Binary file added lib/HearthDb.dll
Binary file not shown.
Binary file added lib/HearthstoneDeckTracker.exe
Binary file not shown.
Binary file added lib/MahApps.Metro.dll
Binary file not shown.
246 changes: 246 additions & 0 deletions src/AnyfinCalculator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
/**
* Code originally from https://github.com/ericBG/AnyfinCalculator.
* Thank you ericBG for all the hard work!
*
* The MIT License (MIT)
*
* Copyright (c) 2016 ericBG
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
using System.Runtime.CompilerServices;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System;
using HearthDb.Enums;
using Hearthstone_Deck_Tracker;
using Hearthstone_Deck_Tracker.Hearthstone;
using Hearthstone_Deck_Tracker.Hearthstone.Entities;
using Hearthstone_Deck_Tracker.Utility.Logging;

namespace HDT.Plugins.Graveyard
{
public class MurlocInfo
{
public enum State
{
Dead,
OnBoard,
OnOpponentsBoard
}

public State BoardState { get; set; }
public bool CanAttack { get; set; }
public bool IsSilenced { get; set; }
public bool AreBuffsApplied { get; set; }
public int Attack { get; set; }
public Card Murloc { get; set; }
}

public class Range<T> where T : IComparable<T>
{
public T Minimum { get; set; }
public T Maximum { get; set; }
}

public class AnyfinCalculator
{
public static Range<int> CalculateDamageDealt(List<Card> Graveyard)
{
var DeadMurlocs = new List<Card>();
foreach (var card in Graveyard) {
for (var i = 0; i < card.Count; i++) {
var c = card.Clone() as Card;
c.Count = 1;
DeadMurlocs.Add(c);
}
}

if (Core.Game.PlayerMinionCount >= 7) return new Range<int>() {Maximum = 0, Minimum = 0};
if (DeadMurlocs.Count() + Core.Game.PlayerMinionCount <= 7)
{
var damage = CalculateDamageInternal(DeadMurlocs, Core.Game.Player.Board,
Core.Game.Opponent.Board);
return new Range<int> {Maximum = damage, Minimum = damage};
}
var sw = Stopwatch.StartNew();
var board = Core.Game.Player.Board.ToList();
var opponent = Core.Game.Opponent.Board.ToList();
int? min = null, max = null;
foreach (var combination in Combinations(DeadMurlocs, 7 - Core.Game.PlayerMinionCount))
{
var damage = CalculateDamageInternal(combination, board, opponent);
if (damage > max || !max.HasValue) max = damage;
if (damage < min || !min.HasValue) min = damage;
}
sw.Stop();
Log.Debug($"Time to calculate the possibilities: {sw.Elapsed.ToString("ss\\:fff")}");
return new Range<int> {Maximum = max.Value, Minimum = min.Value};
}

private static int CalculateDamageInternal(IEnumerable<Card> graveyard, IEnumerable<Entity> friendlyBoard,
IEnumerable<Entity> opponentBoard)
{
var deadMurlocs = graveyard.ToList();
var aliveMurlocs = friendlyBoard.Where(c => c.Card.IsMurloc()).ToList();
var opponentMurlocs = opponentBoard.Where(c => c.Card.IsMurloc()).ToList();
//compiles together into one big freaking list
var murlocs =
deadMurlocs.Select(
c =>
new MurlocInfo
{
AreBuffsApplied = false,
Attack = c.Attack,
BoardState = MurlocInfo.State.Dead,
CanAttack = c.IsChargeMurloc(),
IsSilenced = false,
Murloc = c
})
.Concat(
aliveMurlocs.Select(
ent =>
new MurlocInfo
{
AreBuffsApplied = true,
Attack = ent.GetTag(GameTag.ATK),
BoardState = MurlocInfo.State.OnBoard,
CanAttack = CanAttack(ent),
IsSilenced = IsSilenced(ent),
Murloc = ent.Card
}))
.Concat(
opponentMurlocs.Select(
ent =>
new MurlocInfo
{
AreBuffsApplied = false,
Attack = ent.Card.Attack,
BoardState = MurlocInfo.State.OnOpponentsBoard,
CanAttack = false,
IsSilenced = IsSilenced(ent),
Murloc = ent.Card
})).ToList();
var nonSilencedWarleaders =
murlocs.Count(m => m.BoardState != MurlocInfo.State.Dead && m.Murloc.IsWarleader() && !m.IsSilenced);
var nonSilencedGrimscales =
murlocs.Count(m => m.BoardState != MurlocInfo.State.Dead && m.Murloc.IsGrimscale() && !m.IsSilenced);
var murlocsToBeSummoned = murlocs.Count(m => m.BoardState == MurlocInfo.State.Dead);
foreach (var murloc in murlocs.Where(t => t.AreBuffsApplied))
{
murloc.AreBuffsApplied = false;
murloc.Attack -= nonSilencedGrimscales + (nonSilencedWarleaders*2);
if (murloc.IsSilenced) continue;
if (murloc.Murloc.IsGrimscale()) murloc.Attack += 1;
if (murloc.Murloc.IsWarleader()) murloc.Attack += 2;
if (murloc.Murloc.IsMurkEye()) murloc.Attack -= (murlocs.Count(m => m.BoardState != MurlocInfo.State.Dead) - 1);
}
nonSilencedWarleaders += murlocs.Count(m => m.BoardState == MurlocInfo.State.Dead && m.Murloc.IsWarleader());
nonSilencedGrimscales += murlocs.Count(m => m.BoardState == MurlocInfo.State.Dead && m.Murloc.IsGrimscale());
foreach (var murloc in murlocs)
{
murloc.AreBuffsApplied = true;
murloc.Attack += nonSilencedGrimscales + (nonSilencedWarleaders*2);
if (murloc.IsSilenced) continue;
if (murloc.Murloc.IsWarleader()) murloc.Attack -= 2;
if (murloc.Murloc.IsGrimscale()) murloc.Attack -= 1;
if (murloc.Murloc.IsMurkEye()) murloc.Attack += (murlocs.Count - 1);
if (murloc.Murloc.IsTidecaller()) murloc.Attack += murlocsToBeSummoned;
}
Log.Debug(murlocs.Aggregate("",
(s, m) =>
s + $"{m.Murloc.Name}{(m.IsSilenced ? " (Silenced)" : "")}: {m.Attack} {(!m.CanAttack ? "(Can't Attack)" : "")}\n"));
return murlocs.Sum(m => m.CanAttack ? m.Attack : 0);
}

private static bool IsSilenced(Entity entity) => entity.GetTag(GameTag.SILENCED) == 1;

private static bool CanAttack(Entity entity)
{
if (entity.GetTag(GameTag.CANT_ATTACK) == 1 || entity.GetTag(GameTag.FROZEN) == 1)
return false;
if (entity.GetTag(GameTag.EXHAUSTED) == 1)
//from reading the HDT source, it seems like internally Charge minions still have summoning sickness
return entity.GetTag(GameTag.CHARGE) == 1 &&
entity.GetTag(GameTag.NUM_ATTACKS_THIS_TURN) < MaxAttacks(entity);
return entity.GetTag(GameTag.NUM_ATTACKS_THIS_TURN) < MaxAttacks(entity);
}

private static int MaxAttacks(Entity entity)
{
// GVG_111t == V-07-TR-0N (MegaWindfury, 4x attack)
if (entity.CardId == "GVG_111t") return 4;
// if it has windfury it can attack twice, else it can only attack once
return entity.GetTag(GameTag.WINDFURY) == 1 ? 2 : 1;
}

public static IEnumerable<IEnumerable<T>> Combinations<T>(IEnumerable<T> elements, int k)
{
return k == 0
? new[] {new T[0]}
: elements.SelectMany((e, i) =>
Combinations(elements.Skip(i + 1), k - 1).Select(c => (new[] {e}).Concat(c)));
}
}

public static class Murlocs
{
static Murlocs()
{
BluegillWarrior = Database.GetCardFromId("CS2_173");
GrimscaleOracle = Database.GetCardFromId("EX1_508");
MurlocWarleader = Database.GetCardFromId("EX1_507");
OldMurkEye = Database.GetCardFromId("EX1_062");
MurlocTidecaller = Database.GetCardFromId("EX1_509");
AnyfinCanHappen = Database.GetCardFromId("LOE_026");
}

public static Card BluegillWarrior { get; }
public static Card GrimscaleOracle { get; }
public static Card MurlocWarleader { get; }
public static Card OldMurkEye { get; }
public static Card MurlocTidecaller { get; }
public static Card AnyfinCanHappen { get; }

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsMurloc(this Card card) => card.Race == "Murloc";

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsChargeMurloc(this Card card) => card.Id == OldMurkEye.Id || card.Id == BluegillWarrior.Id;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsBluegill(this Card card) => card.Id == BluegillWarrior.Id;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsGrimscale(this Card card) => card.Id == GrimscaleOracle.Id;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsWarleader(this Card card) => card.Id == MurlocWarleader.Id;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsMurkEye(this Card card) => card.Id == OldMurkEye.Id;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsTidecaller(this Card card) => card.Id == MurlocTidecaller.Id;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsAnyfin(this Card card) => card.Id == AnyfinCanHappen.Id;
}
}
44 changes: 44 additions & 0 deletions src/AnyfinView.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using System.Linq;
using System.Windows;
using Hearthstone_Deck_Tracker;
using Hearthstone_Deck_Tracker.Hearthstone;

namespace HDT.Plugins.Graveyard
{
public class AnyfinView : NormalView
{
private HearthstoneTextBlock _dmg;

public static bool isValid () {
return Core.Game.Player.PlayerCardList.FindIndex(card => card.Id == "LOE_026") > -1;
}

public AnyfinView () {
// Section Label
Label.Text = "Anyfin Can Happen";

// Damage Label
_dmg = new HearthstoneTextBlock();
_dmg.FontSize = 24;
_dmg.TextAlignment = TextAlignment.Center;
_dmg.Text = "0";
Children.Add(_dmg);
_dmg.Visibility = Visibility.Hidden;
}

new public bool Update (Card card) {
if (card.Race != "Murloc" || !base.Update(card)) return false;
UpdateDamage();
return true;
}

public void UpdateDamage () {
// Update damage counter
Range<int> damage = AnyfinCalculator.CalculateDamageDealt(Cards);

_dmg.Text = damage.Minimum == damage.Maximum ?
damage.Maximum.ToString() : $"{damage.Minimum} - {damage.Maximum}";
_dmg.Visibility = Cards.Count > 0 ? Visibility.Visible : Visibility.Hidden;
}
}
}
Loading

0 comments on commit c860058

Please sign in to comment.