├── .gitignore
├── LICENSE
├── PackHistorian
├── Controls
│ ├── Brushes.xaml
│ ├── Cards.xaml
│ ├── Cards.xaml.cs
│ ├── History.xaml
│ ├── History.xaml.cs
│ ├── HistoryDatePicker.xaml
│ ├── HistoryDatePicker.xaml.cs
│ ├── Log.xaml
│ ├── Log.xaml.cs
│ ├── Menu.xaml
│ ├── Menu.xaml.cs
│ ├── PackDropDown.xaml
│ ├── PackDropDown.xaml.cs
│ ├── PityTimer
│ │ ├── BarChartPrev.xaml
│ │ ├── BarChartPrev.xaml.cs
│ │ ├── BarChartSingle.xaml
│ │ ├── BarChartSingle.xaml.cs
│ │ ├── Label.xaml
│ │ ├── Label.xaml.cs
│ │ ├── PityTimer.xaml
│ │ ├── PityTimer.xaml.cs
│ │ ├── PityTimerOverlay.xaml
│ │ └── PityTimerOverlay.xaml.cs
│ ├── Search.xaml
│ ├── Search.xaml.cs
│ ├── Settings
│ │ ├── Credits.xaml
│ │ ├── Credits.xaml.cs
│ │ ├── General.xaml
│ │ ├── General.xaml.cs
│ │ ├── Settings.xaml
│ │ ├── Settings.xaml.cs
│ │ ├── Update.xaml
│ │ └── Update.xaml.cs
│ ├── Statistic.xaml
│ ├── Statistic.xaml.cs
│ ├── Toast.xaml
│ └── Toast.xaml.cs
├── Entity
│ ├── Card.cs
│ ├── Index.cs
│ └── Pack.cs
├── Event
│ └── PackOpenedEventArgs.cs
├── History.cs
├── ITitledElement.cs
├── IndexRepository.cs
├── PackTracker.csproj
├── PackWatcher.cs
├── Plugin.cs
├── Properties
│ └── AssemblyInfo.cs
├── Resources
│ └── Icons
│ │ ├── 1497613802_feather.ico
│ │ ├── 1498438250_calendar-3.ico
│ │ ├── 1498438378_line-chart_4.ico
│ │ ├── 1498791216_calendar-2.ico
│ │ ├── if__chart_2006795.ico
│ │ ├── if_circle-help-question-mark-glyph_763459.ico
│ │ └── if_search_322497.ico
├── Settings.cs
├── Storage
│ ├── IHistoryStorage.cs
│ ├── ISettingsStorage.cs
│ ├── XmlHistory.cs
│ └── XmlSettings.cs
├── Update
│ ├── Asset.cs
│ ├── Release.cs
│ └── Updater.cs
├── View
│ ├── AbstractValueConverter.cs
│ ├── Average.cs
│ ├── AverageCollection.cs
│ ├── Cache
│ │ └── PityTimerRepository.cs
│ ├── DateConverter.cs
│ ├── DateTimeConverter.cs
│ ├── DecimalConverter.cs
│ ├── PackNameConverter.cs
│ ├── PercentConverter.cs
│ ├── PityTimer.cs
│ ├── Statistic.cs
│ └── TimeConverter.cs
└── WindowManager.cs
├── PackTracker.sln
├── README.md
└── doc
└── Screenshots
├── History.png
├── Log.png
├── PityTimer.png
├── Search.png
└── Statistic.png
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.suo
8 | *.user
9 | *.userosscache
10 | *.sln.docstates
11 |
12 | # User-specific files (MonoDevelop/Xamarin Studio)
13 | *.userprefs
14 |
15 | # Build results
16 | [Dd]ebug/
17 | [Dd]ebugPublic/
18 | [Rr]elease/
19 | [Rr]eleases/
20 | x64/
21 | x86/
22 | bld/
23 | [Bb]in/
24 | [Oo]bj/
25 | [Ll]og/
26 |
27 | # Visual Studio 2015 cache/options directory
28 | .vs/
29 | # Uncomment if you have tasks that create the project's static files in wwwroot
30 | #wwwroot/
31 |
32 | # MSTest test Results
33 | [Tt]est[Rr]esult*/
34 | [Bb]uild[Ll]og.*
35 |
36 | # NUNIT
37 | *.VisualState.xml
38 | TestResult.xml
39 |
40 | # Build Results of an ATL Project
41 | [Dd]ebugPS/
42 | [Rr]eleasePS/
43 | dlldata.c
44 |
45 | # .NET Core
46 | project.lock.json
47 | project.fragment.lock.json
48 | artifacts/
49 | **/Properties/launchSettings.json
50 |
51 | *_i.c
52 | *_p.c
53 | *_i.h
54 | *.ilk
55 | *.meta
56 | *.obj
57 | *.pch
58 | *.pdb
59 | *.pgc
60 | *.pgd
61 | *.rsp
62 | *.sbr
63 | *.tlb
64 | *.tli
65 | *.tlh
66 | *.tmp
67 | *.tmp_proj
68 | *.log
69 | *.vspscc
70 | *.vssscc
71 | .builds
72 | *.pidb
73 | *.svclog
74 | *.scc
75 |
76 | # Chutzpah Test files
77 | _Chutzpah*
78 |
79 | # Visual C++ cache files
80 | ipch/
81 | *.aps
82 | *.ncb
83 | *.opendb
84 | *.opensdf
85 | *.sdf
86 | *.cachefile
87 | *.VC.db
88 | *.VC.VC.opendb
89 |
90 | # Visual Studio profiler
91 | *.psess
92 | *.vsp
93 | *.vspx
94 | *.sap
95 |
96 | # TFS 2012 Local Workspace
97 | $tf/
98 |
99 | # Guidance Automation Toolkit
100 | *.gpState
101 |
102 | # ReSharper is a .NET coding add-in
103 | _ReSharper*/
104 | *.[Rr]e[Ss]harper
105 | *.DotSettings.user
106 |
107 | # JustCode is a .NET coding add-in
108 | .JustCode
109 |
110 | # TeamCity is a build add-in
111 | _TeamCity*
112 |
113 | # DotCover is a Code Coverage Tool
114 | *.dotCover
115 |
116 | # Visual Studio code coverage results
117 | *.coverage
118 | *.coveragexml
119 |
120 | # NCrunch
121 | _NCrunch_*
122 | .*crunch*.local.xml
123 | nCrunchTemp_*
124 |
125 | # MightyMoose
126 | *.mm.*
127 | AutoTest.Net/
128 |
129 | # Web workbench (sass)
130 | .sass-cache/
131 |
132 | # Installshield output folder
133 | [Ee]xpress/
134 |
135 | # DocProject is a documentation generator add-in
136 | DocProject/buildhelp/
137 | DocProject/Help/*.HxT
138 | DocProject/Help/*.HxC
139 | DocProject/Help/*.hhc
140 | DocProject/Help/*.hhk
141 | DocProject/Help/*.hhp
142 | DocProject/Help/Html2
143 | DocProject/Help/html
144 |
145 | # Click-Once directory
146 | publish/
147 |
148 | # Publish Web Output
149 | *.[Pp]ublish.xml
150 | *.azurePubxml
151 | # TODO: Comment the next line if you want to checkin your web deploy settings
152 | # but database connection strings (with potential passwords) will be unencrypted
153 | *.pubxml
154 | *.publishproj
155 |
156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
157 | # checkin your Azure Web App publish settings, but sensitive information contained
158 | # in these scripts will be unencrypted
159 | PublishScripts/
160 |
161 | # NuGet Packages
162 | *.nupkg
163 | # The packages folder can be ignored because of Package Restore
164 | **/packages/*
165 | # except build/, which is used as an MSBuild target.
166 | !**/packages/build/
167 | # Uncomment if necessary however generally it will be regenerated when needed
168 | #!**/packages/repositories.config
169 | # NuGet v3's project.json files produces more ignorable files
170 | *.nuget.props
171 | *.nuget.targets
172 |
173 | # Microsoft Azure Build Output
174 | csx/
175 | *.build.csdef
176 |
177 | # Microsoft Azure Emulator
178 | ecf/
179 | rcf/
180 |
181 | # Windows Store app package directories and files
182 | AppPackages/
183 | BundleArtifacts/
184 | Package.StoreAssociation.xml
185 | _pkginfo.txt
186 | *.appx
187 |
188 | # Visual Studio cache files
189 | # files ending in .cache can be ignored
190 | *.[Cc]ache
191 | # but keep track of directories ending in .cache
192 | !*.[Cc]ache/
193 |
194 | # Others
195 | ClientBin/
196 | ~$*
197 | *~
198 | *.dbmdl
199 | *.dbproj.schemaview
200 | *.jfm
201 | *.pfx
202 | *.publishsettings
203 | orleans.codegen.cs
204 |
205 | # Since there are multiple workflows, uncomment next line to ignore bower_components
206 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
207 | #bower_components/
208 |
209 | # RIA/Silverlight projects
210 | Generated_Code/
211 |
212 | # Backup & report files from converting an old project file
213 | # to a newer Visual Studio version. Backup files are not needed,
214 | # because we have git ;-)
215 | _UpgradeReport_Files/
216 | Backup*/
217 | UpgradeLog*.XML
218 | UpgradeLog*.htm
219 |
220 | # SQL Server files
221 | *.mdf
222 | *.ldf
223 | *.ndf
224 |
225 | # Business Intelligence projects
226 | *.rdl.data
227 | *.bim.layout
228 | *.bim_*.settings
229 |
230 | # Microsoft Fakes
231 | FakesAssemblies/
232 |
233 | # GhostDoc plugin setting file
234 | *.GhostDoc.xml
235 |
236 | # Node.js Tools for Visual Studio
237 | .ntvs_analysis.dat
238 | node_modules/
239 |
240 | # Typescript v1 declaration files
241 | typings/
242 |
243 | # Visual Studio 6 build log
244 | *.plg
245 |
246 | # Visual Studio 6 workspace options file
247 | *.opt
248 |
249 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
250 | *.vbw
251 |
252 | # Visual Studio LightSwitch build output
253 | **/*.HTMLClient/GeneratedArtifacts
254 | **/*.DesktopClient/GeneratedArtifacts
255 | **/*.DesktopClient/ModelManifest.xml
256 | **/*.Server/GeneratedArtifacts
257 | **/*.Server/ModelManifest.xml
258 | _Pvt_Extensions
259 |
260 | # Paket dependency manager
261 | .paket/paket.exe
262 | paket-files/
263 |
264 | # FAKE - F# Make
265 | .fake/
266 |
267 | # JetBrains Rider
268 | .idea/
269 | *.sln.iml
270 |
271 | # CodeRush
272 | .cr/
273 |
274 | # Python Tools for Visual Studio (PTVS)
275 | __pycache__/
276 | *.pyc
277 |
278 | # Cake - Uncomment if you are using it
279 | # tools/**
280 | # !tools/packages.config
281 |
282 | # Telerik's JustMock configuration file
283 | *.jmconfig
284 |
285 | # BizTalk build output
286 | *.btp.cs
287 | *.btm.cs
288 | *.odx.cs
289 | *.xsd.cs
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Marcel Kapma
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/PackHistorian/Controls/Brushes.xaml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/PackHistorian/Controls/Cards.xaml:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/PackHistorian/Controls/Cards.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 | using System.Windows.Controls;
8 | using System.Windows.Data;
9 | using System.Windows.Documents;
10 | using System.Windows.Input;
11 | using System.Windows.Media;
12 | using System.Windows.Media.Imaging;
13 | using System.Windows.Navigation;
14 | using System.Windows.Shapes;
15 |
16 | namespace PackTracker.Controls {
17 | ///
18 | /// Interaktionslogik für PackToast.xaml
19 | ///
20 | public partial class Cards : UserControl {
21 | public Cards() {
22 | InitializeComponent();
23 | }
24 |
25 | public Cards(IEnumerable Cards) {
26 | InitializeComponent();
27 | DataContext = Cards;
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/PackHistorian/Controls/History.xaml:
--------------------------------------------------------------------------------
1 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/PackHistorian/Controls/History.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 | using System.Windows.Controls;
8 | using System.Windows.Data;
9 | using System.Windows.Documents;
10 | using System.Windows.Input;
11 | using System.Windows.Media;
12 | using System.Windows.Media.Imaging;
13 | using System.Windows.Shapes;
14 | using PackTracker.Entity;
15 | using MahApps.Metro.Controls;
16 | using System.Collections.ObjectModel;
17 | using Hearthstone_Deck_Tracker.Hearthstone;
18 | using PackTracker.View;
19 | //using HearthDb;
20 |
21 | namespace PackTracker.Controls {
22 | ///
23 | /// Interaktionslogik für History.xaml
24 | ///
25 | public partial class History {
26 | public History(PackTracker.History History, HistoryDatePicker DatePicker) {
27 | InitializeComponent();
28 | ic_Cards.DataContext = DatePicker;
29 | uc_Date.Content = DatePicker;
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/PackHistorian/Controls/HistoryDatePicker.xaml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/PackHistorian/Controls/HistoryDatePicker.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.ObjectModel;
4 | using System.Collections.Specialized;
5 | using System.ComponentModel;
6 | using System.Linq;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 | using System.Windows;
10 | using System.Windows.Controls;
11 | using System.Windows.Data;
12 | using System.Windows.Documents;
13 | using System.Windows.Input;
14 | using System.Windows.Media;
15 | using System.Windows.Media.Imaging;
16 | using System.Windows.Navigation;
17 | using System.Windows.Shapes;
18 |
19 | namespace PackTracker.Controls {
20 | ///
21 | /// Interaktionslogik für HistoryDatePicker.xaml
22 | ///
23 | public partial class HistoryDatePicker : UserControl, INotifyPropertyChanged {
24 | PackTracker.History _history;
25 | Dictionary> _associatedPacks = new Dictionary>();
26 |
27 | public ObservableCollection AssociatedPack { get
28 | {
29 | DateTime? Selection = dp_DatePicker.SelectedDate;
30 | if(Selection == null) {
31 | return new ObservableCollection();
32 | }
33 |
34 | if(!_associatedPacks.ContainsKey(Selection)) {
35 | _associatedPacks.Add(Selection, new ObservableCollection(_history.Where(x => x.Time.Date == Selection)));
36 | }
37 |
38 | return _associatedPacks[Selection];
39 | }
40 | }
41 |
42 | public HistoryDatePicker(PackTracker.History History) {
43 | InitializeComponent();
44 | _history = History;
45 |
46 | if(History.Count > 0) {
47 | InitializeCalender(History);
48 | } else {
49 | dp_DatePicker.DisplayDateStart = DateTime.Today;
50 | History.CollectionChanged += InitializeCalender;
51 | }
52 |
53 | dp_DatePicker.SelectedDateChanged += (sender, e) => OnPropertyChanged("AssociatedPack");
54 | History.CollectionChanged += (sender, e) => {
55 | if(e.Action == NotifyCollectionChangedAction.Add) {
56 | foreach(var Pack in e.NewItems) {
57 | if(Pack is Entity.Pack) {
58 | Entity.Pack NewPack = (Entity.Pack)Pack;
59 | if(_associatedPacks.ContainsKey(NewPack.Time.Date)) {
60 | _associatedPacks[NewPack.Time.Date].Add(NewPack);
61 | }
62 |
63 | if(dp_DatePicker.SelectedDate != NewPack.Time.Date) {
64 | dp_DatePicker.SelectedDate = NewPack.Time.Date;
65 | }
66 | }
67 | }
68 | }
69 | };
70 |
71 | dp_DatePicker.MouseWheel += (sender, e) => {
72 | if(dp_DatePicker.SelectedDate == null) {
73 | return;
74 | }
75 |
76 | int day = e.Delta < 0 ? -1 : 1;
77 | DateTime Date = (DateTime)dp_DatePicker.SelectedDate;
78 | DateTime First = History.First().Time.Date;
79 | DateTime Last = History.Last().Time.Date;
80 |
81 | do {
82 | Date = Date.AddDays(day);
83 |
84 | if(day == 1) {
85 | if(Date > Last) {
86 | return;
87 | }
88 |
89 | if(Date < First) {
90 | dp_DatePicker.SelectedDate = First;
91 | return;
92 | }
93 | } else {
94 | if(Date < First) {
95 | return;
96 | }
97 |
98 | if(Date > Last) {
99 | dp_DatePicker.SelectedDate = Last;
100 | return;
101 | }
102 | }
103 |
104 | } while(dp_DatePicker.BlackoutDates.Contains(Date));
105 |
106 | dp_DatePicker.SelectedDate = Date;
107 | };
108 | }
109 |
110 | public event PropertyChangedEventHandler PropertyChanged;
111 | private void OnPropertyChanged(string prop) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
112 |
113 | private void InitializeCalender(PackTracker.History History) {
114 | Entity.Pack FirstPack = History.First();
115 | dp_DatePicker.DisplayDateStart = FirstPack.Time;
116 | dp_DatePicker.SelectedDate = History.Last().Time.Date;
117 |
118 | IEnumerable HistoryDates = History.Select(x => x.Time.Date).Distinct();
119 | for(DateTime i = FirstPack.Time.Date; i.Date < DateTime.Today; i = i.AddDays(1)) {
120 | if(!HistoryDates.Contains(i)) {
121 | dp_DatePicker.BlackoutDates.Add(new CalendarDateRange(i));
122 | }
123 | }
124 | }
125 |
126 | private void InitializeCalender(object sender, NotifyCollectionChangedEventArgs e) {
127 | if(e.Action == NotifyCollectionChangedAction.Add && e.NewItems.Count > 0 && sender is PackTracker.History) {
128 | PackTracker.History History = (PackTracker.History)sender;
129 | InitializeCalender(History);
130 | History.CollectionChanged -= InitializeCalender;
131 | }
132 | }
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/PackHistorian/Controls/Log.xaml:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/PackHistorian/Controls/Log.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.Specialized;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using System.Windows;
8 | using System.Windows.Controls;
9 | using System.Windows.Data;
10 | using System.Windows.Documents;
11 | using System.Windows.Input;
12 | using System.Windows.Media;
13 | using System.Windows.Media.Imaging;
14 | using System.Windows.Shapes;
15 | using HearthDb.Enums;
16 | using PackTracker.Entity;
17 | using PackTracker.View;
18 |
19 | namespace PackTracker.Controls {
20 | ///
21 | /// Interaktionslogik für Log.xaml
22 | ///
23 | public partial class Log {
24 | SolidColorBrush Legendary, Epic, Rare;
25 |
26 | public Log(PackTracker.History History) {
27 | InitializeComponent();
28 | Legendary = (SolidColorBrush)FindResource("Legendary");
29 | Epic = (SolidColorBrush)FindResource("Epic");
30 | Rare = (SolidColorBrush)FindResource("Rare");
31 |
32 | Loaded += (sender, e) => AddLogs(History);
33 |
34 | History.CollectionChanged += (sender, e) => {
35 | if(e.Action == NotifyCollectionChangedAction.Add) {
36 | AddLogs(e.NewItems.Cast());
37 | }
38 | };
39 | }
40 |
41 | void AddLogs(IEnumerable Packs) {
42 | StringBuilder sb = new StringBuilder();
43 | DateTimeConverter DateTimeConverter = new DateTimeConverter();
44 | PackNameConverter PackNameConverter = new PackNameConverter();
45 | string sep = ",";
46 |
47 | foreach(Pack Pack in Packs) {
48 | sb.Clear();
49 |
50 | string date = DateTimeConverter.Convert(Pack.Time, null, null, null).ToString();
51 | string packname = PackNameConverter.Convert(Pack.Id, null, null, null).ToString();
52 | int commons = Pack.Cards.Count(x => x.Rarity == Rarity.COMMON);
53 | int commonGolds = commons > 0 ? Pack.Cards.Count(x => x.Premium && x.Rarity == Rarity.COMMON) : 0;
54 | int rares = Pack.Cards.Count(x => x.Rarity == Rarity.RARE);
55 | int rareGolds = rares > 0 ? Pack.Cards.Count(x => x.Premium && x.Rarity == Rarity.RARE) : 0;
56 | int epics = Pack.Cards.Count(x => x.Rarity == Rarity.EPIC);
57 | int epicGolds = epics > 0 ? Pack.Cards.Count(x => x.Premium && x.Rarity == Rarity.EPIC) : 0;
58 | int legendarys = Pack.Cards.Count(x => x.Rarity == Rarity.LEGENDARY);
59 | int legendaryGolds = legendarys > 0 ? Pack.Cards.Count(x => x.Premium && x.Rarity == Rarity.LEGENDARY) : 0;
60 |
61 | SolidColorBrush Color = null as SolidColorBrush;
62 | if(legendarys > 0) {
63 | Color = Legendary;
64 | }
65 | else if(epics > 0) {
66 | Color = Epic;
67 | }
68 | else {
69 | Color = Rare;
70 | }
71 |
72 | sb
73 | .Append(date).Append(": ")
74 | .Append(packname).Append("(")
75 | .Append(commons);
76 | AddGoldStars(commonGolds, Color, sb);
77 |
78 | sb
79 | .Append(sep)
80 | .Append(rares);
81 | AddGoldStars(rareGolds, Color, sb);
82 |
83 | sb
84 | .Append(sep)
85 | .Append(epics);
86 | AddGoldStars(epicGolds, Color, sb);
87 |
88 | sb
89 | .Append(sep)
90 | .Append(legendarys);
91 | AddGoldStars(legendaryGolds, Color, sb);
92 |
93 | sb.AppendLine(")");
94 | txt_Log.Inlines.Add(new Run(sb.ToString()) { Foreground = Color });
95 | }
96 |
97 | sv_Scrollbar.ScrollToEnd();
98 | }
99 |
100 | void AddGoldStars(int amount, SolidColorBrush Color, StringBuilder sb) {
101 | if(amount > 0) {
102 | txt_Log.Inlines.Add(new Run(sb.ToString()) { Foreground = Color });
103 | sb.Clear().Append('*', amount);
104 |
105 | txt_Log.Inlines.Add(new Run(sb.ToString()) { Foreground = Brushes.Gold });
106 | sb.Clear();
107 | }
108 | }
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/PackHistorian/Controls/Menu.xaml:
--------------------------------------------------------------------------------
1 |
49 |
--------------------------------------------------------------------------------
/PackHistorian/Controls/Menu.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 | using System.Windows.Controls;
8 | using System.Windows.Data;
9 | using System.Windows.Documents;
10 | using System.Windows.Input;
11 | using System.Windows.Media;
12 | using System.Windows.Media.Imaging;
13 | using System.Windows.Navigation;
14 | using System.Windows.Shapes;
15 |
16 | namespace PackTracker.Controls {
17 | ///
18 | /// Interaktionslogik für Menu.xaml
19 | ///
20 | public partial class Menu : MenuItem {
21 | public Menu() {
22 | InitializeComponent();
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/PackHistorian/Controls/PackDropDown.xaml:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/PackHistorian/Controls/PackDropDown.xaml.cs:
--------------------------------------------------------------------------------
1 | using MahApps.Metro.Controls;
2 | using PackTracker.Entity;
3 | using System;
4 | using System.CodeDom.Compiler;
5 | using System.Collections;
6 | using System.Collections.Generic;
7 | using System.Collections.ObjectModel;
8 | using System.Collections.Specialized;
9 | using System.ComponentModel;
10 | using System.Diagnostics;
11 | using System.Linq;
12 | using System.Windows;
13 | using System.Windows.Input;
14 | using System.Windows.Markup;
15 |
16 | namespace PackTracker.Controls {
17 | public partial class PackDropDown : SplitButton {
18 | private ObservableCollection _dropDown;
19 |
20 | public PackDropDown() {
21 | InitializeComponent();
22 |
23 | _dropDown = new ObservableCollection();
24 | }
25 |
26 | private void dd_Packs_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e) {
27 | _dropDown.Clear();
28 |
29 | if(e.NewValue is PackTracker.History) {
30 | PackTracker.History History = (PackTracker.History)e.NewValue;
31 | _dropDown = new ObservableCollection(History.Select(x => x.Id).Distinct().OrderBy(x => x));
32 | dd_Packs.ItemsSource = _dropDown;
33 | History.CollectionChanged += DropDown_NewEntry;
34 |
35 |
36 | }
37 | if(e.OldValue is PackTracker.History) {
38 | ((PackTracker.History)e.OldValue).CollectionChanged -= DropDown_NewEntry;
39 | }
40 |
41 | if(_dropDown.Count > 0) {
42 | dd_Packs.SelectedIndex = -1;
43 | if(e.NewValue is PackTracker.History) {
44 | dd_Packs.SelectedItem = ((PackTracker.History)e.NewValue).Last().Id;
45 | } else {
46 | dd_Packs.SelectedIndex = 0;
47 | }
48 | }
49 | }
50 |
51 | private void DropDown_NewEntry(object sender, NotifyCollectionChangedEventArgs e) {
52 | if(e.Action == NotifyCollectionChangedAction.Add) {
53 | foreach(Pack newPack in e.NewItems) {
54 | if(!_dropDown.Contains(newPack.Id)) {
55 | bool isInserted = false;
56 |
57 | foreach(int id in _dropDown) {
58 | if(newPack.Id < id) {
59 | _dropDown.Insert(_dropDown.IndexOf(id), newPack.Id);
60 | isInserted = true;
61 | break;
62 | }
63 | }
64 |
65 | if(!isInserted) {
66 | _dropDown.Add(newPack.Id);
67 | }
68 | }
69 |
70 | dd_Packs.SelectedItem = newPack.Id;
71 | }
72 | }
73 | }
74 |
75 | private void dd_Packs_MouseWheel(object sender, MouseWheelEventArgs e) {
76 | if(e.Delta > 0) {
77 | if(dd_Packs.SelectedIndex > 0) {
78 | dd_Packs.SelectedIndex--;
79 | }
80 | } else {
81 | if(dd_Packs.SelectedIndex < dd_Packs.Items.Count - 1) {
82 | dd_Packs.SelectedIndex++;
83 | }
84 | }
85 | }
86 |
87 | private void dd_Packs_Click(object sender, RoutedEventArgs e) {
88 | dd_Packs.IsExpanded = !dd_Packs.IsExpanded;
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/PackHistorian/Controls/PityTimer/BarChartPrev.xaml:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/PackHistorian/Controls/PityTimer/BarChartPrev.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.Specialized;
4 | using System.ComponentModel;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 | using System.Windows;
9 | using System.Windows.Controls;
10 | using System.Windows.Data;
11 | using System.Windows.Documents;
12 | using System.Windows.Input;
13 | using System.Windows.Media;
14 | using System.Windows.Media.Imaging;
15 | using System.Windows.Navigation;
16 | using System.Windows.Shapes;
17 | using LiveCharts;
18 | using LiveCharts.Configurations;
19 | using LiveCharts.Defaults;
20 | using LiveCharts.Wpf;
21 |
22 | namespace PackTracker.Controls.PityTimer {
23 | ///
24 | /// Interaktionslogik für BarChart.xaml
25 | ///
26 | public partial class BarChartPrev : UserControl, INotifyPropertyChanged {
27 | SeriesCollection _sc;
28 | ColumnSeries _cs;
29 | ObservableValue _currTimer = new ObservableValue(0);
30 | ChartValues _prevTimer = new ChartValues();
31 |
32 | Brush _fillOrig, _fillPrev, _fillCurr;
33 |
34 | public SeriesCollection Prev { get => _sc; }
35 | public Brush Fill { set {
36 | _fillPrev = value.Clone();
37 | _fillPrev.Opacity = .9;
38 | _fillCurr = value.Clone();
39 | _fillCurr.Opacity = .5;
40 | _fillOrig = value;
41 | } }
42 |
43 | public int SoftThreshold { get; set; }
44 | public int Threshold { get; set; }
45 | public int? Average { get => DataContext is View.PityTimer ? ((View.PityTimer)DataContext).Average : null; }
46 | public string XTitle { get; set; }
47 | public string YTitle { get; set; }
48 | public double? MaxValue { get; set; }
49 |
50 | public BarChartPrev() {
51 | InitializeComponent();
52 | Chart.DataContext = this;
53 |
54 | CartesianMapper mapper = Mappers.Xy()
55 | .Fill((x, y) => x == _currTimer ? _fillCurr : _fillPrev)
56 | .Y((obs, y) => obs.Value)
57 | .X((obs, x) => x)
58 | ;
59 | _cs = new ColumnSeries(mapper) {
60 | Values = _prevTimer,
61 | };
62 |
63 | _sc = new SeriesCollection() {
64 | _cs,
65 | };
66 |
67 | DataContextChanged += (sender, e) => {
68 | _prevTimer.Clear();
69 |
70 | if(e.NewValue is View.PityTimer) {
71 | View.PityTimer pt = (View.PityTimer)e.NewValue;
72 | _prevTimer.AddRange(pt.Prev.Select(x => new ObservableValue(x)));
73 | _currTimer = new ObservableValue(pt.Current);
74 | _prevTimer.Add(_currTimer);
75 |
76 | pt.Prev.CollectionChanged += PrevChanged;
77 | pt.PropertyChanged += AverageChanged;
78 | pt.PropertyChanged += CurrentChanged;
79 | }
80 |
81 | if(e.OldValue is View.PityTimer) {
82 | ((View.PityTimer)e.OldValue).Prev.CollectionChanged -= PrevChanged;
83 | ((View.PityTimer)e.OldValue).PropertyChanged -= AverageChanged;
84 | ((View.PityTimer)e.OldValue).PropertyChanged -= CurrentChanged;
85 | }
86 |
87 | OnPropertyChanged("Average");
88 | };
89 | }
90 |
91 | private void CurrentChanged(object sender, PropertyChangedEventArgs e) {
92 | if(e.PropertyName == "Current" && sender is View.PityTimer) {
93 | View.PityTimer pt = (View.PityTimer)sender;
94 | _currTimer.Value = pt.Current;
95 | }
96 | }
97 |
98 | private void AverageChanged(object sender, PropertyChangedEventArgs e) {
99 | if(e.PropertyName == "Average") {
100 | OnPropertyChanged(e.PropertyName);
101 | }
102 | }
103 |
104 | private void PrevChanged(object sender, NotifyCollectionChangedEventArgs e) {
105 | if(DataContext is View.PityTimer) {
106 | View.PityTimer pt = (View.PityTimer)DataContext;
107 | _currTimer = new ObservableValue(pt.Current);
108 | _prevTimer.Add(_currTimer);
109 | }
110 | }
111 |
112 | public event PropertyChangedEventHandler PropertyChanged;
113 | void OnPropertyChanged(string prop) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/PackHistorian/Controls/PityTimer/BarChartSingle.xaml:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/PackHistorian/Controls/PityTimer/BarChartSingle.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 | using System.Windows.Controls;
8 | using System.Windows.Data;
9 | using System.Windows.Documents;
10 | using System.Windows.Input;
11 | using System.Windows.Media;
12 | using System.Windows.Media.Imaging;
13 | using System.Windows.Navigation;
14 | using System.Windows.Shapes;
15 | using LiveCharts.Wpf;
16 | using LiveCharts;
17 | using LiveCharts.Defaults;
18 | using System.ComponentModel;
19 |
20 | namespace PackTracker.Controls.PityTimer {
21 | ///
22 | /// Interaktionslogik für BarChartSingle.xaml
23 | ///
24 | public partial class BarChartSingle : UserControl, INotifyPropertyChanged {
25 | ObservableValue _single = new ObservableValue(0);
26 | ColumnSeries _cs = new ColumnSeries();
27 | SeriesCollection _sc;
28 |
29 | public SeriesCollection Single => _sc;
30 | public string Title { get; set; }
31 | public int Threshold { get; set; }
32 | public int SoftThreshold { get; set; }
33 | public double? MaxValue { get; set; }
34 | public AxisPosition Position { get; set; } = AxisPosition.LeftBottom;
35 | public Brush Fill { set {
36 | _cs.Fill = value.Clone();
37 | _cs.Fill.Opacity = .9;
38 | } }
39 |
40 | public int? Average => DataContext is View.PityTimer ? ((View.PityTimer)DataContext).Average : null;
41 |
42 | public BarChartSingle() {
43 | InitializeComponent();
44 | Chart.DataContext = this;
45 |
46 | ChartValues cv = new ChartValues();
47 | cv.Add(_single);
48 |
49 | _cs.Values = cv;
50 |
51 | _sc = new SeriesCollection() {
52 | _cs,
53 | };
54 |
55 | DataContextChanged += BarChartSingle_DataContextChanged;
56 | }
57 |
58 | private void BarChartSingle_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e) {
59 | if(e.OldValue is View.PityTimer) {
60 | View.PityTimer pt = (View.PityTimer)e.OldValue;
61 | pt.PropertyChanged -= Pt_PropertyChanged;
62 | }
63 |
64 | if(e.NewValue is View.PityTimer) {
65 | View.PityTimer pt = (View.PityTimer)e.NewValue;
66 | _single.Value = pt.Current;
67 | pt.PropertyChanged += Pt_PropertyChanged;
68 | } else {
69 | _single.Value = 0;
70 | }
71 |
72 | OnPropertyChanged("Average");
73 | }
74 |
75 | private void Pt_PropertyChanged(object sender, PropertyChangedEventArgs e) {
76 | switch(e.PropertyName) {
77 | case "Current":
78 | _single.Value = ((View.PityTimer)sender).Current;
79 | break;
80 | case "Average":
81 | OnPropertyChanged(e.PropertyName);
82 | break;
83 | }
84 | }
85 |
86 | public event PropertyChangedEventHandler PropertyChanged;
87 | void OnPropertyChanged(string prop) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/PackHistorian/Controls/PityTimer/Label.xaml:
--------------------------------------------------------------------------------
1 |
17 |
18 | 120
19 | 48
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
40 |
41 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/PackHistorian/Controls/PityTimer/Label.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using System.Windows;
8 | using System.Windows.Controls;
9 | using System.Windows.Data;
10 | using System.Windows.Documents;
11 | using System.Windows.Input;
12 | using System.Windows.Media;
13 | using System.Windows.Media.Imaging;
14 | using System.Windows.Navigation;
15 | using System.Windows.Shapes;
16 | using MahApps.Metro.Controls;
17 |
18 | namespace PackTracker.Controls.PityTimer {
19 | ///
20 | /// Interaktionslogik für Label.xaml
21 | ///
22 | public partial class Label : UserControl, INotifyPropertyChanged {
23 | int _curr = 0;
24 | bool _stillSyncing;
25 | int? _packId = null;
26 |
27 | public int Current {
28 | get { return _curr; }
29 | set {
30 | if(_curr == value) return;
31 | pt_Curr.Transition = value > _curr ? TransitionType.Down : TransitionType.Up;
32 | _curr = value;
33 | OnPropertyChanged("Current");
34 | }
35 | }
36 |
37 | public bool Popup { get; set; } = false;
38 | public string PopupText => GeneratePopupText();
39 | public int Limit { get; set; }
40 | public string RarityPlaceholder { get; set; } = null;
41 |
42 | public Label() {
43 | InitializeComponent();
44 | Counters.DataContext = this;
45 | }
46 |
47 | private void This_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e) {
48 | if(e.OldValue is View.PityTimer) {
49 | View.PityTimer pt = (View.PityTimer)e.OldValue;
50 |
51 | pt.PropertyChanged -= Pt_PropertyChanged;
52 | }
53 |
54 | if(e.NewValue is View.PityTimer) {
55 | View.PityTimer pt = (View.PityTimer)e.NewValue;
56 |
57 | Current = pt.Current;
58 | pt.PropertyChanged += Pt_PropertyChanged;
59 |
60 | _stillSyncing = pt.SkipFirst && pt.WaitForFirst;
61 | _packId = pt.PackId;
62 | } else {
63 | Current = 0;
64 | _packId = null;
65 | }
66 | }
67 |
68 | private void Pt_PropertyChanged(object sender, PropertyChangedEventArgs e) {
69 | if(!(sender is View.PityTimer)) {
70 | return;
71 | }
72 | View.PityTimer pt = (View.PityTimer)sender;
73 |
74 | switch(e.PropertyName) {
75 | case "Current":
76 | Current = pt.Current;
77 | _stillSyncing = pt.SkipFirst && pt.WaitForFirst;
78 | break;
79 | case "Average":
80 | break;
81 | }
82 | }
83 |
84 | private void btn_Popup_Click(object sender, RoutedEventArgs e) {
85 | Popup = true;
86 | OnPropertyChanged("PopupText");
87 | OnPropertyChanged("Popup");
88 | }
89 |
90 | private string GeneratePopupText() {
91 | string rarity = RarityPlaceholder ?? "a card of the respective rarity";
92 | string packName = _packId != null ? View.PackNameConverter.Convert((int)_packId, HearthDb.Enums.Locale.enUS) : "the selected set";
93 |
94 | StringBuilder sb = new StringBuilder();
95 | sb.AppendLine("This is your pity counter.");
96 | if(_stillSyncing) {
97 | sb.Append("You haven't had ").Append(rarity).AppendLine(" in ").Append(packName).Append(" while recording.")
98 | .Append(Plugin.NAME).AppendLine(" waits for your pity timer to reset in order to sync up with it.")
99 | .AppendLine("Just keep opening packs to achieve this.")
100 | .Append("\nSince recording, you have opened ").Append(_curr).Append(_curr != 1 ? " packs of " : " pack of ").Append(packName).Append(" without gaining ").Append(rarity).Append(".")
101 | ;
102 | } else {
103 | sb.Append("Your ").Append(Plugin.NAME).Append(" synced up with ").Append(packName).Append(" already. This was achieved by you, opening ").Append(rarity).AppendLine(".")
104 | .Append("\nSince that, ").Append(_curr).Append(_curr != 1 ? " packs of " : " pack of ").Append(packName).Append(_curr != 1 ? " have" : " has").Append(" been opened without ").Append(rarity).Append(".")
105 | ;
106 | }
107 | sb.AppendLine(" This is indicated by the number above the line.")
108 | .Append("The longest possible streak of ").Append(rarity).Append(" is ").Append(Limit).Append(". ")
109 | .AppendLine("This is indicated by the number below the line.")
110 | ;
111 |
112 | if(_stillSyncing) {
113 | sb.AppendLine("\nSince your Pity Timer is not synced yet, it is most likely that ").Append(packName).Append(" was recently started by you or you just started tracking it.");
114 | }
115 |
116 | sb.AppendLine("\nAll of this, is also indicated by the bar charts on the left.");
117 | if(_stillSyncing) {
118 | sb.Append("Once you have opned a pack with ").Append(rarity).AppendLine(", the bar that you see, will drop back to 0 and start all over again. Because it wasn't synced yet,")
119 | .Append("keeping that streak would falsify the data. Therefor, ").Append(Plugin.NAME).AppendLine(" cleans is up at the reset of your pity timer.")
120 | ;
121 | } else {
122 | sb.Append("They represent all your streaks without ").Append(rarity).AppendLine(".")
123 | .Append("Every time you gain ").Append(rarity).AppendLine(", the right bar becomes static and gets pushed to the left, to make space for a new bar, that count from 0 again.")
124 | ;
125 | }
126 |
127 | sb.Append("The half transparent bar is just another representation of this numeric label. The yellow line indicated the average that Blizzard aims on to gain ").Append(rarity).AppendLine(".")
128 | .Append("The red line is the maximum, that's why it is located excactly on ").Append(Limit).AppendLine(". If a bar passes that line, there is probably something wrong with the data.")
129 | .Append("A dashed line is visable when your pity timer was reseted for the second time and is meant to display your personal average.")
130 | ;
131 |
132 | return sb.ToString();
133 | }
134 |
135 | public event PropertyChangedEventHandler PropertyChanged;
136 | void OnPropertyChanged(string prop) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/PackHistorian/Controls/PityTimer/PityTimer.xaml:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/PackHistorian/Controls/PityTimer/PityTimer.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 | using System.Windows.Controls;
8 | using System.Windows.Data;
9 | using System.Windows.Documents;
10 | using System.Windows.Input;
11 | using System.Windows.Media;
12 | using System.Windows.Media.Imaging;
13 | using System.Windows.Shapes;
14 | using HearthDb.Enums;
15 | using MahApps.Metro.Controls;
16 | using PackTracker.View.Cache;
17 |
18 | namespace PackTracker.Controls.PityTimer {
19 | ///
20 | /// Interaktionslogik für PityTimer.xaml
21 | ///
22 | public partial class PityTimer : MetroWindow {
23 | PityTimerRepository _pityTimers;
24 |
25 | public PityTimer(PackTracker.History History, PityTimerRepository PityTimers) {
26 | InitializeComponent();
27 |
28 | _pityTimers = PityTimers;
29 |
30 | dd_Packs.SelectionChanged += (sender, e) => {
31 | if(e.AddedItems.Count == 1) {
32 | Ep_Prev.DataContext = Ep_Label.DataContext = _pityTimers.GetPityTimer((int)e.AddedItems[0], Rarity.EPIC, false, true);
33 | Leg_Prev.DataContext = Leg_Label.DataContext = _pityTimers.GetPityTimer((int)e.AddedItems[0], Rarity.LEGENDARY, false, true);
34 | }
35 | };
36 |
37 | Loaded += (sender, e) => dd_Packs.DataContext = History;
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/PackHistorian/Controls/PityTimer/PityTimerOverlay.xaml:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/PackHistorian/Controls/PityTimer/PityTimerOverlay.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using System.Windows;
8 | using System.Windows.Controls;
9 | using System.Windows.Data;
10 | using System.Windows.Documents;
11 | using System.Windows.Input;
12 | using System.Windows.Interop;
13 | using System.Windows.Media;
14 | using System.Windows.Media.Imaging;
15 | using System.Windows.Shapes;
16 | using HearthDb.Enums;
17 | using PackTracker.Entity;
18 | using PackTracker.View.Cache;
19 |
20 | namespace PackTracker.Controls.PityTimer {
21 | ///
22 | /// Interaktionslogik für PityTimerOverlay.xaml
23 | ///
24 | public partial class PityTimerOverlay : Window, INotifyPropertyChanged {
25 | int? _packId = null;
26 |
27 | public int? PackId => _packId;
28 |
29 | public event PropertyChangedEventHandler PropertyChanged;
30 | void OnPropertyChanged(string prop) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
31 |
32 | public PityTimerOverlay(PackTracker.History History, PityTimerRepository PityTimers) {
33 | InitializeComponent();
34 | DataContext = this;
35 |
36 | if(History.Count > 0) {
37 | _packId = History.Last().Id;
38 | Chart_Epic.DataContext = PityTimers.GetPityTimer((int)_packId, Rarity.EPIC, false, true);
39 | Chart_Leg.DataContext = PityTimers.GetPityTimer((int)_packId, Rarity.LEGENDARY, false, true);
40 | }
41 |
42 | History.CollectionChanged += (sender, e) => {
43 | foreach(Pack Pack in e.NewItems) {
44 | Chart_Epic.DataContext = PityTimers.GetPityTimer(Pack.Id, Rarity.EPIC, false, true);
45 | Chart_Leg.DataContext = PityTimers.GetPityTimer(Pack.Id, Rarity.LEGENDARY, false, true);
46 | _packId = Pack.Id;
47 | }
48 |
49 | OnPropertyChanged("PackId");
50 | };
51 | }
52 |
53 | private void UniformGrid_Loaded(object sender, RoutedEventArgs e) {
54 | SetPosition();
55 | }
56 |
57 | private void SetPosition() {
58 | double relativeRight = .01;
59 | double relativeBottom = .35;
60 |
61 | Left = SystemParameters.PrimaryScreenWidth - (SystemParameters.PrimaryScreenWidth * relativeRight) - Width;
62 | Top = SystemParameters.PrimaryScreenHeight - (SystemParameters.PrimaryScreenHeight * relativeBottom) - Height;
63 | }
64 |
65 | private void Window_SourceInitialized(object sender, EventArgs e) {
66 | WindowInteropHelper helper = new WindowInteropHelper(this);
67 | Hearthstone_Deck_Tracker.User32.SetWindowExStyle(helper.Handle, Hearthstone_Deck_Tracker.User32.WsExTransparent | Hearthstone_Deck_Tracker.User32.WsExToolWindow);
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/PackHistorian/Controls/Search.xaml:
--------------------------------------------------------------------------------
1 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/PackHistorian/Controls/Search.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 | using System.Windows.Controls;
8 | using System.Windows.Data;
9 | using System.Windows.Documents;
10 | using System.Windows.Input;
11 | using System.Windows.Media;
12 | using System.Windows.Media.Imaging;
13 | using System.Windows.Shapes;
14 |
15 | namespace PackTracker.Controls {
16 | ///
17 | /// Interaktionslogik für Search.xaml
18 | ///
19 | public partial class Search {
20 | IndexRepository _index;
21 |
22 | public Search(PackTracker.History History) {
23 | InitializeComponent();
24 |
25 | _index = new IndexRepository(History);
26 | txt_Search.Focus();
27 | }
28 |
29 | private void txt_Search_KeyDown(object sender, KeyEventArgs e) {
30 | if(e.Key == Key.Return) {
31 | dg_Result.ItemsSource = _index.Find(((TextBox)sender).Text);
32 | txt_Search.SelectAll();
33 | }
34 | }
35 |
36 | private void btn_Search_Click(object sender, RoutedEventArgs e) {
37 | dg_Result.ItemsSource = _index.Find(txt_Search.Text);
38 | txt_Search.Focus();
39 | txt_Search.SelectAll();
40 | }
41 |
42 | private void MetroWindow_KeyDown(object sender, KeyEventArgs e) {
43 | if(e.Key == Key.Escape) {
44 | Close();
45 | }
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/PackHistorian/Controls/Settings/Credits.xaml:
--------------------------------------------------------------------------------
1 |
12 |
13 | Kozuka Gothic Pro R
14 |
15 |
16 |
17 | 18
18 |
19 |
20 |
21 |
22 |
23 | Creator:
24 | DBqFetti <dbqfetti@gmail.com>
25 |
26 |
27 |
28 |
29 |
30 | Beta Testers:
31 | Antony Alvarado
32 |
33 |
34 |
35 |
36 |
37 | Translations:
38 | (english) Antony Alvarado
39 |
40 |
41 |
42 |
43 |
44 | Icons:
45 | Icons Solid <www.iconsolid.com>
46 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/PackHistorian/Controls/Settings/Credits.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 | using System.Windows.Controls;
8 | using System.Windows.Data;
9 | using System.Windows.Documents;
10 | using System.Windows.Input;
11 | using System.Windows.Media;
12 | using System.Windows.Media.Imaging;
13 | using System.Windows.Navigation;
14 | using System.Windows.Shapes;
15 | using MahApps.Metro.Controls;
16 |
17 | namespace PackTracker.Controls.Settings {
18 | ///
19 | /// Interaktionslogik für Credits.xaml
20 | ///
21 | public partial class Credits : MetroContentControl, ITitledElement {
22 | public string Title => "Credits";
23 |
24 | public Credits() {
25 | InitializeComponent();
26 | }
27 |
28 | private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e) {
29 | System.Diagnostics.Process.Start(e.Uri.ToString());
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/PackHistorian/Controls/Settings/General.xaml:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/PackHistorian/Controls/Settings/General.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 | using System.Windows.Controls;
8 | using System.Windows.Data;
9 | using System.Windows.Documents;
10 | using System.Windows.Input;
11 | using System.Windows.Media;
12 | using System.Windows.Media.Imaging;
13 | using System.Windows.Navigation;
14 | using System.Windows.Shapes;
15 | using MahApps.Metro.Controls;
16 |
17 | namespace PackTracker.Controls.Settings {
18 | ///
19 | /// Interaktionslogik für General.xaml
20 | ///
21 | public partial class General : MetroContentControl, ITitledElement {
22 | public string Title => "General";
23 |
24 | public General(PackTracker.Settings Settings) {
25 | InitializeComponent();
26 | DataContext = Settings;
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/PackHistorian/Controls/Settings/Settings.xaml:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/PackHistorian/Controls/Settings/Settings.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 | using System.Windows.Controls;
8 | using System.Windows.Data;
9 | using System.Windows.Documents;
10 | using System.Windows.Input;
11 | using System.Windows.Media;
12 | using System.Windows.Media.Animation;
13 | using System.Windows.Media.Imaging;
14 | using System.Windows.Shapes;
15 | using PackTracker.Update;
16 |
17 | namespace PackTracker.Controls.Settings {
18 | ///
19 | /// Interaktionslogik für Settings.xaml
20 | ///
21 | public partial class Settings {
22 | PackTracker.Settings _settings;
23 | bool allowClosing = false;
24 |
25 | public Settings(PackTracker.Settings Settings) {
26 | InitializeComponent();
27 | lb_tabs.ItemsSource = new List() {
28 | new General(Settings),
29 | new Update(Settings, new Updater()),
30 | new Credits(),
31 | };
32 | lb_tabs.SelectedIndex = 0;
33 |
34 | _settings = Settings;
35 | AnimateSizeToContentStart();
36 | }
37 |
38 | void AnimateSizeToContentStart() {
39 | SizeChanged += ChangeSize;
40 | SizeToContent = SizeToContent.Width;
41 | }
42 |
43 | void AnimateSizeToContentStop() {
44 | SizeChanged -= ChangeSize;
45 | SizeToContent = SizeToContent.Manual;
46 | }
47 |
48 | void ChangeSize(object sender, SizeChangedEventArgs e) {
49 | DoubleAnimation at = new DoubleAnimation(e.PreviousSize.Width, e.NewSize.Width, new Duration(new TimeSpan(6000000)));
50 | AnimateSizeToContentStop();
51 | at.EasingFunction = new ExponentialEase() { EasingMode = EasingMode.EaseInOut };
52 | at.Completed += (sender2, e2) => {
53 | AnimateSizeToContentStart();
54 | };
55 |
56 | BeginAnimation(WidthProperty, at);
57 | }
58 |
59 | private void MetroWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e) {
60 | if(allowClosing) {
61 | return;
62 | } else {
63 | e.Cancel = true;
64 | }
65 |
66 | AnimateSizeToContentStop();
67 | Duration Duration = new Duration(TimeSpan.FromSeconds(.4));
68 | IEasingFunction Easing = new ExponentialEase() { EasingMode = EasingMode.EaseInOut };
69 |
70 | DoubleAnimation Width = new DoubleAnimation(ActualWidth, 2, Duration) { EasingFunction = Easing };
71 | Width.Completed += (sender2, e2) => {
72 | DoubleAnimation Height = new DoubleAnimation(ActualHeight, 0, Duration) { EasingFunction = Easing };
73 | Height.Completed += (sender3, e3) => {
74 | allowClosing = true;
75 | Close();
76 | };
77 |
78 | BeginAnimation(HeightProperty, Height);
79 | };
80 |
81 | BeginAnimation(WidthProperty, Width);
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/PackHistorian/Controls/Settings/Update.xaml:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/PackHistorian/Controls/Settings/Update.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Timers;
7 | using System.Threading.Tasks;
8 | using System.Windows;
9 | using System.Windows.Controls;
10 | using System.Windows.Data;
11 | using System.Windows.Documents;
12 | using System.Windows.Input;
13 | using System.Windows.Media;
14 | using System.Windows.Media.Imaging;
15 | using System.Windows.Navigation;
16 | using System.Windows.Shapes;
17 | using PackTracker.Update;
18 | using MahApps.Metro.Controls;
19 |
20 | namespace PackTracker.Controls.Settings {
21 | ///
22 | /// Interaktionslogik für Update.xaml
23 | ///
24 | public partial class Update : MetroContentControl, ITitledElement {
25 | Updater _updater;
26 | Timer _timer;
27 |
28 | public string Title => "Update";
29 |
30 | public Update(PackTracker.Settings Settings, Updater Updater) {
31 | InitializeComponent();
32 | DataContext = Settings;
33 |
34 | _updater = Updater;
35 | _timer = new Timer((new TimeSpan(0, 0, 10)).TotalMilliseconds) { AutoReset = false };
36 | _timer.Elapsed += (sender, e) => { Dispatcher.Invoke(() => btn_Refresh.IsEnabled = true); };
37 |
38 | Loaded += Update_Loaded;
39 | }
40 |
41 | private void Update_Loaded(object sender, RoutedEventArgs e) {
42 | Loaded -= Update_Loaded;
43 | Refresh();
44 | }
45 |
46 | void Refresh() {
47 | btn_Refresh.IsEnabled = false;
48 | btn_Update.IsEnabled = false;
49 | pb_Bar.Visibility = Visibility.Visible;
50 |
51 | txt_ChangeLog.Inlines.Clear();
52 | BackgroundWorker bw = new BackgroundWorker();
53 |
54 | bw.DoWork += (sender, e) => {
55 | e.Result = _updater.GetAllReleases();
56 | };
57 |
58 | bw.RunWorkerCompleted += (sender, e) => {
59 | if(e.Result is IEnumerable) {
60 | IEnumerable Result = (IEnumerable)e.Result;
61 | InsertInlines(Result, txt_ChangeLog.Inlines);
62 | btn_Update.IsEnabled = Result.Any(x => Updater.ParseVersion(x.tag_name) > Plugin.CurrentVersion);
63 | } else {
64 | MessageBox.Show("Request failed", "Update", MessageBoxButton.OK, MessageBoxImage.Error);
65 | }
66 |
67 | pb_Bar.Visibility = Visibility.Hidden;
68 | _timer.Start();
69 | };
70 |
71 | bw.RunWorkerAsync();
72 | }
73 |
74 | void InsertInlines(IEnumerable Releases, InlineCollection Target) {
75 | foreach(Release Release in Releases) {
76 | Run Headline = new Run(Release.tag_name + (Release.name != Release.tag_name ? (" \"" + Release.name + "\"") : "")) {
77 | FontStyle = FontStyles.Oblique,
78 | FontWeight = FontWeights.Bold,
79 | Foreground = Brushes.CornflowerBlue,
80 | TextDecorations = TextDecorations.Underline,
81 | FontSize = 18,
82 | };
83 | Target.Add(Headline);
84 |
85 | if(Plugin.CurrentVersion == Updater.ParseVersion(Release.tag_name)) {
86 | Run Installed = new Run(" (installed)\n") {
87 | FontStyle = FontStyles.Oblique,
88 | Foreground = Brushes.White,
89 | FontSize = 10,
90 | };
91 | Target.Add(Installed);
92 | } else {
93 | Headline.Text += "\n";
94 | }
95 |
96 | Run Body = new Run(Release.body + "\n\n") {
97 | Foreground = Brushes.White,
98 | };
99 | Target.Add(Body);
100 | }
101 | }
102 |
103 | private void btn_Refresh_Click(object sender, RoutedEventArgs e) {
104 | Refresh();
105 | }
106 |
107 | private void btn_Update_Click(object sender, RoutedEventArgs e) {
108 | BackgroundWorker bw = new BackgroundWorker();
109 | bw.DoWork += (bwsender, bwe) => {
110 | bwe.Result = _updater.Update();
111 | };
112 | bw.RunWorkerCompleted += (bwsender, bwe) => {
113 | pb_Bar.Visibility = Visibility.Hidden;
114 |
115 | if((bool)bwe.Result) {
116 | MessageBox.Show("Update completed\nPlease restart Hearthstone Deck Tracker", "Pack Tracker: Update");
117 | } else {
118 | btn_Refresh.IsEnabled = true;
119 | btn_Refresh.IsEnabled = true;
120 | MessageBox.Show("Update failed\nPlease try again later or download on Github", "Pack Tracker: Update");
121 | }
122 | };
123 |
124 | btn_Refresh.IsEnabled = false;
125 | btn_Update.IsEnabled = false;
126 | pb_Bar.Visibility = Visibility.Visible;
127 | _timer.Stop();
128 | bw.RunWorkerAsync();
129 | }
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/PackHistorian/Controls/Statistic.xaml:
--------------------------------------------------------------------------------
1 |
18 |
19 |
20 |
21 |
22 |
23 | 16
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
--------------------------------------------------------------------------------
/PackHistorian/Controls/Statistic.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.ObjectModel;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using System.Windows;
8 | using System.Windows.Controls;
9 | using System.Windows.Data;
10 | using System.Windows.Documents;
11 | using System.Windows.Input;
12 | using System.Windows.Media;
13 | using System.Windows.Media.Imaging;
14 | using System.Windows.Shapes;
15 | using System.Collections.Specialized;
16 | using PackTracker.Entity;
17 |
18 | namespace PackTracker.Controls {
19 | ///
20 | /// Interaktionslogik für Statistic.xaml
21 | ///
22 | public partial class Statistic {
23 | public Statistic(PackTracker.History History) {
24 | InitializeComponent();
25 |
26 | Dictionary _statistics = new Dictionary();
27 |
28 | dd_Packs.SelectionChanged += (sender, e) => {
29 | if(e.AddedItems.Count == 1) {
30 | int selection = (int)e.AddedItems[0];
31 | if(!_statistics.ContainsKey(selection)) {
32 | _statistics.Add(selection, new View.Statistic(selection, History));
33 | }
34 |
35 | dp_Statistic.DataContext = _statistics[selection];
36 | } else {
37 | dp_Statistic.DataContext = null;
38 | }
39 | };
40 |
41 | Loaded += (sender, e) => dd_Packs.DataContext = History;
42 | dd_Packs.Focus();
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/PackHistorian/Controls/Toast.xaml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
37 |
38 |
39 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/PackHistorian/Controls/Toast.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 | using System.Windows.Controls;
8 | using System.Windows.Data;
9 | using System.Windows.Documents;
10 | using System.Windows.Input;
11 | using System.Windows.Media;
12 | using System.Windows.Media.Imaging;
13 | using System.Windows.Navigation;
14 | using System.Windows.Shapes;
15 | using PackTracker.Entity;
16 |
17 | namespace PackTracker.Controls {
18 | ///
19 | /// Interaktionslogik für Toast.xaml
20 | ///
21 | public partial class Toast : UserControl {
22 | public Toast(Pack Pack, View.Average Average) {
23 | InitializeComponent();
24 | ctr_Cards.DataContext = Pack.Cards;
25 | ctr_Average.DataContext = Average;
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/PackHistorian/Entity/Card.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using HearthDb.Enums;
7 | using HDTCard = Hearthstone_Deck_Tracker.Hearthstone.Card;
8 |
9 | namespace PackTracker.Entity {
10 | public class Card {
11 | HDTCard _card;
12 | bool _premium;
13 |
14 | public HDTCard HDTCard { get { return _card; } }
15 | public bool Premium { get { return _premium; } }
16 | public Rarity Rarity { get { return _card.Rarity; } }
17 |
18 | public Card(HDTCard Card, bool premium) {
19 | _card = Card;
20 | _premium = premium;
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/PackHistorian/Entity/Index.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace PackTracker.Entity {
8 | class Index {
9 | Card _card;
10 | DateTime _datetime;
11 |
12 | public Card Card { get => _card; }
13 | public DateTime DateTime { get => _datetime; }
14 |
15 | public Index(Card Card, DateTime DateTime) {
16 | _card = Card;
17 | _datetime = DateTime;
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/PackHistorian/Entity/Pack.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace PackTracker.Entity {
8 | public class Pack {
9 | int _id;
10 | DateTime _time;
11 | IEnumerable _cards;
12 |
13 | public int Id { get { return _id; } }
14 | public DateTime Time { get { return _time; } }
15 | public IEnumerable Cards { get { return _cards; } }
16 |
17 | public Pack(int id, DateTime Time, IEnumerable Cards) {
18 | _id = id;
19 | _time = Time;
20 | _cards = Cards;
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/PackHistorian/Event/PackOpenedEventArgs.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using PackTracker.Entity;
7 |
8 | namespace PackTracker.Event {
9 | class PackOpenedEventArgs {
10 | Pack _pack;
11 |
12 | public Pack Pack { get { return _pack; } }
13 |
14 | public PackOpenedEventArgs(Pack Pack) {
15 | _pack = Pack;
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/PackHistorian/History.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.Collections.ObjectModel;
5 | using System.Collections.Specialized;
6 | using System.Linq;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 | using PackTracker.Entity;
10 |
11 | namespace PackTracker {
12 | public class History : IEnumerable, INotifyCollectionChanged {
13 | ObservableCollection _packs;
14 |
15 | public event NotifyCollectionChangedEventHandler CollectionChanged;
16 |
17 | public int Count { get { return _packs.Count; } }
18 |
19 | public History() {
20 | _packs = new ObservableCollection();
21 | Initialize();
22 | }
23 |
24 | public History(IEnumerable Packs) {
25 | _packs = new ObservableCollection(Packs);
26 | Initialize();
27 | }
28 |
29 | private void Initialize() {
30 | _packs.CollectionChanged += (sender, e) => { OnCollectionChanged(e); };
31 | }
32 |
33 | public History Ascending { get { return new History(_packs.OrderBy(x => x.Time)); } }
34 |
35 | public void Add(Pack Pack) {
36 | _packs.Add(Pack);
37 | }
38 |
39 | public Pack First() {
40 | Pack First = null as Pack;
41 | foreach(Pack Pack in _packs) {
42 | if(First == null || Pack.Time.Ticks < First.Time.Ticks) {
43 | First = Pack;
44 | }
45 | }
46 |
47 | return First;
48 | }
49 |
50 | public Pack Last() {
51 | Pack Last = null as Pack;
52 | foreach(Pack Pack in _packs) {
53 | if(Last == null || Pack.Time.Ticks > Last.Time.Ticks) {
54 | Last = Pack;
55 | }
56 | }
57 |
58 | return Last;
59 | }
60 |
61 | public IEnumerator GetEnumerator() {
62 | return _packs.GetEnumerator();
63 | }
64 |
65 | IEnumerator IEnumerable.GetEnumerator() {
66 | return _packs.GetEnumerator();
67 | }
68 |
69 | private void OnCollectionChanged(NotifyCollectionChangedEventArgs Args) {
70 | CollectionChanged?.Invoke(this, Args);
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/PackHistorian/ITitledElement.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace PackTracker {
8 | interface ITitledElement {
9 | string Title { get; }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/PackHistorian/IndexRepository.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.Specialized;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using HearthDb.Enums;
8 | using Hearthstone_Deck_Tracker;
9 | using Hearthstone_Deck_Tracker.Hearthstone;
10 | using PackTracker.Entity;
11 |
12 | namespace PackTracker {
13 | class IndexRepository {
14 | readonly StringBuilder _sb = new StringBuilder();
15 | Dictionary _index = new Dictionary(); //
16 | Dictionary> _indexObjects = new Dictionary>(); //
17 |
18 | public IndexRepository(History History) {
19 | foreach(Pack Pack in History) {
20 | foreach(Entity.Card Card in Pack.Cards) {
21 | Add(new Index(Card, Pack.Time));
22 | }
23 | }
24 |
25 | History.CollectionChanged += (sender, e) => {
26 | if(e.Action == NotifyCollectionChangedAction.Add) {
27 | foreach(Pack Pack in e.NewItems) {
28 | foreach(Entity.Card Card in Pack.Cards) {
29 | Add(new Index(Card, Pack.Time));
30 | }
31 | }
32 | }
33 | };
34 | }
35 |
36 | void Add(Index Index) {
37 | if(!_index.ContainsValue(Index.Card.HDTCard.Id)) {
38 | HearthDb.Card DbCard = HearthDb.Cards.GetFromDbfId(Index.Card.HDTCard.DbfIf);
39 |
40 | string name = "";
41 | string text = "";
42 | name = DbCard.Name?.ToLower();
43 | text = DbCard.Text?.ToLower();
44 |
45 | string locName = "";
46 | string locText = "";
47 |
48 | if(Enum.TryParse(Config.Instance.SelectedLanguage, out Locale lang)) {
49 | locName = DbCard.GetLocName(lang)?.ToLower();
50 | locText = DbCard.GetLocText(lang)?.ToLower();
51 | }
52 |
53 | _sb.Append(locName).Append(name).Append(locText).Append(text);
54 | _index.Add(_sb.ToString(), Index.Card.HDTCard.Id);
55 | _sb.Clear();
56 |
57 | _indexObjects.Add(Index.Card.HDTCard.Id, new List());
58 | }
59 |
60 | _indexObjects[Index.Card.HDTCard.Id].Add(Index);
61 | }
62 |
63 | public IEnumerable Find(string searchString) {
64 | string[] elems = searchString.Split(' ');
65 | IEnumerable> Filtered = _index.Where(x => x.Key.Contains(elems[0]));
66 |
67 | foreach(string elem in elems.Skip(1)) {
68 | Filtered = Filtered.Where(x => x.Key.Contains(elem));
69 | }
70 |
71 | List Result = new List();
72 | foreach(List Index in _indexObjects.Where(x => Filtered.Select(y => y.Value).Contains(x.Key)).Select(x => x.Value)) {
73 | Result.AddRange(Index);
74 | }
75 |
76 | return Result.Distinct();
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/PackHistorian/PackTracker.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {3C5023C1-D9B6-4FFA-B3A2-2A8466DE5074}
8 | {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
9 | Library
10 | Properties
11 | PackTracker
12 | PackTracker
13 | v4.7.2
14 | 512
15 |
16 |
17 |
18 | true
19 | full
20 | false
21 | bin\Debug\
22 | DEBUG;TRACE
23 | prompt
24 | 4
25 |
26 |
27 | pdbonly
28 | true
29 | bin\Release\
30 | TRACE
31 | prompt
32 | 4
33 |
34 |
35 | true
36 | bin\x86\Debug\
37 | DEBUG;TRACE
38 | full
39 | x86
40 | prompt
41 | MinimumRecommendedRules.ruleset
42 |
43 |
44 | bin\x86\Release\
45 | TRACE
46 | true
47 | pdbonly
48 | x86
49 | prompt
50 | MinimumRecommendedRules.ruleset
51 |
52 |
53 |
54 | False
55 | C:\Users\Fetti\AppData\Local\HearthstoneDeckTracker\app-1.11.1\HearthDb.dll
56 |
57 |
58 | False
59 | C:\Users\Fetti\AppData\Local\HearthstoneDeckTracker\app-1.14.4\HearthMirror.dll
60 |
61 |
62 | False
63 | C:\Users\Fetti\AppData\Local\HearthstoneDeckTracker\app-1.11.1\HearthstoneDeckTracker.exe
64 |
65 |
66 | False
67 | C:\Users\Fetti\AppData\Local\HearthstoneDeckTracker\app-1.11.1\HearthWatcher.dll
68 |
69 |
70 | False
71 | C:\Users\Fetti\AppData\Local\HearthstoneDeckTracker\app-1.11.1\LiveCharts.dll
72 |
73 |
74 | False
75 | C:\Users\Fetti\AppData\Local\HearthstoneDeckTracker\app-1.11.1\LiveCharts.Wpf.dll
76 |
77 |
78 | False
79 | C:\Users\Fetti\AppData\Local\HearthstoneDeckTracker\app-1.11.1\MahApps.Metro.dll
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 | False
98 | C:\Users\Fetti\AppData\Local\HearthstoneDeckTracker\app-1.11.1\XAMLMarkupExtensions.dll
99 |
100 |
101 |
102 |
103 | BarChartSingle.xaml
104 |
105 |
106 | PityTimerOverlay.xaml
107 |
108 |
109 |
110 | Cards.xaml
111 |
112 |
113 | HistoryDatePicker.xaml
114 |
115 |
116 | Log.xaml
117 |
118 |
119 | Menu.xaml
120 |
121 |
122 | PackDropDown.xaml
123 |
124 |
125 | BarChartPrev.xaml
126 |
127 |
128 | Label.xaml
129 |
130 |
131 | PityTimer.xaml
132 |
133 |
134 | Search.xaml
135 |
136 |
137 | Credits.xaml
138 |
139 |
140 | General.xaml
141 |
142 |
143 | Settings.xaml
144 |
145 |
146 | Update.xaml
147 |
148 |
149 | Statistic.xaml
150 |
151 |
152 | Toast.xaml
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 | History.xaml
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 | Designer
191 | MSBuild:Compile
192 |
193 |
194 | Designer
195 | MSBuild:Compile
196 |
197 |
198 | Designer
199 | MSBuild:Compile
200 |
201 |
202 | Designer
203 | MSBuild:Compile
204 |
205 |
206 | Designer
207 | MSBuild:Compile
208 |
209 |
210 | Designer
211 | MSBuild:Compile
212 |
213 |
214 | Designer
215 | MSBuild:Compile
216 |
217 |
218 | Designer
219 | MSBuild:Compile
220 |
221 |
222 | Designer
223 | MSBuild:Compile
224 |
225 |
226 | Designer
227 | MSBuild:Compile
228 |
229 |
230 | Designer
231 | MSBuild:Compile
232 |
233 |
234 | Designer
235 | MSBuild:Compile
236 |
237 |
238 | Designer
239 | MSBuild:Compile
240 |
241 |
242 | Designer
243 | MSBuild:Compile
244 |
245 |
246 | Designer
247 | MSBuild:Compile
248 |
249 |
250 | Designer
251 | MSBuild:Compile
252 |
253 |
254 | Designer
255 | MSBuild:Compile
256 |
257 |
258 | Designer
259 | MSBuild:Compile
260 |
261 |
262 | Designer
263 | MSBuild:Compile
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
--------------------------------------------------------------------------------
/PackHistorian/PackWatcher.cs:
--------------------------------------------------------------------------------
1 | using HearthMirror.Objects;
2 | using Hearthstone_Deck_Tracker.Hearthstone;
3 | using HDTCard = Hearthstone_Deck_Tracker.Hearthstone.Card;
4 | using HearthWatcher;
5 | using HearthWatcher.EventArgs;
6 | using System.Collections.Generic;
7 | using System.Windows;
8 | using PackTracker.Event;
9 | using PackTracker.Entity;
10 | using System;
11 | using Hearthstone_Deck_Tracker.Enums.Hearthstone;
12 | using System.Diagnostics;
13 |
14 | namespace PackTracker {
15 | delegate void PackOpenedEventHandler(object sender, PackOpenedEventArgs e);
16 |
17 | class PackWatcher {
18 | bool _running = false;
19 | List _hearthstones = new List();
20 |
21 | public bool Running { get { return _running; } }
22 |
23 | public event PackOpenedEventHandler PackOpened;
24 | public event EventHandler PackScreenEntered;
25 | public event EventHandler PackScreenLeft;
26 |
27 | public PackWatcher() {
28 | Hearthstone_Deck_Tracker.API.GameEvents.OnModeChanged.Add(HandleMode);
29 | }
30 |
31 | private void NewPack(object sender, PackEventArgs e) {
32 | DateTime Time = DateTime.Now;
33 | List Cards = new List();
34 |
35 | foreach(var Card in e.Cards) {
36 | HDTCard cardFromId = Database.GetCardFromId(Card.Id);
37 | Cards.Add(new Entity.Card(cardFromId, 1 == Card.PremiumType));
38 | }
39 |
40 | OnPackOpened(new Pack(e.PackId, Time, Cards));
41 | }
42 |
43 | void OnPackOpened(Pack Pack) {
44 | PackOpened?.Invoke(this, new PackOpenedEventArgs(Pack));
45 | }
46 |
47 | private void HandleMode(Mode Mode) {
48 | if(!_running) return;
49 |
50 | if(Mode == Mode.PACKOPENING) {
51 | foreach(Process hs in Process.GetProcessesByName("Hearthstone")) {
52 | if(hs is Process && !_hearthstones.Contains(hs)) {
53 | hs.EnableRaisingEvents = true;
54 | hs.Exited += Hs_Exited;
55 | _hearthstones.Add(hs);
56 | }
57 | }
58 |
59 | PackScreenEntered.Invoke(this, new EventArgs());
60 | } else {
61 | if(_hearthstones.Count > 0) {
62 | _hearthstones.ForEach(x => { x.Exited -= Hs_Exited; x.EnableRaisingEvents = false; });
63 | _hearthstones.Clear();
64 |
65 | PackScreenLeft?.Invoke(this, new EventArgs());
66 | }
67 | }
68 | }
69 |
70 | private void Hs_Exited(object sender, EventArgs e) {
71 | if(sender is Process) {
72 | Process hs = (Process)sender;
73 | if(_hearthstones.Contains(hs)) {
74 | hs.Exited -= Hs_Exited;
75 | hs.EnableRaisingEvents = false;
76 | _hearthstones.Remove(hs);
77 | }
78 | }
79 |
80 | if(_hearthstones.Count == 0) {
81 | PackScreenLeft?.Invoke(this, new EventArgs());
82 | }
83 | }
84 |
85 | public void Start() {
86 | if(!_running) {
87 | Watchers.PackWatcher.NewPackEventHandler += NewPack;
88 | _running = true;
89 | }
90 | }
91 |
92 | public void Stop() {
93 | if(_running) {
94 | Watchers.PackWatcher.NewPackEventHandler -= NewPack;
95 | _hearthstones.ForEach(x => { x.Exited -= Hs_Exited; x.EnableRaisingEvents = false; });
96 | _hearthstones.Clear();
97 | _running = false;
98 | }
99 | }
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/PackHistorian/Plugin.cs:
--------------------------------------------------------------------------------
1 |
2 | using Hearthstone_Deck_Tracker.Plugins;
3 | using System;
4 | using System.Reflection;
5 | using System.Windows.Controls;
6 | using PackTracker.Storage;
7 | using Hearthstone_Deck_Tracker.Utility.Toasts;
8 | using System.ComponentModel;
9 | using PackTracker.Update;
10 | using System.Windows;
11 |
12 | namespace PackTracker {
13 | public class Plugin : IPlugin {
14 | const string _name = "Pack Tracker";
15 | readonly static Version _version = new Version("1.4.0");
16 |
17 | private PackWatcher _watcher;
18 | Updater _updater;
19 | History _history;
20 | IHistoryStorage _historyStorage = new XmlHistory();
21 | Settings _settings;
22 | ISettingsStorage _settingsStorage = new XmlSettings();
23 | WindowManager _windows = new WindowManager(_name);
24 | View.AverageCollection _averageCollection;
25 | View.Cache.PityTimerRepository _pityTimers;
26 |
27 | public static Version CurrentVersion { get => _version; }
28 |
29 | public Plugin() {
30 | _watcher = new PackWatcher();
31 | _updater = new Updater();
32 |
33 | try {
34 | _history = _historyStorage.Fetch();
35 | _averageCollection = new View.AverageCollection(_history);
36 | } catch {
37 | _history = new History();
38 | }
39 |
40 | try {
41 | _settings = _settingsStorage.Fetch();
42 | } catch {
43 | _settings = new Settings();
44 | }
45 |
46 | _pityTimers = new View.Cache.PityTimerRepository(_history);
47 |
48 |
49 | //watcher
50 | _watcher.PackOpened += (sender, e) => {
51 | _history.Add(e.Pack);
52 | _historyStorage.Store(_history.Ascending);
53 |
54 | if(_settings.Spoil) {
55 | View.Average Average = _averageCollection.FindForPackId(e.Pack.Id);
56 | ToastManager.ShowCustomToast(new Controls.Toast(e.Pack, Average));
57 | }
58 | };
59 |
60 | _watcher.PackScreenEntered += (sender, e) => { if(_settings.PityOverlay) _windows.ShowPityTimerOverlay(_history, _pityTimers); };
61 | _watcher.PackScreenLeft += (sender, e) => _windows.ClosePityTimerOverlay();
62 | }
63 |
64 | public string Author
65 | {
66 | get
67 | {
68 | return "DBqFetti ";
69 | }
70 | }
71 |
72 | public string ButtonText
73 | {
74 | get
75 | {
76 | return "Settings";
77 | }
78 | }
79 |
80 | public string Description
81 | {
82 | get
83 | {
84 | return Name + " is a plugin that allows you to keep an eye on every pack you open. This allows you to see how many cards of different rarities have dropped over time and also enables you to estimate when your next Epic or Legendary is coming!";
85 | }
86 | }
87 |
88 | public MenuItem MenuItem {
89 | get {
90 | Controls.Menu Menu = new Controls.Menu();
91 | Menu.mnu_History.Click += (sender, e) => _windows.ShowHistoryWin(_history);
92 | Menu.mnu_Statistic.Click += (sender, e) => _windows.ShowStatisticWin(_history);
93 | Menu.mnu_Log.Click += (sender, e) => _windows.ShowLogWin(_history);
94 | Menu.mnu_Search.Click += (sender, e) => _windows.ShowSearchWin(_history);
95 | Menu.mnu_PityTimers.Click += (sender, e) => _windows.ShowPityWin(_history, _pityTimers);
96 |
97 | return Menu;
98 | }
99 | }
100 |
101 | public string Name => _name;
102 | public static string NAME => _name;
103 |
104 | public Version Version
105 | {
106 | get
107 | {
108 | return CurrentVersion;
109 | }
110 | }
111 |
112 | public void OnButtonPress() {
113 | _windows.ShowSettingsWin(_settings, _settingsStorage);
114 | }
115 |
116 | public void OnLoad() {
117 | _watcher.Start();
118 |
119 | if(_settings.Update) {
120 | BackgroundWorker bwCheck = new BackgroundWorker();
121 | bwCheck.DoWork += (sender, e) => {
122 | e.Result = _updater.NewVersionAvailable();
123 | };
124 | bwCheck.RunWorkerCompleted += (sender, e) => {
125 | if((bool?)e.Result == true) {
126 | _windows.ShowSettingsWin(_settings, _settingsStorage, typeof(Controls.Settings.Update));
127 | }
128 | };
129 | bwCheck.RunWorkerAsync();
130 | }
131 | }
132 |
133 | public void OnUnload() {
134 | _watcher.Stop();
135 | }
136 |
137 | public void OnUpdate() {
138 | }
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/PackHistorian/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // Allgemeine Informationen über eine Assembly werden über die folgenden
6 | // Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern,
7 | // die einer Assembly zugeordnet sind.
8 | [assembly: AssemblyTitle("PackTracker")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("PackTracker")]
13 | [assembly: AssemblyCopyright("")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Durch Festlegen von ComVisible auf FALSE werden die Typen in dieser Assembly
18 | // für COM-Komponenten unsichtbar. Wenn Sie auf einen Typ in dieser Assembly von
19 | // COM aus zugreifen müssen, sollten Sie das ComVisible-Attribut für diesen Typ auf "True" festlegen.
20 | [assembly: ComVisible(false)]
21 |
22 | // Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird
23 | [assembly: Guid("3c5023c1-d9b6-4ffa-b3a2-2a8466de5074")]
24 |
25 | // Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten:
26 | //
27 | // Hauptversion
28 | // Nebenversion
29 | // Buildnummer
30 | // Revision
31 | //
32 | // Sie können alle Werte angeben oder Standardwerte für die Build- und Revisionsnummern verwenden,
33 | // indem Sie "*" wie unten gezeigt eingeben:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("0.3.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/PackHistorian/Resources/Icons/1497613802_feather.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MGK82/PackTracker/d6ac5706790bc0287798e59b72b42f58667afc99/PackHistorian/Resources/Icons/1497613802_feather.ico
--------------------------------------------------------------------------------
/PackHistorian/Resources/Icons/1498438250_calendar-3.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MGK82/PackTracker/d6ac5706790bc0287798e59b72b42f58667afc99/PackHistorian/Resources/Icons/1498438250_calendar-3.ico
--------------------------------------------------------------------------------
/PackHistorian/Resources/Icons/1498438378_line-chart_4.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MGK82/PackTracker/d6ac5706790bc0287798e59b72b42f58667afc99/PackHistorian/Resources/Icons/1498438378_line-chart_4.ico
--------------------------------------------------------------------------------
/PackHistorian/Resources/Icons/1498791216_calendar-2.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MGK82/PackTracker/d6ac5706790bc0287798e59b72b42f58667afc99/PackHistorian/Resources/Icons/1498791216_calendar-2.ico
--------------------------------------------------------------------------------
/PackHistorian/Resources/Icons/if__chart_2006795.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MGK82/PackTracker/d6ac5706790bc0287798e59b72b42f58667afc99/PackHistorian/Resources/Icons/if__chart_2006795.ico
--------------------------------------------------------------------------------
/PackHistorian/Resources/Icons/if_circle-help-question-mark-glyph_763459.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MGK82/PackTracker/d6ac5706790bc0287798e59b72b42f58667afc99/PackHistorian/Resources/Icons/if_circle-help-question-mark-glyph_763459.ico
--------------------------------------------------------------------------------
/PackHistorian/Resources/Icons/if_search_322497.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MGK82/PackTracker/d6ac5706790bc0287798e59b72b42f58667afc99/PackHistorian/Resources/Icons/if_search_322497.ico
--------------------------------------------------------------------------------
/PackHistorian/Settings.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace PackTracker {
9 | public class Settings {
10 | public bool Spoil { get; set; } = false;
11 | public bool PityOverlay { get; set; } = true;
12 | public bool Update { get; set; } = true;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/PackHistorian/Storage/IHistoryStorage.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace PackTracker.Storage {
8 | interface IHistoryStorage {
9 | History Fetch();
10 | void Store(History History);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/PackHistorian/Storage/ISettingsStorage.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace PackTracker.Storage {
8 | interface ISettingsStorage {
9 | Settings Fetch();
10 | void Store(Settings History);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/PackHistorian/Storage/XmlHistory.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Xml;
7 | using PackTracker.Entity;
8 | using Hearthstone_Deck_Tracker;
9 | using System.IO;
10 | using Hearthstone_Deck_Tracker.Hearthstone;
11 | using HDTCard = Hearthstone_Deck_Tracker.Hearthstone.Card;
12 |
13 | namespace PackTracker.Storage {
14 | class XmlHistory : IHistoryStorage {
15 | public History Fetch() {
16 | History History = new History();
17 |
18 | string path = Path.Combine(Config.AppDataPath, "PackTracker", "History.xml");
19 | if(File.Exists(path)) {
20 | XmlDocument Xml = new XmlDocument();
21 | Xml.Load(path);
22 | XmlNode Root = Xml.SelectSingleNode("history");
23 |
24 | if(Root != null) {
25 | XmlNodeList Packs = Root.SelectNodes("pack");
26 |
27 | if(Packs.Count > 0) {
28 | foreach(XmlNode Pack in Packs) {
29 |
30 | if(int.TryParse(Pack.Attributes["id"]?.Value, out int packId) && long.TryParse(Pack.Attributes["time"]?.Value, out long ticks)) {
31 | DateTime Time = new DateTime(ticks);
32 | XmlNodeList Cards = Pack.SelectNodes("card");
33 |
34 | if(Cards.Count > 0) {
35 | List HistoryCards = new List();
36 |
37 | foreach(XmlNode Card in Cards) {
38 | string cardId = Card.Attributes["id"]?.Value;
39 | if(!string.IsNullOrEmpty(cardId)) {
40 | HDTCard HDTCard = Database.GetCardFromId(cardId);
41 | string premium = Card.Attributes["premium"]?.Value;
42 |
43 | HistoryCards.Add(new Entity.Card(HDTCard, premium == "premium"));
44 | } else {
45 | return new History();
46 | }
47 | }
48 |
49 | History.Add(new Pack(packId, Time, HistoryCards));
50 | } else {
51 | return new History();
52 | }
53 | } else {
54 | return new History();
55 | }
56 | }
57 | }
58 | }
59 | }
60 |
61 | return History;
62 | }
63 |
64 | public void Store(History History) {
65 | XmlDocument Xml = new XmlDocument();
66 | Xml.AppendChild(Xml.CreateXmlDeclaration("1.0", "UTF-8", null));
67 |
68 | XmlNode Root = Xml.CreateElement("history");
69 | Xml.AppendChild(Root);
70 |
71 | foreach(Pack Pack in History) {
72 | XmlNode PackNode = Xml.CreateElement("pack");
73 | Root.AppendChild(PackNode);
74 |
75 | XmlAttribute Time = Xml.CreateAttribute("time");
76 | Time.Value = Pack.Time.Ticks.ToString();
77 | PackNode.Attributes.Append(Time);
78 |
79 | XmlAttribute PackId = Xml.CreateAttribute("id");
80 | PackId.Value = Pack.Id.ToString();
81 | PackNode.Attributes.Append(PackId);
82 |
83 | foreach(Entity.Card Card in Pack.Cards) {
84 | XmlNode CardNode = Xml.CreateElement("card");
85 | PackNode.AppendChild(CardNode);
86 |
87 | XmlAttribute CardId = Xml.CreateAttribute("id");
88 | CardId.Value = Card.HDTCard.Id;
89 | CardNode.Attributes.Append(CardId);
90 |
91 | if(Card.Premium) {
92 | XmlAttribute Premium = Xml.CreateAttribute("premium");
93 | Premium.Value = "premium";
94 | CardNode.Attributes.Append(Premium);
95 | }
96 | }
97 | }
98 |
99 | string path = Path.Combine(Config.AppDataPath, "PackTracker");
100 | if(!Directory.Exists(path)) {
101 | Directory.CreateDirectory(path);
102 | }
103 | path = Path.Combine(path, "History.xml");
104 |
105 | Xml.Save(path);
106 | }
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/PackHistorian/Storage/XmlSettings.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using System.Xml;
8 | using Hearthstone_Deck_Tracker;
9 |
10 | namespace PackTracker.Storage {
11 | class XmlSettings : ISettingsStorage {
12 | public Settings Fetch() {
13 | Settings Settings = new Settings();
14 |
15 | string path = Path.Combine(Config.AppDataPath, "PackTracker", "Settings.xml");
16 | if(File.Exists(path)) {
17 | XmlDocument Xml = new XmlDocument();
18 | Xml.Load(path);
19 | XmlNode Root = Xml.SelectSingleNode("settings");
20 |
21 | if(Root != null) {
22 | if(bool.TryParse(Root.SelectSingleNode("spoil").InnerText, out bool spoil)) {
23 | Settings.Spoil = spoil;
24 | }
25 |
26 | try {
27 | if(bool.TryParse(Root.SelectSingleNode("pityoverlay").InnerText, out bool pityoverlay)) {
28 | Settings.PityOverlay = pityoverlay;
29 | }
30 | } catch {
31 | Settings.PityOverlay = true;
32 | }
33 |
34 | if(bool.TryParse(Root.SelectSingleNode("update").InnerText, out bool update)) {
35 | Settings.Update = update;
36 | }
37 | }
38 | }
39 |
40 | return Settings;
41 | }
42 |
43 | public void Store(Settings Settings) {
44 | XmlDocument Xml = new XmlDocument();
45 | Xml.AppendChild(Xml.CreateXmlDeclaration("1.0", "UTF-8", null));
46 |
47 | XmlNode Root = Xml.CreateElement("settings");
48 | Xml.AppendChild(Root);
49 |
50 | XmlNode SpoilNode = Xml.CreateElement("spoil");
51 | SpoilNode.InnerText = Settings.Spoil.ToString();
52 | Root.AppendChild(SpoilNode);
53 |
54 | XmlNode PityOverlayNode = Xml.CreateElement("pityoverlay");
55 | PityOverlayNode.InnerText = Settings.PityOverlay.ToString();
56 | Root.AppendChild(PityOverlayNode);
57 |
58 | XmlNode UpdateNode = Xml.CreateElement("update");
59 | UpdateNode.InnerText = Settings.Update.ToString();
60 | Root.AppendChild(UpdateNode);
61 |
62 |
63 | string path = Path.Combine(Config.AppDataPath, "PackTracker");
64 | if(!Directory.Exists(path)) {
65 | Directory.CreateDirectory(path);
66 | }
67 | path = Path.Combine(path, "Settings.xml");
68 |
69 | Xml.Save(path);
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/PackHistorian/Update/Asset.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Runtime.Serialization;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace PackTracker.Update {
9 | [DataContract]
10 | public class Asset {
11 | [DataMember]
12 | public string name;
13 |
14 | [DataMember]
15 | public string browser_download_url;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/PackHistorian/Update/Release.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Runtime.Serialization;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace PackTracker.Update {
9 | [DataContract]
10 | public class Release {
11 | [DataMember]
12 | public string tag_name;
13 |
14 | [DataMember]
15 | public string name;
16 |
17 | [DataMember]
18 | public List assets;
19 |
20 | [DataMember]
21 | public string body;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/PackHistorian/Update/Updater.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.IO.Compression;
5 | using System.Linq;
6 | using System.Net;
7 | using System.Reflection;
8 | using System.Runtime.Serialization.Json;
9 | using System.Text;
10 | using System.Text.RegularExpressions;
11 | using System.Threading.Tasks;
12 | using Hearthstone_Deck_Tracker;
13 |
14 | namespace PackTracker.Update {
15 | public class Updater {
16 | static string _userAgend = "PackTracker";
17 |
18 | public bool? NewVersionAvailable() {
19 | Release LatestRelease = null as Release;
20 | try {
21 | LatestRelease = GetLatestRelease();
22 | }
23 | catch(WebException) {
24 | return null;
25 | }
26 |
27 | Version LatestVersion = ParseVersion(LatestRelease.tag_name);
28 |
29 | return Plugin.CurrentVersion.CompareTo(LatestVersion) < 0;
30 | }
31 |
32 | public static Version ParseVersion(string version) {
33 | return new Version(Regex.Match(version, @"\d+(\.\d+)*").ToString());
34 | }
35 |
36 | public bool Update() {
37 | Release LatestRelease = GetLatestRelease();
38 | Asset Asset = LatestRelease.assets.Single(x => x.name == "PackTracker.zip");
39 |
40 | try {
41 | using(WebClient client = new WebClient()) {
42 | using(Stream download = client.OpenRead(Asset.browser_download_url)) {
43 | string path = Path.Combine(Config.AppDataPath, "Plugins");
44 | string tempPath = Path.Combine(Path.GetTempPath(), "PackTracker");
45 |
46 | if(Directory.Exists(tempPath)) {
47 | Directory.Delete(tempPath, true);
48 | }
49 |
50 | ZipArchive Zipper = new ZipArchive(download);
51 | Zipper.ExtractToDirectory(tempPath);
52 |
53 | foreach(string file in Directory.GetFiles(tempPath)) {
54 | string target = Path.Combine(path, Path.GetFileName(file));
55 | File.Copy(file, target, true);
56 | File.SetLastWriteTime(target, DateTime.Now);
57 | }
58 |
59 | Directory.Delete(tempPath, true);
60 |
61 | return true;
62 | }
63 | }
64 | }
65 | catch(WebException) {
66 | return false;
67 | }
68 | }
69 |
70 | public Release GetLatestRelease() {
71 | HttpWebRequest request = WebRequest.CreateHttp(@"https://api.github.com/repos/mgk82/PackTracker/releases/latest");
72 | request.Proxy = null;
73 | request.UserAgent = _userAgend;
74 |
75 | Release Release = new Release();
76 | using(Stream response = request.GetResponse().GetResponseStream()) {
77 | DataContractJsonSerializer ser = new DataContractJsonSerializer(Release.GetType());
78 | Release = (Release)ser.ReadObject(response);
79 | }
80 |
81 | return Release;
82 | }
83 |
84 | public IEnumerable GetAllReleases() {
85 | List Releases = new List();
86 |
87 | HttpWebRequest request = WebRequest.CreateHttp(@"https://api.github.com/repos/mgk82/PackTracker/releases");
88 | request.Proxy = null;
89 | request.UserAgent = _userAgend;
90 |
91 | try {
92 | using(Stream response = request.GetResponse().GetResponseStream()) {
93 | DataContractJsonSerializer ser = new DataContractJsonSerializer(Releases.GetType());
94 | Releases = (List)ser.ReadObject(response);
95 | }
96 | } catch (WebException) {
97 | return null;
98 | }
99 |
100 | return Releases.AsEnumerable();
101 | }
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/PackHistorian/View/AbstractValueConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Globalization;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using System.Windows.Data;
8 | using Hearthstone_Deck_Tracker;
9 |
10 | namespace PackTracker.View {
11 | abstract class AbstractValueConverter : IValueConverter {
12 | public abstract object Convert(object value, Type targetType, object parameter, CultureInfo culture);
13 | public abstract object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture);
14 |
15 | protected CultureInfo GetCultureInfo() {
16 | CultureInfo cult = null as CultureInfo;
17 | try {
18 | string lang = Config.Instance.Localization.ToString().Insert(2, "-");
19 |
20 | return new CultureInfo(lang);
21 | }
22 | catch(CultureNotFoundException) {
23 | return CultureInfo.InstalledUICulture;
24 | }
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/PackHistorian/View/Average.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using HearthDb.Enums;
7 | using System.Collections.Specialized;
8 | using System.ComponentModel;
9 |
10 | namespace PackTracker.View {
11 | public class Average : INotifyPropertyChanged {
12 | int _packId;
13 |
14 | List
15 | _countsEpic = new List(),
16 | _countsLeg = new List();
17 |
18 | bool
19 | _skippingEpic = true,
20 | _skippingLeg = true;
21 |
22 | int
23 | _currentEpic = 0,
24 | _currentLeg = 0;
25 |
26 | public event PropertyChangedEventHandler PropertyChanged;
27 |
28 | public int Id { get { return _packId; } }
29 |
30 | public int? AverageEpic { get { return _countsEpic.Count > 1 ? (int?)Math.Round(_countsEpic.Average(), 0) : null; } }
31 | public int? AverageLegendary { get { return _countsLeg.Count > 1 ? (int?)Math.Round(_countsLeg.Average(), 0) : null; } }
32 |
33 | public int CurrentEpic { get { return _currentEpic; } }
34 | public int CurrentLegendary { get { return _currentLeg; } }
35 |
36 | public Average(int PackId, History History) {
37 | _packId = PackId;
38 | AddCounts(History);
39 |
40 | History.CollectionChanged += History_CollectionChanged;
41 | }
42 |
43 | private void History_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
44 | if(e.Action == NotifyCollectionChangedAction.Add) {
45 | AddCounts(e.NewItems.Cast());
46 | }
47 | }
48 |
49 | private void AddCounts(IEnumerable Packs) {
50 | bool notifyAverageEpic = false;
51 | bool notifyAverageLegendary = false;
52 | bool notifyCurrent = false;
53 |
54 | foreach(Entity.Pack Pack in Packs) {
55 | if(Pack.Id == _packId) {
56 | _currentEpic++;
57 | _currentLeg++;
58 |
59 | notifyCurrent = true;
60 |
61 | if(Pack.Cards.Any(x => x.Rarity == Rarity.EPIC)) {
62 | if(_skippingEpic) {
63 | _skippingEpic = false;
64 | } else {
65 | _countsEpic.Add(_currentEpic);
66 | notifyAverageEpic = true;
67 | }
68 |
69 | _currentEpic = 0;
70 | }
71 |
72 | if(Pack.Cards.Any(x => x.Rarity == Rarity.LEGENDARY)) {
73 | if(_skippingLeg) {
74 | _skippingLeg = false;
75 | } else {
76 | _countsLeg.Add(_currentLeg);
77 | notifyAverageLegendary = true;
78 | }
79 |
80 | _currentLeg = 0;
81 | }
82 | }
83 | }
84 |
85 | if(notifyAverageEpic) {
86 | OnPropertyChanged("AverageEpic");
87 | }
88 |
89 | if(notifyAverageLegendary) {
90 | OnPropertyChanged("AverageLegendary");
91 | }
92 |
93 | if(notifyCurrent) {
94 | OnPropertyChanged("CurrentEpic");
95 | OnPropertyChanged("CurrentLegendary");
96 | }
97 | }
98 |
99 | private void OnPropertyChanged(string property) {
100 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
101 | }
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/PackHistorian/View/AverageCollection.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.Collections.ObjectModel;
5 | using System.Collections.Specialized;
6 | using System.Linq;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 |
10 | namespace PackTracker.View {
11 | public class AverageCollection : INotifyCollectionChanged, IEnumerable {
12 | ObservableCollection _statistics = new ObservableCollection();
13 | public event NotifyCollectionChangedEventHandler CollectionChanged;
14 |
15 | public AverageCollection(History History) {
16 | IEnumerable PackIds = History.Select(x => x.Id).Distinct().OrderBy(x => x);
17 | foreach(int Id in PackIds) {
18 | _statistics.Add(new Average(Id, History));
19 | }
20 |
21 | History.CollectionChanged += (sender, e) => {
22 | if(sender is History) {
23 | if(e.Action == NotifyCollectionChangedAction.Add) {
24 | foreach(Entity.Pack Pack in e.NewItems) {
25 | if(!_statistics.Any(x => x.Id == Pack.Id)) {
26 | Add(new Average(Pack.Id, (History)sender));
27 | }
28 | }
29 | }
30 | }
31 | };
32 |
33 | _statistics.CollectionChanged += (sender, e) => { CollectionChanged?.Invoke(this, e); };
34 | }
35 |
36 | void Add(Average Statistic) {
37 | foreach(Average Stat in _statistics) {
38 | if(Statistic.Id < Stat.Id) {
39 | _statistics.Insert(_statistics.IndexOf(Stat), Statistic);
40 | return;
41 | }
42 | }
43 |
44 | _statistics.Add(Statistic);
45 | }
46 |
47 | public IEnumerator GetEnumerator() {
48 | return _statistics.GetEnumerator();
49 |
50 | }
51 |
52 | IEnumerator IEnumerable.GetEnumerator() {
53 | return _statistics.GetEnumerator();
54 | }
55 |
56 | public Average FindForPackId(int id) {
57 | return _statistics.SingleOrDefault(x => x.Id == id);
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/PackHistorian/View/Cache/PityTimerRepository.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 | using HearthDb.Enums;
8 |
9 | namespace PackTracker.View.Cache {
10 | public class PityTimerRepository {
11 | History _history;
12 | List _cache = new List();
13 |
14 | public PityTimerRepository(History History) {
15 | _history = History;
16 | }
17 |
18 | public PityTimer GetPityTimer(int packId, Rarity rarity, bool premium, bool skipFirst) {
19 | PityTimer pt = _cache.FirstOrDefault(x => x.PackId == packId && x.Rarity == rarity && x.Premium == premium && x.SkipFirst == skipFirst);
20 | if(!(pt is PityTimer)) {
21 | pt = new PityTimer(_history, packId, rarity, premium, skipFirst);
22 | _cache.Add(pt);
23 | }
24 |
25 | return pt;
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/PackHistorian/View/DateConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace PackTracker.View {
8 | class DateConverter : DateTimeConverter {
9 | protected override string _format { get => "D"; }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/PackHistorian/View/DateTimeConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Globalization;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using System.Windows.Data;
8 | using Hearthstone_Deck_Tracker;
9 |
10 | namespace PackTracker.View {
11 | class DateTimeConverter : AbstractValueConverter {
12 | static Config _config = Config.Instance;
13 | protected virtual string _format { get => "G"; }
14 |
15 | public override object Convert(object value, Type targetType, object parameter, CultureInfo culture) => value is DateTime ? ((DateTime)value).ToString(_format, GetCultureInfo()) : value;
16 | public override object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
17 | throw new NotImplementedException();
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/PackHistorian/View/DecimalConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Globalization;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using System.Windows.Data;
8 | using Hearthstone_Deck_Tracker;
9 |
10 | namespace PackTracker.View {
11 | class DecimalConverter : AbstractValueConverter {
12 | public override object Convert(object value, Type targetType, object parameter, CultureInfo culture) => int.TryParse(value.ToString(), out int dec) ? dec.ToString("n0", GetCultureInfo()) : value;
13 | public override object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
14 | throw new NotImplementedException();
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/PackHistorian/View/PackNameConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Globalization;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using System.Windows.Data;
8 | using HearthDb.Enums;
9 | using Hearthstone_Deck_Tracker;
10 |
11 | namespace PackTracker.View {
12 | class PackNameConverter : IValueConverter {
13 | static Config _config = Config.Instance;
14 | static Dictionary> PackNames = new Dictionary>() {
15 | {
16 | 1, new Dictionary() {
17 | { Locale.enUS, "Classic" },
18 | { Locale.enGB, "Classic" },
19 | { Locale.deDE, "Klassik" },
20 | { Locale.zhCN, "经典" },
21 | }
22 | },
23 | {
24 | 9, new Dictionary() {
25 | { Locale.enUS, "Goblins vs Gnomes" },
26 | { Locale.enGB, "Goblins vs Gnomes" },
27 | { Locale.frFR, "Gobelins et Gnomes" },
28 | { Locale.deDE, "Goblins gegen Gnome" },
29 | { Locale.koKR, "고블린 대 노움" },
30 | { Locale.esES, "Goblins vs Gnomos" },
31 | { Locale.esMX, "Goblins vs Gnomos" },
32 | { Locale.ruRU, "Гоблины и гномы" },
33 | { Locale.zhTW, "《哥哥打地地》" },
34 | { Locale.zhCN, "地精大战侏儒" },
35 | { Locale.itIT, "Goblin vs Gnomi" },
36 | { Locale.ptBR, "Goblins vs Gnomos" },
37 | { Locale.plPL, "Gobliny vs Gnomy" },
38 | { Locale.ptPT, "Goblins vs Gnomos" },
39 | { Locale.jaJP, "ゴブリンvsノーム" },
40 | { Locale.thTH, "Goblins vs Gnomes" },
41 | }
42 | },
43 | {
44 | 10, new Dictionary() {
45 | { Locale.enUS, "The Grand Tournament" },
46 | { Locale.enGB, "The Grand Tournament" },
47 | { Locale.frFR, "Le Grand Tournoi" },
48 | { Locale.deDE, "Das Große Turnier" },
49 | { Locale.koKR, "대 마상시합" },
50 | { Locale.esES, "El Gran Torneo" },
51 | { Locale.esMX, "El Gran Torneo" },
52 | { Locale.ruRU, "Большой турнир" },
53 | { Locale.zhTW, "《銀白聯賽》" },
54 | { Locale.zhCN, "冠军的试炼" },
55 | { Locale.itIT, "Gran Torneo" },
56 | { Locale.ptBR, "O Grande Torneio" },
57 | { Locale.plPL, "Wielki Turniej" },
58 | { Locale.ptPT, "O Grande Torneio" },
59 | { Locale.jaJP, "グランド・トーナメント" },
60 | { Locale.thTH, "The Grand Tournament" },
61 | }
62 | },
63 | {
64 | 11, new Dictionary() {
65 | { Locale.enUS, "Whispers of the Old Gods" },
66 | { Locale.enGB, "Whispers of the Old Gods" },
67 | { Locale.frFR, "Les murmures des Dieux très anciens" },
68 | { Locale.deDE, "Das Flüstern der Alten Götter" },
69 | { Locale.koKR, "고대 신의 속삭임" },
70 | { Locale.esES, "Susurros de los Dioses Antiguos" },
71 | { Locale.esMX, "Susurros de los Dioses Antiguos" },
72 | { Locale.ruRU, "Пробуждение древних богов" },
73 | { Locale.zhTW, "《古神碎碎念》" },
74 | { Locale.zhCN, "上古之神的低语" },
75 | { Locale.itIT, "Sussurri degli Dei Antichi" },
76 | { Locale.ptBR, "Sussurros dos Deuses Antigos" },
77 | { Locale.plPL, "Przedwieczni Bogowie" },
78 | { Locale.ptPT, "Sussurros dos Deuses Antigos" },
79 | { Locale.jaJP, "旧神のささやき" },
80 | { Locale.thTH, "Whispers of the Old Gods" },
81 | }
82 | },
83 | {
84 | 19, new Dictionary() {
85 | { Locale.enUS, "Mean Streets of Gadgetzan" },
86 | { Locale.enGB, "Mean Streets of Gadgetzan" },
87 | { Locale.frFR, "Main basse sur Gadgetzan" },
88 | { Locale.deDE, "Die Straßen von Gadgetzan" },
89 | { Locale.koKR, "비열한 거리의 가젯잔" },
90 | { Locale.esES, "Mafias de Gadgetzan" },
91 | { Locale.esMX, "Mafias de Gadgetzan" },
92 | { Locale.ruRU, "Злачный город Прибамбасск" },
93 | { Locale.zhTW, "《黑街英雄之加基森風雲》" },
94 | { Locale.zhCN, "龙争虎斗加基森" },
95 | { Locale.itIT, "I bassifondi di Meccania" },
96 | { Locale.ptBR, "As Gangues de Geringontzan" },
97 | { Locale.plPL, "Ciemne zaułki Gadżetonu" },
98 | { Locale.ptPT, "As Gangues de Geringontzan" },
99 | { Locale.jaJP, "仁義なきガジェッツァン" },
100 | { Locale.thTH, "Mean Streets of Gadgetzan" },
101 | }
102 | },
103 | {
104 | 20, new Dictionary() {
105 | { Locale.enUS, "Journey to Un'Goro" },
106 | { Locale.enGB, "Journey to Un'Goro" },
107 | { Locale.frFR, "Voyage au centre d’Un’Goro" },
108 | { Locale.deDE, "Reise nach Un'Goro" },
109 | { Locale.koKR, "운고로를 향한 여정" },
110 | { Locale.esES, "Viaje a Un'Goro" },
111 | { Locale.esMX, "Viaje a Un'Goro" },
112 | { Locale.ruRU, "Экспедиция в Ун'Горо" },
113 | { Locale.zhTW, "《安戈洛歷險記》" },
114 | { Locale.zhCN, "勇闯安戈洛" },
115 | { Locale.itIT, "Viaggio a Un'Goro" },
116 | { Locale.ptBR, "Jornada a Un'Goro" },
117 | { Locale.plPL, "Podróż do wnętrza Un'Goro" },
118 | { Locale.ptPT, "Jornada a Un'Goro" },
119 | { Locale.jaJP, "大魔境ウンゴロ" },
120 | { Locale.thTH, "Journey to Un'Goro" },
121 | }
122 | },
123 | {
124 | 21, new Dictionary() {
125 | { Locale.enUS, "Knights of the Frozen Throne" },
126 | { Locale.enGB, "Knights of the Frozen Throne" },
127 | { Locale.frFR, "Chevaliers du Trône de glace" },
128 | { Locale.deDE, "Ritter des Frostthrons" },
129 | { Locale.koKR, "얼어붙은 왕좌의 기사들" },
130 | { Locale.esES, "Caballeros del Trono Helado" },
131 | { Locale.esMX, "Caballeros del Trono Helado" },
132 | { Locale.ruRU, "Рыцари Ледяного Трона" },
133 | { Locale.zhTW, "《冰封王座》" },
134 | { Locale.zhCN, "冰封王座的骑士" },
135 | { Locale.itIT, "Cavalieri del Trono di Ghiaccio" },
136 | { Locale.ptBR, "Cavaleiros do Trono Gélido" },
137 | { Locale.plPL, "Rycerze Mroźnego Tronu" },
138 | { Locale.ptPT, "Cavaleiros do Trono Gélido" },
139 | { Locale.jaJP, "凍てつく玉座の騎士団" },
140 | { Locale.thTH, "Knights of the Frozen Throne" },
141 | }
142 | },
143 | {
144 | 23, new Dictionary() {
145 | { Locale.enUS, "Classic (Gold)" },
146 | { Locale.enGB, "Classic (Gold)" },
147 | { Locale.deDE, "Klassik (Gold)" },
148 | { Locale.zhCN, "经典(金色)" },
149 | }
150 | },
151 | {
152 | 30, new Dictionary() {
153 | { Locale.enUS, "Kobolds and Catacombs" },
154 | { Locale.enGB, "Kobolds and Catacombs" },
155 | { Locale.frFR, "Kobolds et Catacombes" },
156 | { Locale.deDE, "Kobolde & Katakomben" },
157 | { Locale.koKR, "코볼트와 지하 미궁" },
158 | { Locale.esES, "Kóbolds & Catacumbas" },
159 | { Locale.esMX, "Kóbolds & Catacumbas" },
160 | { Locale.ruRU, "Кобольды и катакомбы" },
161 | { Locale.zhTW, "《狗頭人與地下城》" },
162 | { Locale.zhCN, "狗头人与地下世界" },
163 | { Locale.itIT, "Coboldi & Catacombe" },
164 | { Locale.ptBR, "Kobolds & Catacumbas" },
165 | { Locale.plPL, "Koboldy i katakumby" },
166 | { Locale.ptPT, "Kobolds & Catacumbas" },
167 | { Locale.jaJP, "コボルトと秘宝の迷宮" },
168 | { Locale.thTH, "Kobolds and Catacombs" }
169 | }
170 | },
171 | {
172 | 31, new Dictionary() {
173 | { Locale.enUS, "The Witchwood" },
174 | { Locale.enGB, "The Witchwood" },
175 | { Locale.frFR, "Le Bois Maudit" },
176 | { Locale.deDE, "Der Hexenwald" },
177 | { Locale.koKR, "마녀숲" },
178 | { Locale.esES, "El Bosque Embrujado" },
179 | { Locale.esMX, "El Bosque Embrujado" },
180 | { Locale.ruRU, "Ведьмин лес" },
181 | { Locale.zhTW, "《黑巫森林》" },
182 | { Locale.zhCN, "女巫森林" },
183 | { Locale.itIT, "Boscotetro" },
184 | { Locale.ptBR, "O Bosque das Bruxas" },
185 | { Locale.plPL, "Wiedźmi Las" },
186 | { Locale.ptPT, "O Bosque das Bruxas" },
187 | { Locale.jaJP, "妖の森ウィッチウッド" },
188 | { Locale.thTH, "The Witchwood" }
189 | }
190 | },
191 | {
192 | 38, new Dictionary() {
193 | { Locale.enUS, "The Boomsday Project" },
194 | { Locale.enGB, "The Boomsday Project" },
195 | { Locale.frFR, "Projet Armageboum" },
196 | { Locale.deDE, "Dr. Bumms Geheimlabor" },
197 | { Locale.koKR, "박사 붐의 폭심만만 프로젝트" },
198 | { Locale.esES, "El Proyecto Armagebum" },
199 | { Locale.esMX, "El Proyecto K-Bum" },
200 | { Locale.ruRU, "Проект Бумного дня" },
201 | { Locale.zhTW, "《爆爆計畫》" },
202 | { Locale.zhCN, "砰砰计划" },
203 | { Locale.itIT, "Operazione Apocalisse" },
204 | { Locale.ptBR, "O Projeto Cabum" },
205 | { Locale.plPL, "Projekt Hukatomba" },
206 | { Locale.ptPT, "O Projeto Cabum" },
207 | { Locale.jaJP, "博士のメカメカ大作戦" },
208 | { Locale.thTH, "The Boomsday Project" },
209 | }
210 | },
211 | {
212 | 40, new Dictionary() {
213 | { Locale.enUS, "Rastakhan's Rumble" },
214 | { Locale.enGB, "Rastakhan's Rumble" },
215 | { Locale.frFR, "Les Jeux de Rastakhan" },
216 | { Locale.deDE, "Rastakhans Rambazamba" },
217 | { Locale.koKR, "라스타칸의 대난투" },
218 | { Locale.esES, "La Arena de Rastakhan" },
219 | { Locale.esMX, "El Reto de Rastakhan" },
220 | { Locale.ruRU, "Растахановы игрища" },
221 | { Locale.zhTW, "《拉斯塔哈大混戰》" },
222 | { Locale.zhCN, "拉斯塔哈的大乱斗" },
223 | { Locale.itIT, "La sfida di Rastakhan" },
224 | { Locale.ptBR, "Ringue do Rastakhan" },
225 | { Locale.plPL, "Rozróba Rastakana" },
226 | { Locale.ptPT, "Ringue do Rastakhan" },
227 | { Locale.jaJP, "天下一ヴドゥ祭" },
228 | { Locale.thTH, "Rastakhan's Rumble" },
229 | }
230 | },
231 | {
232 | 49, new Dictionary() {
233 | { Locale.enUS, "Rise of Shadows" },
234 | { Locale.enGB, "Rise of Shadows" },
235 | { Locale.frFR, "Éveil des ombres" },
236 | { Locale.deDE, "Verschwörung der Schatten" },
237 | { Locale.koKR, "어둠의 반격" },
238 | { Locale.esES, "El Auge de las Sombras" },
239 | { Locale.esMX, "Ascenso de las Sombras" },
240 | { Locale.ruRU, "Возмездие теней" },
241 | { Locale.zhTW, "《反派大進擊》" },
242 | { Locale.zhCN, "暗影崛起" },
243 | { Locale.itIT, "L'ascesa delle ombre" },
244 | { Locale.ptBR, "Ascensão das Sombras" },
245 | { Locale. plPL, "Wyjście z cienia" },
246 | { Locale.ptPT, "Ascensão das Sombras" },
247 | { Locale.jaJP, "爆誕!悪党同盟" },
248 | { Locale.thTH, "Rise of Shadows" },
249 | }
250 | },
251 | {
252 | 128, new Dictionary() {
253 | { Locale.enUS, "Saviors of Uldum" },
254 | { Locale.enGB, "Saviors of Uldum" },
255 | { Locale.frFR, "Les Aventuriers d’Uldum" },
256 | { Locale.deDE, "Retter von Uldum" },
257 | { Locale.koKR, "울둠의 구원자" },
258 | { Locale.esES, "Salvadores de Uldum" },
259 | { Locale.esMX, "Defensores de Uldum" },
260 | { Locale.ruRU, "Спасители Ульдума" },
261 | { Locale.zhTW, "《奧丹姆守護者》" },
262 | { Locale.zhCN, "奧丹姆奇兵" },
263 | { Locale.itIT, "Salvatori di Uldum" },
264 | { Locale.ptBR, "Salvadores de Uldum" },
265 | { Locale. plPL, "Wybawcy Uldum" },
266 | { Locale.ptPT, "Salvadores de Uldum" },
267 | { Locale.jaJP, "突撃!探検同盟" },
268 | { Locale.thTH, "Saviors of Uldum" },
269 | }
270 | },
271 | {
272 | 347, new Dictionary() {
273 | { Locale.enUS, "Descent of Dragons" },
274 | { Locale.enGB, "Descent of Dragons" },
275 | { Locale.frFR, "L’Envol des Dragons" },
276 | { Locale.deDE, "Erbe der Drachen" },
277 | { Locale.koKR, "용의 강림" },
278 | { Locale.esES, "El Descenso de los Dragones" },
279 | { Locale.esMX, "Descenso de los Dragones" },
280 | { Locale.ruRU, "Натиск драконов" },
281 | { Locale.zhTW, "《降臨!遠古巨龍》" },
282 | { Locale.zhCN, "巨龙降临" },
283 | { Locale.itIT, "La Discesa dei Draghi" },
284 | { Locale.ptBR, "Despontar dos Dragões" },
285 | { Locale.plPL, "Wejście smoków" },
286 | { Locale.ptPT, "Despontar dos Dragões" },
287 | { Locale.jaJP, "激闘!ドラゴン大決戦" },
288 | { Locale.thTH, "Descent of Dragons" },
289 | }
290 | },
291 | {
292 | 423, new Dictionary() {
293 | { Locale.enUS, "Ashes of Outland" },
294 | { Locale.enGB, "Ashes of Outland" },
295 | { Locale.frFR, "Les Cendres de l’Outreterre" },
296 | { Locale.deDE, "Ruinen der Scherbenwelt" },
297 | { Locale.koKR, "황폐한 아웃랜드" },
298 | { Locale.esES, "Cenizas de Terrallende" },
299 | { Locale.esMX, "Cenizas de Terrallende" },
300 | { Locale.ruRU, "Руины Запределья" },
301 | { Locale.zhTW, "《外域之燼》" },
302 | { Locale.zhCN, "伊利丹的崛起" },
303 | { Locale.itIT, "Ceneri delle Terre Esterne" },
304 | { Locale.ptBR, "Cinzas de Terralém" },
305 | { Locale.plPL, "Popioły Rubieży" },
306 | { Locale.ptPT, "Cinzas de Terralém" },
307 | { Locale.jaJP, "灰に舞う降魔の狩人" },
308 | { Locale.thTH, "Ashes of Outland" },
309 | }
310 | },
311 | {
312 | 468, new Dictionary() {
313 | { Locale.enUS, "Scholomance Academy" },
314 | { Locale.enGB, "Scholomance Academy" },
315 | { Locale.frFR, "L’Académie Scholomance" },
316 | { Locale.deDE, "Akademie Scholomance" },
317 | { Locale.koKR, "스칼로맨스 아카데미" },
318 | { Locale.esES, "Academia Scholomance" },
319 | { Locale.esMX, "Academia Scholomance" },
320 | { Locale.ruRU, "Некроситет" },
321 | { Locale.zhTW, "通靈學院" },
322 | { Locale.zhCN, "通灵学园" },
323 | { Locale.itIT, "L'Accademia di Scholomance" },
324 | { Locale.ptBR, "Universidade de Scolomântia" },
325 | { Locale.plPL, "Scholomancjum" },
326 | { Locale.ptPT, "Universidade de Scolomântia" },
327 | { Locale.jaJP, "魔法学院スクロマンス" },
328 | { Locale.thTH, "Scholomance Academy" },
329 | }
330 | },
331 | {
332 | 470, new Dictionary() {
333 | { Locale.enUS, "Golden Pack" },
334 | { Locale.enGB, "Golden Pack" },
335 | { Locale.frFR, "Golden Pack" },
336 | { Locale.deDE, "Golden Pack" },
337 | { Locale.koKR, "Golden Pack" },
338 | { Locale.esES, "Golden Pack" },
339 | { Locale.esMX, "Golden Pack" },
340 | { Locale.ruRU, "Golden Pack" },
341 | { Locale.zhTW, "Golden Pack" },
342 | { Locale.zhCN, "Golden Pack" },
343 | { Locale.itIT, "Golden Pack" },
344 | { Locale.ptBR, "Golden Pack" },
345 | { Locale.plPL, "Golden Pack" },
346 | { Locale.ptPT, "Golden Pack" },
347 | { Locale.jaJP, "Golden Pack" },
348 | { Locale.thTH, "Golden Pack" },
349 | }
350 | },
351 | {
352 | 616, new Dictionary() {
353 | { Locale.enUS, "Madness at the Darkmoon Faire" },
354 | { Locale.enGB, "Madness at the Darkmoon Faire" },
355 | { Locale.frFR, "Folle journée à Sombrelune" },
356 | { Locale.deDE, "Der Dunkelmond-Wahnsinn" },
357 | { Locale.koKR, "광기의 다크문 축제" },
358 | { Locale.esES, "Locura en la Feria de la Luna Negra" },
359 | { Locale.esMX, "Locura en la Feria de la Luna Negra" },
360 | { Locale.ruRU, "Ярмарка безумия" },
361 | { Locale.zhTW, "《暗月馬戲團:古神也瘋狂》" },
362 | { Locale.zhCN, "疯狂的暗月马戏团" },
363 | { Locale.itIT, "Follia alla Fiera di Lunacupa" },
364 | { Locale.ptBR, "Delírios em Negraluna" },
365 | { Locale.ptPT, "Delírios em Negraluna" },
366 | { Locale.plPL, "Obłędny Festyn Lunomroku" },
367 | { Locale.jaJP, "ダークムーン・フェアへの招待状" },
368 | { Locale.thTH, "Madness at the Darkmoon Faire" },
369 | }
370 | },
371 | {
372 | 1525, new Dictionary() {
373 | { Locale.enUS, "Forged In The Barrens" },
374 | { Locale.enGB, "Forged In The Barrens" },
375 | { Locale.frFR, "Forgés dans les Tarides" },
376 | { Locale.deDE, "Geschmiedet im Brachland" },
377 | { Locale.koKR, "불모의 땅" },
378 | { Locale.esES, "Forjados en los Baldíos" },
379 | { Locale.esMX, "Forjados en Los Baldíos" },
380 | { Locale.ruRU, "Закаленные Степями" },
381 | { Locale.zhTW, "貧瘠之地" },
382 | { Locale.zhCN, "贫瘠之地的锤炼" },
383 | { Locale.itIT, "Forgiati nelle Savane" },
384 | { Locale.ptBR, "Forjado nos Sertões" },
385 | { Locale.ptPT, "Forjado nos Sertões" },
386 | { Locale.plPL, "Zahartowani przez Pustkowia" },
387 | { Locale.jaJP, "荒ぶる大地の強者たち" },
388 | { Locale.thTH, "Forged In The Barrens" },
389 | }
390 | },
391 | };
392 |
393 | public object Convert(object value, System.Type targetType, object parameter, CultureInfo culture) {
394 | if(value == null) {
395 | return "";
396 | }
397 |
398 | if(int.TryParse(value.ToString(), out int id)) {
399 | if(Enum.TryParse(_config.SelectedLanguage, out Locale lang)) {
400 | string converted = Convert(id, lang);
401 | if(!string.IsNullOrEmpty(converted)) {
402 | return converted;
403 | }
404 | }
405 |
406 | if(PackNames.ContainsKey(id)) {
407 | if(PackNames[id].ContainsKey(Locale.enUS)) {
408 | return PackNames[id][Locale.enUS];
409 | }
410 | }
411 | }
412 |
413 | return value;
414 | }
415 |
416 | public static string Convert(int packId, Locale lang) {
417 | if(PackNames.ContainsKey(packId)) {
418 | if(PackNames[packId].ContainsKey(lang)) {
419 | return PackNames[packId][lang];
420 | }
421 | }
422 |
423 | return null;
424 | }
425 |
426 | public object ConvertBack(object value, System.Type targetType, object parameter, CultureInfo culture) {
427 | throw new NotImplementedException();
428 | }
429 | }
430 | }
431 |
--------------------------------------------------------------------------------
/PackHistorian/View/PercentConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Globalization;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using System.Windows.Data;
8 | using Hearthstone_Deck_Tracker;
9 |
10 | namespace PackTracker.View {
11 | class PercentConverter : AbstractValueConverter {
12 | public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
13 | => double.TryParse(value.ToString(), out double percent) ? percent.ToString("p1", GetCultureInfo()) : value;
14 |
15 | public override object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
16 | throw new NotImplementedException();
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/PackHistorian/View/PityTimer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.ObjectModel;
4 | using System.Collections.Specialized;
5 | using System.ComponentModel;
6 | using System.Linq;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 | using HearthDb.Enums;
10 | using PackTracker.Entity;
11 |
12 | namespace PackTracker.View {
13 | public class PityTimer : INotifyPropertyChanged {
14 | int _packId;
15 | Rarity _rarity;
16 | bool _premium;
17 | bool _skipFirst;
18 | bool _waitForFirst;
19 |
20 | int _current = 0;
21 | ObservableCollection _prev = new ObservableCollection();
22 |
23 | public int PackId { get => _packId; }
24 | public Rarity Rarity { get => _rarity; }
25 | public bool Premium { get => _premium; }
26 | public bool SkipFirst { get => _skipFirst; }
27 | public bool WaitForFirst { get => _waitForFirst; }
28 |
29 | public int Current { get => _current; }
30 | public ObservableCollection Prev { get => _prev; }
31 | public int? Average { get => _prev.Count > 0 ? (int?)Math.Round(_prev.Average(), 0) : null; }
32 |
33 | public PityTimer(History History, int packId, Rarity rarity, bool premium, bool skipFirst) {
34 | _packId = packId;
35 | _rarity = rarity;
36 | _premium = premium;
37 | _skipFirst = _waitForFirst = skipFirst;
38 |
39 | foreach(Pack Pack in History) {
40 | AddPack(Pack);
41 | }
42 |
43 | History.CollectionChanged += (sender, e) => {
44 | if(e.Action == NotifyCollectionChangedAction.Add) {
45 | foreach(Pack Pack in e.NewItems) {
46 | AddPack(Pack);
47 | }
48 | }
49 | };
50 | }
51 |
52 | void AddPack(Pack Pack) {
53 | if(Pack.Id != _packId) {
54 | return;
55 | }
56 |
57 | if(Condition(Pack)) {
58 | int newCurr = _current;
59 | _current = 0;
60 |
61 | if(_waitForFirst) {
62 | _waitForFirst = false;
63 | } else {
64 | _prev.Add(newCurr);
65 | OnPropertyChanged("Average");
66 | }
67 | } else {
68 | _current++;
69 | }
70 |
71 | OnPropertyChanged("Current");
72 | }
73 |
74 | bool Condition(Pack Pack) {
75 | return Pack.Cards.Any(x => x.Rarity == _rarity && (_premium ? x.Premium : true));
76 | }
77 |
78 | public event PropertyChangedEventHandler PropertyChanged;
79 | void OnPropertyChanged(string prop) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/PackHistorian/View/Statistic.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using PackTracker.Entity;
7 | using System.Collections.Specialized;
8 | using System.ComponentModel;
9 | using HearthDb.Enums;
10 |
11 | namespace PackTracker.View {
12 | class Statistic : INotifyPropertyChanged{
13 | int _packId;
14 | List _packs;
15 |
16 | int
17 | _commonAmount = 0,
18 | _commonPacks = 0,
19 |
20 | _rareAmount = 0,
21 | _rarePacks = 0,
22 |
23 | _epicAmount = 0,
24 | _epicPacks = 0,
25 |
26 | _legendaryAmount = 0,
27 | _legendaryPacks = 0,
28 |
29 | _totalAmount = 0,
30 |
31 | _epicCurrStreak = 0,
32 | _epicLongStreak = 0,
33 |
34 | _legendaryCurrStreak = 0,
35 | _legendaryLongStreak = 0;
36 |
37 | public int CommonAmount { get => _commonAmount; }
38 | public double CommonCards { get => _totalAmount == 0 ? 0 : (double)_commonAmount / _totalAmount; }
39 | public double CommonPacks { get => _packs.Count == 0 ? 0 : (double)_commonPacks / _packs.Count; }
40 |
41 | public int RareAmount { get => _rareAmount; }
42 | public double RareCards { get => _totalAmount == 0 ? 0 : (double)_rareAmount / _totalAmount; }
43 | public double RarePacks { get => _packs.Count == 0 ? 0 : (double)_rarePacks / _packs.Count; }
44 |
45 | public int EpicAmount { get => _epicAmount; }
46 | public double EpicCards { get => _totalAmount == 0 ? 0 : (double)_epicAmount / _totalAmount; }
47 | public double EpicPacks { get => _packs.Count == 0 ? 0 : (double)_epicPacks / _packs.Count; }
48 |
49 | public int LegendaryAmount { get => _legendaryAmount; }
50 | public double LegendaryCards { get => _totalAmount == 0 ? 0 : (double)_legendaryAmount / _totalAmount; }
51 | public double LegendaryPacks { get => _packs.Count == 0 ? 0 : (double)_legendaryPacks / _packs.Count; }
52 |
53 | public int TotalPacks { get => _packs.Count; }
54 |
55 | public int EpicStreak { get => _epicLongStreak; }
56 | public int LegendaryStreak { get => _legendaryLongStreak; }
57 |
58 | public Statistic(int packId, History History) {
59 | _packId = packId;
60 | _packs = new List(History.Where(x => x.Id == packId));
61 |
62 | foreach(Pack Pack in _packs) {
63 | CountRarity(Pack);
64 | CountStreak(Pack);
65 | }
66 |
67 | History.CollectionChanged += (sender, e) => {
68 | if(e.Action == NotifyCollectionChangedAction.Add) {
69 | foreach(Pack Pack in e.NewItems) {
70 | if(Pack.Id == _packId) {
71 | _packs.Add(Pack);
72 | CountRarity(Pack);
73 | CountStreak(Pack);
74 |
75 | if(Pack.Cards.Any(x => x.Rarity == Rarity.COMMON)) {
76 | OnPropertyChanged("CommonAmount");
77 | }
78 |
79 | if(Pack.Cards.Any(x => x.Rarity == Rarity.RARE)) {
80 | OnPropertyChanged("RareAmount");
81 | }
82 |
83 | if(Pack.Cards.Any(x => x.Rarity == Rarity.EPIC)) {
84 | OnPropertyChanged("EpicAmount");
85 | }
86 |
87 | if(Pack.Cards.Any(x => x.Rarity == Rarity.LEGENDARY)) {
88 | OnPropertyChanged("LegendaryAmount");
89 | }
90 |
91 | OnPropertyChanged("CommonCards");
92 | OnPropertyChanged("CommonPacks");
93 | OnPropertyChanged("RareCards");
94 | OnPropertyChanged("RarePacks");
95 | OnPropertyChanged("EpicCards");
96 | OnPropertyChanged("EpicPacks");
97 | OnPropertyChanged("LegendaryCards");
98 | OnPropertyChanged("LegendaryPacks");
99 | OnPropertyChanged("TotalPacks");
100 | }
101 | }
102 | }
103 | };
104 | }
105 |
106 | void CountRarity(Pack Pack) {
107 | bool
108 | hasCommon = false,
109 | hasRare = false,
110 | hasEpic = false,
111 | hasLegendary = false;
112 |
113 | foreach(Card Card in Pack.Cards) {
114 | switch(Card.Rarity) {
115 | case Rarity.COMMON:
116 | _commonAmount++;
117 | hasCommon = true;
118 | break;
119 | case Rarity.RARE:
120 | _rareAmount++;
121 | hasRare = true;
122 | break;
123 | case Rarity.EPIC:
124 | _epicAmount++;
125 | hasEpic = true;
126 | break;
127 | case Rarity.LEGENDARY:
128 | hasLegendary = true;
129 | _legendaryAmount++;
130 | break;
131 | }
132 |
133 | _totalAmount++;
134 | }
135 |
136 | if(hasCommon) {
137 | _commonPacks++;
138 | }
139 |
140 | if(hasRare) {
141 | _rarePacks++;
142 | }
143 |
144 | if(hasEpic) {
145 | _epicPacks++;
146 | }
147 |
148 | if(hasLegendary) {
149 | _legendaryPacks++;
150 | }
151 | }
152 |
153 | void CountStreak(Pack Pack) {
154 | if(Pack.Cards.Any(x => x.Rarity == Rarity.EPIC)) {
155 | _epicCurrStreak = 0;
156 | } else {
157 | _epicCurrStreak++;
158 |
159 | if(_epicCurrStreak > _epicLongStreak) {
160 | _epicLongStreak = _epicCurrStreak;
161 | OnPropertyChanged("EpicStreak");
162 | }
163 | }
164 |
165 | if(Pack.Cards.Any(x => x.Rarity == Rarity.LEGENDARY)) {
166 | _legendaryCurrStreak = 0;
167 | } else {
168 | _legendaryCurrStreak++;
169 |
170 | if(_legendaryCurrStreak > _legendaryLongStreak) {
171 | _legendaryLongStreak = _legendaryCurrStreak;
172 | OnPropertyChanged("LegendaryStreak");
173 | }
174 | }
175 | }
176 |
177 | public event PropertyChangedEventHandler PropertyChanged;
178 | void OnPropertyChanged(string prop) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
179 | }
180 | }
181 |
--------------------------------------------------------------------------------
/PackHistorian/View/TimeConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace PackTracker.View {
8 | class TimeConverter : DateTimeConverter {
9 | protected override string _format { get => "T"; }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/PackHistorian/WindowManager.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 | using System.Windows.Controls;
8 | using PackTracker.Controls;
9 | using PackTracker.Storage;
10 | using PackTracker.View.Cache;
11 |
12 | namespace PackTracker {
13 |
14 | class WindowManager {
15 | string _name;
16 | Window _pityWin, _statisticWin, _historyWin, _logWin, _searchWin, _pityOverlay;
17 |
18 | public WindowManager(string name) {
19 | _name = name;
20 | }
21 |
22 | public void ShowPityWin(History History, PityTimerRepository PityTimers) {
23 | if(_pityWin == null) {
24 | _pityWin = new Controls.PityTimer.PityTimer(History, PityTimers) {
25 | Owner = Hearthstone_Deck_Tracker.Core.MainWindow,
26 | };
27 | _pityWin.Closed += (sender, e) => _pityWin = null;
28 | _pityWin.Loaded += (sender, e) => _pityWin.Title = _name + ": " + _pityWin.Title;
29 |
30 | _pityWin.Show();
31 | }
32 |
33 | _pityWin.Focus();
34 | }
35 |
36 | public void ShowStatisticWin(History History) {
37 | if(_statisticWin == null) {
38 | _statisticWin = new Statistic(History) {
39 | Owner = Hearthstone_Deck_Tracker.Core.MainWindow,
40 | };
41 | _statisticWin.Closed += (sender, e) => _statisticWin = null;
42 | _statisticWin.Loaded += (sender, e) => _statisticWin.Title = _name + ": " + _statisticWin.Title;
43 |
44 | _statisticWin.Show();
45 | }
46 |
47 | _statisticWin.Focus();
48 | }
49 |
50 | public void ShowHistoryWin(History History) {
51 | if(_historyWin == null) {
52 | _historyWin = new Controls.History(History, new HistoryDatePicker(History)) {
53 | Owner = Hearthstone_Deck_Tracker.Core.MainWindow,
54 | };
55 | _historyWin.Closed += (sender, e) => { _historyWin = null; };
56 | _historyWin.Loaded += (sender, e) => _historyWin.Title = _name + ": " + _historyWin.Title;
57 |
58 | _historyWin.Show();
59 | }
60 |
61 | _historyWin.Focus(); ;
62 | }
63 |
64 | public void ShowLogWin(History History) {
65 | if(_logWin == null) {
66 | _logWin = new Log(History) {
67 | Owner = Hearthstone_Deck_Tracker.Core.MainWindow,
68 | };
69 | _logWin.Closed += (sender, e) => _logWin = null;
70 | _logWin.Loaded += (sender, e) => _logWin.Title = _name + ": " + _logWin.Title;
71 |
72 | _logWin.Show();
73 | }
74 |
75 | _logWin.Focus();
76 | }
77 |
78 | public void ShowSearchWin(History History) {
79 | if(_searchWin == null) {
80 | _searchWin = new Search(History) {
81 | Owner = Hearthstone_Deck_Tracker.Core.MainWindow,
82 | };
83 | _searchWin.Closed += (sender, e) => _searchWin = null;
84 | _searchWin.Loaded += (sender, e) => _searchWin.Title = _name + ": " + _searchWin.Title;
85 |
86 | _searchWin.Show();
87 | }
88 |
89 | _searchWin.Focus();
90 | }
91 |
92 | public void ShowSettingsWin(Settings Settings, ISettingsStorage SettingsStorage, Type PreSelection = null) {
93 | Controls.Settings.Settings Win = new Controls.Settings.Settings(Settings) {
94 | Owner = Hearthstone_Deck_Tracker.Core.MainWindow
95 | };
96 | Win.Closed += (sender, e) => SettingsStorage.Store(Settings);
97 | Win.Title = _name + ": " + Win.Title;
98 |
99 | if(PreSelection != null) {
100 | foreach(var Item in Win.lb_tabs.Items) {
101 | if(Item.GetType() == PreSelection) {
102 | Win.lb_tabs.SelectedItem = Item;
103 | break;
104 | }
105 | }
106 | }
107 |
108 | Win.ShowDialog();
109 | }
110 |
111 | public void ShowPityTimerOverlay(History History, PityTimerRepository PityTimers) {
112 | if(_pityOverlay == null) {
113 | _pityOverlay = new Controls.PityTimer.PityTimerOverlay(History, PityTimers);
114 | Hearthstone_Deck_Tracker.Core.MainWindow.Closed += ClosePityTimerOverlay;
115 | _pityOverlay.Closed += (sender, e) => _pityOverlay = null;
116 | }
117 |
118 | _pityOverlay.Show();
119 | }
120 |
121 | private void ClosePityTimerOverlay(object sender, EventArgs e) {
122 | ClosePityTimerOverlay();
123 | }
124 |
125 | public void ClosePityTimerOverlay() {
126 | if(_pityOverlay != null) {
127 | _pityOverlay.Dispatcher.Invoke(() => {
128 | _pityOverlay.Close();
129 | });
130 |
131 | _pityOverlay = null;
132 | Hearthstone_Deck_Tracker.Core.MainWindow.Closed -= ClosePityTimerOverlay;
133 | }
134 | }
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/PackTracker.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.26430.13
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PackTracker", "PackHistorian\PackTracker.csproj", "{3C5023C1-D9B6-4FFA-B3A2-2A8466DE5074}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Debug|x86 = Debug|x86
12 | Release|Any CPU = Release|Any CPU
13 | Release|x86 = Release|x86
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {3C5023C1-D9B6-4FFA-B3A2-2A8466DE5074}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17 | {3C5023C1-D9B6-4FFA-B3A2-2A8466DE5074}.Debug|Any CPU.Build.0 = Debug|Any CPU
18 | {3C5023C1-D9B6-4FFA-B3A2-2A8466DE5074}.Debug|x86.ActiveCfg = Debug|x86
19 | {3C5023C1-D9B6-4FFA-B3A2-2A8466DE5074}.Debug|x86.Build.0 = Debug|x86
20 | {3C5023C1-D9B6-4FFA-B3A2-2A8466DE5074}.Release|Any CPU.ActiveCfg = Release|Any CPU
21 | {3C5023C1-D9B6-4FFA-B3A2-2A8466DE5074}.Release|Any CPU.Build.0 = Release|Any CPU
22 | {3C5023C1-D9B6-4FFA-B3A2-2A8466DE5074}.Release|x86.ActiveCfg = Release|Any CPU
23 | {3C5023C1-D9B6-4FFA-B3A2-2A8466DE5074}.Release|x86.Build.0 = Release|Any CPU
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | EndGlobal
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PackTracker [](https://github.com/mgk82/packtracker/releases)
2 | Plugin for [HearthstoneDeckTracker](https://hsdecktracker.net/)
3 |
4 | Pack Tacker is a small but handy plugin for Hearthstone Deck Tacker that allows you to keep an eye on every pack you open.
5 | This allows you to see how many cards of different rarities have dropped over time and also enables you to estimate when your next Epic or Legendary is coming!
6 |
7 | ## Features [](https://github.com/MGK82/PackTracker/releases/latest) [](https://github.com/MGK82/PackTracker/releases/latest)
8 | - Tracks your pack openings
9 | - **Fully Automatic. No hassle with spreadsheets or any other manual input**
10 | - Data can be represented in
11 | - a date-based (calender-like) history
12 | - a boosterpack-series-based statistic
13 | - a plain chronological text-log
14 | - Supports auto-update
15 | - Counts boosterpack-series-based **Pity Timer** for Epic and Legendary cards
16 | - Represented as bar charts
17 | - Overlay while opening packs (See your pity timers counting live)
18 | - Can search through card names and texts in both, english and the chosen language of your HDT at once
19 | - *HDT must support the translation*
20 | - **All stored locally. No hassle with overloaded webpages on release days**
21 |
22 | [Download](https://github.com/MGK82/PackTracker/releases/latest) and unzip
23 | [How to add plugins to HDT](https://github.com/HearthSim/Hearthstone-Deck-Tracker/wiki/Available-Plugins)
24 |
25 | ## Screenshots
26 |
27 | Pity Timers
28 | 
29 |
30 | Statistic
31 | 
32 |
33 | History
34 | 
35 |
36 | Log
37 | 
38 |
39 | Search
40 | 
41 |
--------------------------------------------------------------------------------
/doc/Screenshots/History.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MGK82/PackTracker/d6ac5706790bc0287798e59b72b42f58667afc99/doc/Screenshots/History.png
--------------------------------------------------------------------------------
/doc/Screenshots/Log.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MGK82/PackTracker/d6ac5706790bc0287798e59b72b42f58667afc99/doc/Screenshots/Log.png
--------------------------------------------------------------------------------
/doc/Screenshots/PityTimer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MGK82/PackTracker/d6ac5706790bc0287798e59b72b42f58667afc99/doc/Screenshots/PityTimer.png
--------------------------------------------------------------------------------
/doc/Screenshots/Search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MGK82/PackTracker/d6ac5706790bc0287798e59b72b42f58667afc99/doc/Screenshots/Search.png
--------------------------------------------------------------------------------
/doc/Screenshots/Statistic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MGK82/PackTracker/d6ac5706790bc0287798e59b72b42f58667afc99/doc/Screenshots/Statistic.png
--------------------------------------------------------------------------------