OSDN Git Service

戦闘結果の計算に誤りがあるときにエラーを表示する
[kancollesniffer/KancolleSniffer.git] / KancolleSniffer / MainForm.cs
1 // Copyright (C) 2013, 2014, 2015 Kazuhiro Fujieda <fujieda@users.osdn.me>\r
2 // \r
3 // Licensed under the Apache License, Version 2.0 (the "License");\r
4 // you may not use this file except in compliance with the License.\r
5 // You may obtain a copy of the License at\r
6 //\r
7 //    http://www.apache.org/licenses/LICENSE-2.0\r
8 //\r
9 // Unless required by applicable law or agreed to in writing, software\r
10 // distributed under the License is distributed on an "AS IS" BASIS,\r
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
12 // See the License for the specific language governing permissions and\r
13 // limitations under the License.\r
14 \r
15 using System;\r
16 using System.Collections.Generic;\r
17 using System.Diagnostics;\r
18 using System.Drawing;\r
19 using System.Globalization;\r
20 using System.IO;\r
21 using System.Linq;\r
22 using System.Runtime.InteropServices;\r
23 using System.Text;\r
24 using System.Text.RegularExpressions;\r
25 using System.Threading.Tasks;\r
26 using System.Windows.Forms;\r
27 using Microsoft.CSharp.RuntimeBinder;\r
28 using static System.Math;\r
29 \r
30 namespace KancolleSniffer\r
31 {\r
32     public partial class MainForm : Form\r
33     {\r
34         private readonly Sniffer _sniffer = new Sniffer();\r
35         private readonly Config _config = new Config();\r
36         private readonly ConfigDialog _configDialog;\r
37         private readonly ProxyManager _proxyManager;\r
38         private int _currentFleet;\r
39         private bool _combinedFleet;\r
40         private readonly Label[] _labelCheckFleets;\r
41         private readonly ShipLabels _shipLabels;\r
42         private readonly ListForm _listForm;\r
43         private readonly NotificationManager _notificationManager;\r
44         private bool _started;\r
45         private string _debugLogFile;\r
46         private IEnumerator<string> _playLog;\r
47 \r
48         private readonly ErrorDialog _errorDialog = new ErrorDialog();\r
49         private bool _missionFinishTimeMode;\r
50         private bool _ndockFinishTimeMode;\r
51         private readonly KancolleDb _kancolleDb = new KancolleDb();\r
52 \r
53         public MainForm()\r
54         {\r
55             InitializeComponent();\r
56             HttpProxy.AfterSessionComplete += HttpProxy_AfterSessionComplete;\r
57             _configDialog = new ConfigDialog(_config, this);\r
58             _labelCheckFleets = new[] {labelCheckFleet1, labelCheckFleet2, labelCheckFleet3, labelCheckFleet4};\r
59 \r
60             // この時点でAutoScaleDimensions == CurrentAutoScaleDimensionsなので、\r
61             // MainForm.Designer.csのAutoScaleDimensionsの6f,12fを使う。\r
62             ShipLabel.ScaleFactor = new SizeF(CurrentAutoScaleDimensions.Width / 6f,\r
63                 CurrentAutoScaleDimensions.Height / 12f);\r
64 \r
65             SetupFleetClick();\r
66             _shipLabels = new ShipLabels();\r
67             _shipLabels.CreateAkashiTimers(panelShipInfo);\r
68             _shipLabels.CreateShipLabels(panelShipInfo, ShowShipOnShipList);\r
69             _shipLabels.CreateCombinedShipLabels(panelCombinedFleet, ShowShipOnShipList);\r
70             _shipLabels.CreateNDockLabels(panelDock, labelNDock_Click);\r
71             panelRepairList.CreateLabels(panelRepairList_Click);\r
72             labelPresetAkashiTimer.BackColor = ShipLabels.ColumnColors[1];\r
73             _listForm = new ListForm(_sniffer, _config) {Owner = this};\r
74             _notificationManager = new NotificationManager(Ring);\r
75             _config.Load();\r
76             _proxyManager = new ProxyManager(_config, this);\r
77             PerformZoom();\r
78             _shipLabels.AdjustAkashiTimers();\r
79             _sniffer.LoadState();\r
80         }\r
81 \r
82         private void HttpProxy_AfterSessionComplete(HttpProxy.Session session)\r
83         {\r
84             Invoke(new Action<HttpProxy.Session>(ProcessRequest), session);\r
85         }\r
86 \r
87         private void ProcessRequest(HttpProxy.Session session)\r
88         {\r
89             var url = session.Request.PathAndQuery;\r
90             if (!url.Contains("kcsapi/"))\r
91                 return;\r
92             var request = session.Request.BodyAsString;\r
93             var response = session.Response.BodyAsString;\r
94             if (response == null || !response.StartsWith("svdata="))\r
95             {\r
96                 WriteDebugLog(url, request, response);\r
97                 return;\r
98             }\r
99             if (_config.KancolleDb.On)\r
100                 _kancolleDb.Send(url, request, response);\r
101             response = UnescapeString(response.Remove(0, "svdata=".Length));\r
102             WriteDebugLog(url, request, response);\r
103             ProcessRequestMain(url, request, response);\r
104         }\r
105 \r
106         private List<string[]> _battleApiLog = new List<string[]>();\r
107 \r
108         private void ProcessRequestMain(string url, string request, string response)\r
109         {\r
110             try\r
111             {\r
112                 UpdateInfo(_sniffer.Sniff(url, request, JsonParser.Parse(response)));\r
113                 SaveBattleApi(url, request, response);\r
114                 if (_sniffer.WrongBattleResult.Length > 0)\r
115                 {\r
116                     if (_errorDialog.ShowDialog(this,\r
117                             "戦闘結果の計算に誤りがあります。",\r
118                             GenerateBattleErrorLog()) == DialogResult.Abort)\r
119                         Application.Exit();\r
120                 }\r
121             }\r
122             catch (RuntimeBinderException e)\r
123             {\r
124                 if (_errorDialog.ShowDialog(this,\r
125                         "艦これに仕様変更があったか、受信内容が壊れています。",\r
126                         GenerateErrorLog(url, request, response, e.ToString())) == DialogResult.Abort)\r
127                     Application.Exit();\r
128             }\r
129             catch (LogIOException e)\r
130             {\r
131                 // ReSharper disable once PossibleNullReferenceException\r
132                 if (_errorDialog.ShowDialog(this, e.Message, e.InnerException.ToString()) == DialogResult.Abort)\r
133                     Application.Exit();\r
134             }\r
135             catch (Exception e)\r
136             {\r
137                 if (_errorDialog.ShowDialog(this, "エラーが発生しました。",\r
138                         GenerateErrorLog(url, request, response, e.ToString())) == DialogResult.Abort)\r
139                     Application.Exit();\r
140             }\r
141         }\r
142 \r
143         private void SaveBattleApi(string url, string request, string response)\r
144         {\r
145             if (_sniffer.Battle.BattleState == BattleState.Day)\r
146                 _battleApiLog = new List<string[]> {new[] {url, request, response}};\r
147             if (_sniffer.Battle.BattleState == BattleState.Night || _sniffer.Battle.BattleState == BattleState.Result)\r
148                 _battleApiLog.Add(new[] {url, request, response});\r
149         }\r
150 \r
151         private string GenerateBattleErrorLog()\r
152         {\r
153             foreach (var logs in _battleApiLog)\r
154                 RemoveSensitiveInformation(ref logs[1], ref logs[2]);\r
155             var version = string.Join(".", Application.ProductVersion.Split('.').Take(2));\r
156             var api = string.Join("\r\n", _battleApiLog.Select(logs => string.Join("\r\n", logs)));\r
157             var status = string.Join(" ",\r
158                 from pair in _sniffer.WrongBattleResult\r
159                 let assumed = pair.Assumed\r
160                 let actual = pair.Actual\r
161                 select $"[{assumed.Fleet}-{assumed.DeckIndex}] {assumed.Id}: {assumed.NowHp}->{actual.NowHp}");\r
162             return $"{DateTime.Now:g} {version}\r\n{api}\r\n{status}";\r
163         }\r
164 \r
165         private string GenerateErrorLog(string url, string request, string response, string exception)\r
166         {\r
167             RemoveSensitiveInformation(ref request, ref response);\r
168             var version = string.Join(".", Application.ProductVersion.Split('.').Take(2));\r
169             return $"{DateTime.Now:g} {version}\r\n{exception}\r\n{url}\r\n{request}\r\n{response}\r\n";\r
170         }\r
171 \r
172         private void RemoveSensitiveInformation(ref string request, ref string response)\r
173         {\r
174             var token = new Regex("&api%5Ftoken=[^&]*|api%5Ftoken=[^&]*&?");\r
175             request = token.Replace(request, "");\r
176             var id = new Regex(@"""api_member_id"":\d+,?|""api_nickname"":[^,]+,""api_nickname_id"":""d+"",?");\r
177             response = id.Replace(response, "");\r
178         }\r
179 \r
180         private void WriteDebugLog(string url, string request, string response)\r
181         {\r
182             if (_debugLogFile != null)\r
183             {\r
184                 File.AppendAllText(_debugLogFile,\r
185                     $"date: {DateTime.Now:g}\nurl: {url}\nrequest: {request}\nresponse: {response ?? "(null)"}\n");\r
186             }\r
187         }\r
188 \r
189 \r
190         private string UnescapeString(string s)\r
191         {\r
192             try\r
193             {\r
194                 var rx = new Regex(@"\\[uU]([0-9A-Fa-f]{4})");\r
195                 return rx.Replace(s,\r
196                     match => ((char)int.Parse(match.Value.Substring(2), NumberStyles.HexNumber)).ToString());\r
197             }\r
198             catch (ArgumentException)\r
199             {\r
200                 return s;\r
201             }\r
202         }\r
203 \r
204         private void UpdateInfo(Sniffer.Update update)\r
205         {\r
206             if (update == Sniffer.Update.Start)\r
207             {\r
208                 labelLogin.Visible = false;\r
209                 labelGuide.Visible = false;\r
210                 _started = true;\r
211                 return;\r
212             }\r
213             if (!_started)\r
214                 return;\r
215             if ((update & Sniffer.Update.Item) != 0)\r
216                 UpdateItemInfo();\r
217             if ((update & Sniffer.Update.Timer) != 0)\r
218                 UpdateTimers();\r
219             if ((update & Sniffer.Update.NDock) != 0)\r
220                 UpdateNDocLabels();\r
221             if ((update & Sniffer.Update.Mission) != 0)\r
222                 UpdateMissionLabels();\r
223             if ((update & Sniffer.Update.QuestList) != 0)\r
224                 UpdateQuestList();\r
225             if ((update & Sniffer.Update.Ship) != 0)\r
226                 UpdateShipInfo();\r
227             if ((update & Sniffer.Update.Battle) != 0)\r
228                 UpdateBattleInfo();\r
229         }\r
230 \r
231         private void MainForm_Load(object sender, EventArgs e)\r
232         {\r
233             RestoreLocation();\r
234             if (_config.HideOnMinimized && WindowState == FormWindowState.Minimized)\r
235                 ShowInTaskbar = false;\r
236             ApplyConfig();\r
237             ApplyDebugLogSetting();\r
238             ApplyLogSetting();\r
239             ApplyProxySetting();\r
240             if (_config.KancolleDb.On)\r
241                 _kancolleDb.Start(_config.KancolleDb.Token);\r
242         }\r
243 \r
244         private void MainForm_FormClosing(object sender, FormClosingEventArgs e)\r
245         {\r
246             if (!_config.ExitSilently)\r
247             {\r
248                 using (var dialog = new ConfirmDialog())\r
249                 {\r
250                     if (dialog.ShowDialog(this) != DialogResult.Yes)\r
251                     {\r
252                         e.Cancel = true;\r
253                         return;\r
254                     }\r
255                 }\r
256             }\r
257             e.Cancel = false;\r
258             _sniffer.FlashLog();\r
259             _config.Location = (WindowState == FormWindowState.Normal ? Bounds : RestoreBounds).Location;\r
260             _config.Save();\r
261             _proxyManager.Shutdown();\r
262             _kancolleDb.Stop();\r
263         }\r
264 \r
265         private void MainForm_Resize(object sender, EventArgs e)\r
266         {\r
267             ShowInTaskbar = !(_config.HideOnMinimized && WindowState == FormWindowState.Minimized);\r
268         }\r
269 \r
270         private void notifyIconMain_MouseDoubleClick(object sender, MouseEventArgs e)\r
271         {\r
272             NotifyIconOpenToolStripMenuItem_Click(sender, e);\r
273         }\r
274 \r
275         private void NotifyIconOpenToolStripMenuItem_Click(object sender, EventArgs e)\r
276         {\r
277             ShowInTaskbar = true;\r
278             WindowState = FormWindowState.Normal;\r
279             TopMost = _config.TopMost; // 最前面に表示されなくなることがあるのを回避する\r
280             Activate();\r
281         }\r
282 \r
283         private void ExitToolStripMenuItem_Click(object sender, EventArgs e)\r
284         {\r
285             Close();\r
286         }\r
287 \r
288         private void ConfigToolStripMenuItem_Click(object sender, EventArgs e)\r
289         {\r
290             if (_configDialog.ShowDialog(this) == DialogResult.OK)\r
291                 ApplyConfig();\r
292         }\r
293 \r
294         private void PerformZoom()\r
295         {\r
296             if (_config.Zoom == 100)\r
297                 return;\r
298             var prev = CurrentAutoScaleDimensions;\r
299             foreach (var control in new Control[] {this, _listForm, labelLogin, labelGuide})\r
300                 control.Font = new Font(control.Font.FontFamily, control.Font.Size * _config.Zoom / 100);\r
301             ShipLabel.LatinFont = new Font("Tahoma", 8f * _config.Zoom / 100);\r
302             var cur = CurrentAutoScaleDimensions;\r
303             ShipLabel.ScaleFactor = new SizeF(ShipLabel.ScaleFactor.Width * cur.Width / prev.Width,\r
304                 ShipLabel.ScaleFactor.Height * cur.Height / prev.Height);\r
305         }\r
306 \r
307         private void RestoreLocation()\r
308         {\r
309             if (_config.Location.X == int.MinValue)\r
310                 return;\r
311             if (IsTitleBarOnAnyScreen(_config.Location))\r
312                 Location = _config.Location;\r
313         }\r
314 \r
315         private void ApplyConfig()\r
316         {\r
317             _listForm.TopMost = TopMost = _config.TopMost;\r
318             _sniffer.Item.MarginShips = _config.MarginShips;\r
319             _sniffer.Item.MarginEquips = _config.MarginEquips;\r
320             _sniffer.Achievement.ResetHours = _config.ResetHours;\r
321             labelAkashiRepair.Visible = labelAkashiRepairTimer.Visible =\r
322                 labelPresetAkashiTimer.Visible = _config.UsePresetAkashi;\r
323             if (_config.KancolleDb.On)\r
324                 _kancolleDb.Start(_config.KancolleDb.Token);\r
325         }\r
326 \r
327         public void ApplyDebugLogSetting()\r
328         {\r
329             _debugLogFile = _config.DebugLogging ? _config.DebugLogFile : null;\r
330         }\r
331 \r
332         public bool ApplyProxySetting()\r
333         {\r
334             return _proxyManager.ApplyConfig();\r
335         }\r
336 \r
337         public void ApplyLogSetting()\r
338         {\r
339             LogServer.OutputDir = _config.Log.OutputDir;\r
340             LogServer.MaterialHistory = _sniffer.Material.MaterialHistory;\r
341             _sniffer.EnableLog(_config.Log.On ? LogType.All : LogType.None);\r
342             _sniffer.MaterialLogInterval = _config.Log.MaterialLogInterval;\r
343             _sniffer.LogOutputDir = _config.Log.OutputDir;\r
344         }\r
345 \r
346         public static bool IsTitleBarOnAnyScreen(Point location)\r
347         {\r
348             var rect = new Rectangle(\r
349                 new Point(location.X + SystemInformation.IconSize.Width + SystemInformation.HorizontalFocusThickness,\r
350                     location.Y + SystemInformation.CaptionHeight) , new Size(60, 1));\r
351             return Screen.AllScreens.Any(screen => screen.WorkingArea.Contains(rect));\r
352         }\r
353 \r
354         private void timerMain_Tick(object sender, EventArgs e)\r
355         {\r
356             if (_started)\r
357             {\r
358                 try\r
359                 {\r
360                     UpdateTimers();\r
361                 }\r
362                 catch (Exception ex)\r
363                 {\r
364                     if (_errorDialog.ShowDialog(this, "エラーが発生しました。", ex.ToString()) == DialogResult.Abort)\r
365                         Application.Exit();\r
366                 }\r
367             }\r
368             if (_playLog == null || _configDialog.Visible)\r
369             {\r
370                 labelPlayLog.Visible = false;\r
371                 return;\r
372             }\r
373             PlayLog();\r
374         }\r
375 \r
376         public void SetPlayLog(string file)\r
377         {\r
378             _playLog = File.ReadLines(file).GetEnumerator();\r
379         }\r
380 \r
381         private void PlayLog()\r
382         {\r
383             var lines = new List<string>();\r
384             foreach (var s in new[] {"url: ", "request: ", "response: "})\r
385             {\r
386                 do\r
387                 {\r
388                     if (!_playLog.MoveNext() || _playLog.Current == null)\r
389                     {\r
390                         labelPlayLog.Visible = false;\r
391                         return;\r
392                     }\r
393                 } while (!_playLog.Current.StartsWith(s));\r
394                 lines.Add(_playLog.Current.Substring(s.Length));\r
395             }\r
396             labelPlayLog.Visible = !labelPlayLog.Visible;\r
397             ProcessRequestMain(lines[0], lines[1], lines[2]);\r
398         }\r
399 \r
400         private void ShowShipOnShipList(object sender, EventArgs ev)\r
401         {\r
402             if (!_listForm.Visible)\r
403                 return;\r
404             var idx = (int)((Control)sender).Tag;\r
405             var statuses = _sniffer.GetShipStatuses(_currentFleet);\r
406             if (statuses.Length <= idx)\r
407                 return;\r
408             _listForm.ShowShip(statuses[idx].Id);\r
409         }\r
410 \r
411         private void UpdateItemInfo()\r
412         {\r
413             UpdateNumOfShips();\r
414             UpdateNumOfEquips();\r
415             labelNumOfBuckets.Text = _sniffer.Material.MaterialHistory[(int)Material.Bucket].Now.ToString("D");\r
416             UpdateBucketHistory();\r
417             var ac = _sniffer.Achievement.Value;\r
418             if (ac >= 10000)\r
419                 ac = 9999;\r
420             labelAchievement.Text = ac >= 1000 ? ((int)ac).ToString("D") : ac.ToString("F1");\r
421             toolTipAchievement.SetToolTip(labelAchievement,\r
422                 "今月 " + _sniffer.Achievement.ValueOfMonth.ToString("F1") + "\n" +\r
423                 "EO " + _sniffer.ExMap.Achievement);\r
424             UpdateMaterialHistry();\r
425             if (_listForm.Visible)\r
426                 _listForm.UpdateList();\r
427         }\r
428 \r
429         private void UpdateNumOfShips()\r
430         {\r
431             var item = _sniffer.Item;\r
432             labelNumOfShips.Text = $"{item.NowShips:D}/{item.MaxShips:D}";\r
433             labelNumOfShips.ForeColor = item.TooManyShips ? CUDColor.Red : Color.Black;\r
434             if (item.RingShips)\r
435             {\r
436                 var message = $"残り{_sniffer.Item.MaxShips - _sniffer.Item.NowShips:D}隻";\r
437                 _notificationManager.Enqueue("艦娘数超過", message);\r
438                 item.RingShips = false;\r
439             }\r
440         }\r
441 \r
442         private void UpdateNumOfEquips()\r
443         {\r
444             var item = _sniffer.Item;\r
445             labelNumOfEquips.Text = $"{item.NowEquips:D}/{item.MaxEquips:D}";\r
446             labelNumOfEquips.ForeColor = item.TooManyEquips ? CUDColor.Red : Color.Black;\r
447             if (item.RingEquips)\r
448             {\r
449                 var message = $"残り{_sniffer.Item.MaxEquips - _sniffer.Item.NowEquips:D}個";\r
450                 _notificationManager.Enqueue("装備数超過", message);\r
451                 item.RingEquips = false;\r
452             }\r
453         }\r
454 \r
455         private void UpdateBucketHistory()\r
456         {\r
457             var count = _sniffer.Material.MaterialHistory[(int)Material.Bucket];\r
458             var day = CutOverflow(count.Now - count.BegOfDay, 999);\r
459             var week = CutOverflow(count.Now - count.BegOfWeek, 999);\r
460             labelBucketHistory.Text = $"{day:+#;-#;±0} 今日\n{week:+#;-#;±0} 今週";\r
461         }\r
462 \r
463         private void UpdateMaterialHistry()\r
464         {\r
465             var labels = new[] {labelFuelHistory, labelBulletHistory, labelSteelHistory, labelBouxiteHistory};\r
466             var text = new[] {"燃料", "弾薬", "鋼材", "ボーキ"};\r
467             for (var i = 0; i < labels.Length; i++)\r
468             {\r
469                 var count = _sniffer.Material.MaterialHistory[i];\r
470                 var port = CutOverflow(count.Now - _sniffer.Material.PrevPort[i], 99999);\r
471                 var day = CutOverflow(count.Now - count.BegOfDay, 99999);\r
472                 var week = CutOverflow(count.Now - count.BegOfWeek, 99999);\r
473                 labels[i].Text = $"{text[i]}\n{port:+#;-#;±0}\n{day:+#;-#;±0}\n{week:+#;-#;±0}";\r
474             }\r
475         }\r
476 \r
477         private int CutOverflow(int value, int limit)\r
478         {\r
479             if (value > limit)\r
480                 return limit;\r
481             if (value < -limit)\r
482                 return -limit;\r
483             return value;\r
484         }\r
485 \r
486         private void UpdateShipInfo()\r
487         {\r
488             UpdatePanelShipInfo();\r
489             NotifyDamagedShip();\r
490             UpdateChargeInfo();\r
491             UpdateRepairList();\r
492             if (_listForm.Visible)\r
493                 _listForm.UpdateList();\r
494         }\r
495 \r
496         private void UpdatePanelShipInfo()\r
497         {\r
498             var statuses = _sniffer.GetShipStatuses(_currentFleet);\r
499             _shipLabels.SetShipLabels(statuses);\r
500             if (_sniffer.CombinedFleetType == 0)\r
501                 _combinedFleet = false;\r
502             labelFleet1.Text = _combinedFleet ? "連合" : "第一";\r
503             panelCombinedFleet.Visible = _combinedFleet;\r
504             if (_combinedFleet)\r
505                 _shipLabels.SetCombinedShipLabels(_sniffer.GetShipStatuses(0), _sniffer.GetShipStatuses(1));\r
506             UpdateAkashiTimer();\r
507             UpdateFighterPower(_combinedFleet);\r
508             UpdateLoS();\r
509             UpdateCondTimers();\r
510         }\r
511 \r
512         private void NotifyDamagedShip()\r
513         {\r
514             if (_sniffer.BadlyDamagedShips.Any())\r
515                 _notificationManager.Enqueue("大破警告", string.Join(" ", _sniffer.BadlyDamagedShips));\r
516         }\r
517 \r
518         private void NotifyAkashiTimer()\r
519         {\r
520             var akashi = _sniffer.AkashiTimer;\r
521             var msgs = akashi.GetNotice();\r
522             if (msgs.Length == 0)\r
523                 return;\r
524             if (!akashi.CheckReparing() && !(akashi.CheckPresetReparing() && _config.UsePresetAkashi))\r
525                 return;\r
526             if (msgs[0].Proceeded == "20分経過しました。")\r
527             {\r
528                 _notificationManager.Enqueue("泊地修理20分経過", msgs[0].Proceeded);\r
529                 msgs[0].Proceeded = "";\r
530                 // 修理完了がいるかもしれないので続ける\r
531             }\r
532             for (var i = 0; i < ShipInfo.FleetCount; i++)\r
533             {\r
534                 if (msgs[i].Proceeded != "")\r
535                     _notificationManager.Enqueue("泊地修理進行", i, msgs[i].Proceeded);\r
536                 if (msgs[i].Completed != "")\r
537                     _notificationManager.Enqueue("泊地修理完了", i, msgs[i].Completed);\r
538             }\r
539         }\r
540 \r
541         public void UpdateFighterPower(bool combined)\r
542         {\r
543             var fp = combined\r
544                 ? _sniffer.GetFighterPower(0).Zip(_sniffer.GetFighterPower(1), (a, b) => a + b).ToArray()\r
545                 : _sniffer.GetFighterPower(_currentFleet);\r
546             labelFighterPower.Text = fp[0].ToString("D");\r
547             var cr = combined\r
548                 ? _sniffer.GetContactTriggerRate(0) + _sniffer.GetContactTriggerRate(1)\r
549                 : _sniffer.GetContactTriggerRate(_currentFleet);\r
550             var text = "制空: " + (fp[0] == fp[1] ? $"{fp[0]}" : $"{fp[0]}~{fp[1]}") +\r
551                        $" 触接: {cr * 100:f1}";\r
552             toolTipFighterPower.SetToolTip(labelFighterPower, text);\r
553             toolTipFighterPower.SetToolTip(labelFighterPowerCaption, text);\r
554         }\r
555 \r
556         private void UpdateLoS()\r
557         {\r
558             labelLoS.Text = RoundDown(_sniffer.GetFleetLineOfSights(_currentFleet, 1)).ToString("F1");\r
559             var text = $"係数3: {RoundDown(_sniffer.GetFleetLineOfSights(_currentFleet, 3)):F1}\r\n" +\r
560                        $"係数4: {RoundDown(_sniffer.GetFleetLineOfSights(_currentFleet, 4)):F1}";\r
561             toolTipLoS.SetToolTip(labelLoS, text);\r
562             toolTipLoS.SetToolTip(labelLoSCaption, text);\r
563         }\r
564 \r
565         private double RoundDown(double number)\r
566         {\r
567             return Floor(number * 10) / 10.0;\r
568         }\r
569 \r
570         private void UpdateBattleInfo()\r
571         {\r
572             ResetBattleInfo();\r
573             if (_sniffer.Battle.BattleState == BattleState.None)\r
574                 return;\r
575             panelBattleInfo.BringToFront();\r
576             var battle = _sniffer.Battle;\r
577             labelFormation.Text = battle.Formation;\r
578             UpdateBattleFighterPower();\r
579             if (_config.AlwaysShowResultRank)\r
580                 ShowResultRank();\r
581             if (_sniffer.Battle.BattleState == BattleState.Day)\r
582                 _listForm.UpdateAirBattleResult();\r
583         }\r
584 \r
585         private void ResetBattleInfo()\r
586         {\r
587             labelFormation.Text = "";\r
588             labelEnemyFighterPower.Text = "";\r
589             labelFighterPower.ForeColor = DefaultForeColor;\r
590             labelResultRank.Text = "判定";\r
591             panelBattleInfo.Visible = _sniffer.Battle.BattleState != BattleState.None;\r
592         }\r
593 \r
594         private void UpdateBattleFighterPower()\r
595         {\r
596             var battle = _sniffer.Battle;\r
597             var power = battle.EnemyFighterPower;\r
598             labelEnemyFighterPower.Text = power.AirCombat + power.UnknownMark;\r
599             if (power.AirCombat != power.Interception)\r
600             {\r
601                 var text = "防空: " + power.Interception + power.UnknownMark;\r
602                 toolTipFighterPower.SetToolTip(labelEnemyFighterPower, text);\r
603                 toolTipFighterPower.SetToolTip(labelEnemyFighterPowerCaption, text);\r
604             }\r
605             UpdateFighterPower(_sniffer.CombinedFleetType > 0 && battle.EnemyIsCombined);\r
606             labelFighterPower.ForeColor = new[]\r
607                 {DefaultForeColor, DefaultForeColor, CUDColor.Blue, CUDColor.Green, CUDColor.Orange, CUDColor.Red}[\r
608                 battle.AirControlLevel + 1];\r
609         }\r
610 \r
611         private void ShowResultRank()\r
612         {\r
613             var result = new[] {"完全S", "勝利S", "勝利A", "勝利B", "敗北C", "敗北D", "敗北E"};\r
614             labelResultRank.Text = result[(int)_sniffer.Battle.ResultRank];\r
615         }\r
616 \r
617         private void labelResultRank_Click(object sender, EventArgs e)\r
618         {\r
619             ShowResultRank();\r
620         }\r
621 \r
622         private void UpdateChargeInfo()\r
623         {\r
624             var fuelSq = new[] {labelFuelSq1, labelFuelSq2, labelFuelSq3, labelFuelSq4};\r
625             var bullSq = new[] {labelBullSq1, labelBullSq2, labelBullSq3, labelBullSq4};\r
626 \r
627             for (var i = 0; i < fuelSq.Length; i++)\r
628             {\r
629                 var stat = _sniffer.ChargeStatuses[i];\r
630                 fuelSq[i].ImageIndex = stat.Fuel;\r
631                 bullSq[i].ImageIndex = stat.Bull;\r
632             }\r
633         }\r
634 \r
635         private void UpdateNDocLabels()\r
636         {\r
637             _shipLabels.SetNDockLabels(_sniffer.NDock);\r
638         }\r
639 \r
640 \r
641         private void labelNDock_Click(object sender, EventArgs e)\r
642         {\r
643             _ndockFinishTimeMode = !_ndockFinishTimeMode;\r
644             UpdateTimers();\r
645         }\r
646 \r
647         private void UpdateMissionLabels()\r
648         {\r
649             foreach (var entry in\r
650                 new[] {labelMissionName1, labelMissionName2, labelMissionName3}.Zip(_sniffer.Missions,\r
651                     (label, mission) => new {label, mission.Name}))\r
652                 entry.label.Text = entry.Name;\r
653         }\r
654 \r
655         private void labelMission_Click(object sender, EventArgs e)\r
656         {\r
657             _missionFinishTimeMode = !_missionFinishTimeMode;\r
658             UpdateTimers();\r
659         }\r
660 \r
661         private void UpdateTimers()\r
662         {\r
663             foreach (var entry in\r
664                 new[] {labelMission1, labelMission2, labelMission3}.Zip(_sniffer.Missions,\r
665                     (label, mission) => new {label, mission.Name, mission.Timer}))\r
666             {\r
667                 entry.Timer.Update();\r
668                 SetTimerColor(entry.label, entry.Timer);\r
669                 entry.label.Text = entry.Timer.ToString(_missionFinishTimeMode);\r
670                 if (!entry.Timer.NeedRing)\r
671                     continue;\r
672                 _notificationManager.Enqueue("遠征終了", entry.Name);\r
673                 entry.Timer.NeedRing = false;\r
674             }\r
675             for (var i = 0; i < _sniffer.NDock.Length; i++)\r
676             {\r
677                 var entry = _sniffer.NDock[i];\r
678                 entry.Timer.Update();\r
679                 _shipLabels.SetNDockTimer(i, entry.Timer, _ndockFinishTimeMode);\r
680                 if (!entry.Timer.NeedRing)\r
681                     continue;\r
682                 _notificationManager.Enqueue("入渠終了", entry.Name);\r
683                 entry.Timer.NeedRing = false;\r
684             }\r
685             var kdock = new[] {labelConstruct1, labelConstruct2, labelConstruct3, labelConstruct4};\r
686             for (var i = 0; i < kdock.Length; i++)\r
687             {\r
688                 var timer = _sniffer.KDock[i];\r
689                 timer.Update();\r
690                 SetTimerColor(kdock[i], timer);\r
691 \r
692                 kdock[i].Text = timer.EndTime == DateTime.MinValue ? "" : timer.Rest.ToString(@"hh\:mm\:ss");\r
693                 if (!timer.NeedRing)\r
694                     continue;\r
695                 _notificationManager.Enqueue("建造完了", $"第{i + 1:D}ドック");\r
696                 timer.NeedRing = false;\r
697             }\r
698             UpdateCondTimers();\r
699             UpdateAkashiTimer();\r
700         }\r
701 \r
702         private void SetTimerColor(Label label, RingTimer timer)\r
703         {\r
704             label.ForeColor = timer.IsFinished ? CUDColor.Red : Color.Black;\r
705         }\r
706 \r
707         private void UpdateCondTimers()\r
708         {\r
709             DateTime timer;\r
710             if (_combinedFleet)\r
711             {\r
712                 var timer1 = _sniffer.GetConditionTimer(0);\r
713                 var timer2 = _sniffer.GetConditionTimer(1);\r
714                 timer = timer2 > timer1 ? timer2 : timer1;\r
715             }\r
716             else\r
717             {\r
718                 timer = _sniffer.GetConditionTimer(_currentFleet);\r
719             }\r
720             var now = DateTime.Now;\r
721             if (timer == DateTime.MinValue)\r
722             {\r
723                 labelCondTimerTitle.Text = "";\r
724                 labelCondTimer.Text = "";\r
725                 return;\r
726             }\r
727             var span = TimeSpan.FromSeconds(Ceiling((timer - now).TotalSeconds));\r
728             if (span >= TimeSpan.FromMinutes(9))\r
729             {\r
730                 labelCondTimerTitle.Text = "cond40まで";\r
731                 labelCondTimer.Text = (span - TimeSpan.FromMinutes(9)).ToString(@"mm\:ss");\r
732                 labelCondTimer.ForeColor = DefaultForeColor;\r
733             }\r
734             else\r
735             {\r
736                 labelCondTimerTitle.Text = "cond49まで";\r
737                 labelCondTimer.Text = (span >= TimeSpan.Zero ? span : TimeSpan.Zero).ToString(@"mm\:ss");\r
738                 labelCondTimer.ForeColor = span <= TimeSpan.Zero ? CUDColor.Red : DefaultForeColor;\r
739             }\r
740             var notice = _sniffer.GetConditionNotice();\r
741             if (notice == null)\r
742                 return;\r
743             for (var i = 0; i < ShipInfo.FleetCount; i++)\r
744             {\r
745                 if (!_config.NotifyConditions.Contains(notice[i]))\r
746                     return;\r
747                 _notificationManager.Enqueue("疲労回復" + notice[i], i, "cond" + notice[i]);\r
748             }\r
749         }\r
750 \r
751         private void UpdateAkashiTimer()\r
752         {\r
753             if (_config.UsePresetAkashi)\r
754                 UpdatePresetAkashiTimer();\r
755             _shipLabels.SetAkashiTimer(_sniffer.GetShipStatuses(_currentFleet),\r
756                 _sniffer.AkashiTimer.GetTimers(_currentFleet));\r
757             NotifyAkashiTimer();\r
758         }\r
759 \r
760         private void UpdatePresetAkashiTimer()\r
761         {\r
762             var akashi = _sniffer.AkashiTimer;\r
763             var span = akashi.PresetDeckTimer;\r
764             var color = span == TimeSpan.Zero && akashi.CheckPresetReparing() ? CUDColor.Red : DefaultForeColor;\r
765             var text = span == TimeSpan.MinValue ? "" : span.ToString(@"mm\:ss");\r
766             labelAkashiRepairTimer.ForeColor = color;\r
767             labelAkashiRepairTimer.Text = text;\r
768             if (akashi.CheckPresetReparing() && !akashi.CheckReparing(_currentFleet))\r
769             {\r
770                 labelPresetAkashiTimer.ForeColor = color;\r
771                 labelPresetAkashiTimer.Text = text;\r
772             }\r
773             else\r
774             {\r
775                 labelPresetAkashiTimer.ForeColor = DefaultForeColor;\r
776                 labelPresetAkashiTimer.Text = "";\r
777             }\r
778         }\r
779 \r
780         private void UpdateRepairList()\r
781         {\r
782             panelRepairList.SetRepairList(_sniffer.RepairList);\r
783         }\r
784 \r
785         private void UpdateQuestList()\r
786         {\r
787             var category = new[]\r
788             {\r
789                 labelQuestColor1, labelQuestColor2, labelQuestColor3, labelQuestColor4, labelQuestColor5,\r
790                 labelQuestColor6\r
791             };\r
792             var name = new[] {labelQuest1, labelQuest2, labelQuest3, labelQuest4, labelQuest5, labelQuest6};\r
793             var progress = new[]\r
794                 {labelProgress1, labelProgress2, labelProgress3, labelProgress4, labelProgress5, labelProgress6};\r
795             var quests = _sniffer.Quests;\r
796             for (var i = 0; i < name.Length; i++)\r
797             {\r
798                 if (i < quests.Length)\r
799                 {\r
800                     category[i].BackColor = quests[i].Color;\r
801                     name[i].Text = quests[i].Name;\r
802                     progress[i].Text = $"{quests[i].Progress:D}%";\r
803                 }\r
804                 else\r
805                 {\r
806                     category[i].BackColor = DefaultBackColor;\r
807                     name[i].Text = progress[i].Text = "";\r
808                 }\r
809             }\r
810         }\r
811 \r
812         private void Ring(string balloonTitle, string balloonMessage, string name)\r
813         {\r
814             if (_config.FlashWindow && (_config.Notifications[name] & NotificationType.FlashWindow) != 0)\r
815                 Win32API.FlashWindow(Handle);\r
816             if (_config.ShowBaloonTip && (_config.Notifications[name] & NotificationType.ShowBaloonTip) != 0)\r
817                 notifyIconMain.ShowBalloonTip(20000, balloonTitle, balloonMessage, ToolTipIcon.Info);\r
818             if (_config.PlaySound && (_config.Notifications[name] & NotificationType.PlaySound) != 0)\r
819                 PlaySound(_config.Sounds[name], _config.Sounds.Volume);\r
820             if (_config.Pushbullet.On && (_config.Notifications[name] & NotificationType.Pushbullet) != 0)\r
821             {\r
822                 Task.Run(() =>\r
823                 {\r
824                     PushBullet.PushNote(_config.Pushbullet.Token, balloonTitle, balloonMessage);\r
825                 });\r
826             }\r
827         }\r
828 \r
829         [DllImport("winmm.dll")]\r
830         private static extern int mciSendString(String command,\r
831             StringBuilder buffer, int bufferSize, IntPtr hwndCallback);\r
832 \r
833 // ReSharper disable InconsistentNaming\r
834         private const int MM_MCINOTIFY = 0x3B9;\r
835 \r
836         private const int MCI_NOTIFY_SUCCESSFUL = 1;\r
837 // ReSharper restore InconsistentNaming\r
838 \r
839         public void PlaySound(string file, int volume)\r
840         {\r
841             if (!File.Exists(file))\r
842                 return;\r
843             mciSendString("close sound", null, 0, IntPtr.Zero);\r
844             if (mciSendString("open \"" + file + "\" type mpegvideo alias sound", null, 0, IntPtr.Zero) != 0)\r
845                 return;\r
846             mciSendString("setaudio sound volume to " + volume * 10, null, 0, IntPtr.Zero);\r
847             mciSendString("play sound notify", null, 0, Handle);\r
848         }\r
849 \r
850         protected override void WndProc(ref Message m)\r
851         {\r
852             if (m.Msg == MM_MCINOTIFY && (int)m.WParam == MCI_NOTIFY_SUCCESSFUL)\r
853                 mciSendString("close sound", null, 0, IntPtr.Zero);\r
854             base.WndProc(ref m);\r
855         }\r
856 \r
857         private void SetupFleetClick()\r
858         {\r
859             var labels = new[]\r
860             {\r
861                 new[] {labelFleet1, labelFleet2, labelFleet3, labelFleet4},\r
862                 new[] {labelFuelSq1, labelFuelSq2, labelFuelSq3, labelFuelSq4},\r
863                 new[] {labelBullSq1, labelBullSq2, labelBullSq3, labelBullSq4}\r
864             };\r
865             foreach (var a in labels)\r
866             {\r
867                 for (var fleet = 0; fleet < labels[0].Length; fleet++)\r
868                 {\r
869                     a[fleet].Tag = fleet;\r
870                     a[fleet].Click += labelFleet_Click;\r
871                 }\r
872             }\r
873         }\r
874 \r
875         private void labelFleet_Click(object sender, EventArgs e)\r
876         {\r
877             if (!_started)\r
878                 return;\r
879             var fleet = (int)((Label)sender).Tag;\r
880             if (_currentFleet == fleet)\r
881             {\r
882                 if (fleet > 0)\r
883                     return;\r
884                 _combinedFleet = _sniffer.CombinedFleetType > 0 && !_combinedFleet;\r
885                 UpdatePanelShipInfo();\r
886                 return;\r
887             }\r
888             _combinedFleet = false;\r
889             _currentFleet = fleet;\r
890             foreach (var label in _labelCheckFleets)\r
891                 label.Visible = false;\r
892             _labelCheckFleets[fleet].Visible = true;\r
893             UpdatePanelShipInfo();\r
894         }\r
895 \r
896         private void labelFleet1_MouseHover(object sender, EventArgs e)\r
897         {\r
898             labelFleet1.Text = _currentFleet == 0 && _sniffer.CombinedFleetType > 0 && !_combinedFleet ? "連合" : "第一";\r
899         }\r
900 \r
901         private void labelFleet1_MouseLeave(object sender, EventArgs e)\r
902         {\r
903             labelFleet1.Text = _combinedFleet ? "連合" : "第一";\r
904         }\r
905 \r
906         private void labelBucketHistoryButton_Click(object sender, EventArgs e)\r
907         {\r
908             if (labelBucketHistory.Visible)\r
909             {\r
910                 labelBucketHistory.Visible = false;\r
911                 labelBucketHistoryButton.BackColor = DefaultBackColor;\r
912             }\r
913             else\r
914             {\r
915                 labelBucketHistory.Visible = true;\r
916                 labelBucketHistory.BringToFront();\r
917                 labelBucketHistoryButton.BackColor = SystemColors.ActiveCaption;\r
918             }\r
919         }\r
920 \r
921         private void labelBucketHistory_Click(object sender, EventArgs e)\r
922         {\r
923             labelBucketHistory.Visible = false;\r
924             labelBucketHistoryButton.BackColor = DefaultBackColor;\r
925         }\r
926 \r
927         private void labelMaterialHistoryButton_Click(object sender, EventArgs e)\r
928         {\r
929             if (panelMaterialHistory.Visible)\r
930             {\r
931                 panelMaterialHistory.Visible = false;\r
932                 labelMaterialHistoryButton.BackColor = DefaultBackColor;\r
933             }\r
934             else\r
935             {\r
936                 panelMaterialHistory.Visible = true;\r
937                 panelMaterialHistory.BringToFront();\r
938                 labelMaterialHistoryButton.BackColor = SystemColors.ActiveCaption;\r
939             }\r
940         }\r
941 \r
942         private void panelMaterialHistory_Click(object sender, EventArgs e)\r
943         {\r
944             panelMaterialHistory.Visible = false;\r
945             labelMaterialHistoryButton.BackColor = DefaultBackColor;\r
946         }\r
947 \r
948         public void ResetAchievemnt()\r
949         {\r
950             _sniffer.Achievement.Reset();\r
951             UpdateItemInfo();\r
952         }\r
953 \r
954         private void labelRepairListButton_Click(object sender, EventArgs e)\r
955         {\r
956             if (panelRepairList.Visible)\r
957             {\r
958                 panelRepairList.Visible = false;\r
959                 labelRepairListButton.BackColor = DefaultBackColor;\r
960             }\r
961             else\r
962             {\r
963                 panelRepairList.Visible = true;\r
964                 panelRepairList.BringToFront();\r
965                 labelRepairListButton.BackColor = SystemColors.ActiveCaption;\r
966             }\r
967         }\r
968 \r
969         private void panelRepairList_Click(object sender, EventArgs e)\r
970         {\r
971             panelRepairList.Visible = false;\r
972             labelRepairListButton.BackColor = DefaultBackColor;\r
973         }\r
974 \r
975         private void ShipListToolStripMenuItem_Click(object sender, EventArgs e)\r
976         {\r
977             _listForm.UpdateList();\r
978             _listForm.Show();\r
979             if (_listForm.WindowState == FormWindowState.Minimized)\r
980                 _listForm.WindowState = FormWindowState.Normal;\r
981             _listForm.Activate();\r
982         }\r
983 \r
984         private void LogToolStripMenuItem_Click(object sender, EventArgs e)\r
985         {\r
986             Process.Start("http://localhost:" + _config.Proxy.Listen + "/");\r
987         }\r
988 \r
989         private void CaptureToolStripMenuItem_Click(object sender, EventArgs e)\r
990         {\r
991             try\r
992             {\r
993                 var proc = new ProcessStartInfo("BurageSnap.exe") {WorkingDirectory = "Capture"};\r
994                 Process.Start(proc);\r
995             }\r
996             catch (FileNotFoundException)\r
997             {\r
998             }\r
999         }\r
1000     }\r
1001 }