OSDN Git Service

制空値の計算で値の範囲を配列で表すのをやめる
[kancollesniffer/KancolleSniffer.git] / KancolleSniffer / View / BattleResultPanel.cs
1 // Copyright (C) 2017 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.Drawing;\r
18 using System.Linq;\r
19 using System.Text.RegularExpressions;\r
20 using System.Windows.Forms;\r
21 using KancolleSniffer.Model;\r
22 using KancolleSniffer.Util;\r
23 using static System.Math;\r
24 \r
25 namespace KancolleSniffer.View\r
26 {\r
27     public class BattleResultPanel : PanelWithToolTip\r
28     {\r
29         private readonly List<ShipLabels> _friendLabels = new List<ShipLabels>();\r
30         private readonly List<ShipLabels> _enemyLabels = new List<ShipLabels>();\r
31         private readonly List<ShipLabel.Hp> _hpLabels = new List<ShipLabel.Hp>();\r
32         private Label _phaseLabel, _rankLabel, _supportLabel, _cellLabel;\r
33         private readonly InformationPanel _information;\r
34         private CellInfo _cellInfo;\r
35         private readonly BattleData _data = new BattleData();\r
36 \r
37         public Spoiler Spoilers { get; set; }\r
38 \r
39         private class BattleData\r
40         {\r
41             private Result _day;\r
42             private Result _night;\r
43             public int[] Formation;\r
44             public Range FighterPower;\r
45             public EnemyFighterPower EnemyFighterPower;\r
46             public int AirControlLevel;\r
47             public int SupportType;\r
48             public bool HaveDay => _day != null;\r
49             public bool HaveNight => _night != null;\r
50 \r
51             public class Result\r
52             {\r
53                 public BattleInfo.BattleResult Damage;\r
54                 public BattleResultRank Rank;\r
55             }\r
56 \r
57             public Result GetResult(BattleState state)\r
58             {\r
59                 switch (state)\r
60                 {\r
61                     case BattleState.Day:\r
62                         return _day;\r
63                     case BattleState.SpNight:\r
64                     case BattleState.Night:\r
65                         return _night;\r
66                     default:\r
67                         return _day;\r
68                 }\r
69             }\r
70 \r
71             public void SetData(BattleInfo battle)\r
72             {\r
73                 switch (battle.BattleState)\r
74                 {\r
75                     case BattleState.Day:\r
76                         _day = new Result {Damage = battle.Result, Rank = battle.ResultRank};\r
77                         _night = null;\r
78                         break;\r
79                     case BattleState.SpNight:\r
80                         _day = null;\r
81                         goto case BattleState.Night;\r
82                     case BattleState.Night:\r
83                         _night = new Result {Damage = battle.Result, Rank = battle.ResultRank};\r
84                         break;\r
85                 }\r
86                 if (battle.BattleState != BattleState.Night)\r
87                     SetNonStateData(battle);\r
88             }\r
89 \r
90             private void SetNonStateData(BattleInfo battle)\r
91             {\r
92                 Formation = battle.Formation;\r
93                 FighterPower = battle.FighterPower;\r
94                 EnemyFighterPower = battle.EnemyFighterPower;\r
95                 AirControlLevel = battle.AirControlLevel;\r
96                 SupportType = battle.SupportType;\r
97             }\r
98         }\r
99 \r
100         public BattleResultPanel()\r
101         {\r
102             SuspendLayout();\r
103             CreateLabels();\r
104             _information = new InformationPanel();\r
105             Controls.Add(_information);\r
106             ResumeLayout();\r
107         }\r
108 \r
109         public event Action HpLabelClick;\r
110 \r
111         private void HpLabelClickHandler(object sender, EventArgs ev)\r
112         {\r
113             HpLabelClick?.Invoke();\r
114         }\r
115 \r
116         public void ToggleHpPercent()\r
117         {\r
118             foreach (var label in _hpLabels)\r
119                 label.ToggleHpPercent();\r
120         }\r
121 \r
122         public void Update(Sniffer sniffer)\r
123         {\r
124             var state = sniffer.Battle.BattleState;\r
125             _cellInfo = sniffer.CellInfo;\r
126             switch (sniffer.Battle.BattleState)\r
127             {\r
128                 case BattleState.None:\r
129                     if (!_data.HaveDay && !_data.HaveNight)\r
130                         return;\r
131                     ClearResult();\r
132                     SetPhase("結果");\r
133                     UpdateCellInfo(_cellInfo);\r
134                     return;\r
135                 case BattleState.Result:\r
136                 case BattleState.Unknown:\r
137                     return;\r
138             }\r
139             _data.SetData(sniffer.Battle);\r
140             if ((Spoilers & Spoiler.BattleResult) != 0)\r
141             {\r
142                 ShowData(sniffer.Battle.BattleState);\r
143                 switch (state)\r
144                 {\r
145                     case BattleState.Day:\r
146                         SetPhase("昼戦");\r
147                         break;\r
148                     case BattleState.Night:\r
149                     case BattleState.SpNight:\r
150                         SetPhase("夜戦");\r
151                         break;\r
152                 }\r
153             }\r
154             else\r
155             {\r
156                 ClearResult();\r
157                 SetPhase("結果");\r
158             }\r
159         }\r
160 \r
161         private void PhaseLabelClick(object sender, EventArgs ev)\r
162         {\r
163             switch (_phaseLabel.Text)\r
164             {\r
165                 case "結果":\r
166                     if (_data.HaveDay)\r
167                     {\r
168                         ShowData(BattleState.Day);\r
169                         SetPhase("昼戦");\r
170                     }\r
171                     else if (_data.HaveNight)\r
172                     {\r
173                         ShowData(BattleState.Night);\r
174                         SetPhase("夜戦");\r
175                     }\r
176                     break;\r
177                 case "昼戦":\r
178                     if (_data.HaveNight)\r
179                     {\r
180                         ShowData(BattleState.Night);\r
181                         SetPhase("夜戦");\r
182                     }\r
183                     break;\r
184                 case "夜戦":\r
185                     if (_data.HaveDay)\r
186                     {\r
187                         ShowData(BattleState.Day);\r
188                         SetPhase("昼戦");\r
189                     }\r
190                     break;\r
191             }\r
192         }\r
193 \r
194         private void SetPhase(string phase)\r
195         {\r
196             _phaseLabel.Text = phase;\r
197             if (phase == "結果" || _data.HaveDay && _data.HaveNight)\r
198             {\r
199                 _phaseLabel.BorderStyle = BorderStyle.FixedSingle;\r
200                 _phaseLabel.Cursor = Cursors.Hand;\r
201             }\r
202             else\r
203             {\r
204                 _phaseLabel.BorderStyle = BorderStyle.None;\r
205                 _phaseLabel.Cursor = Cursors.Default;\r
206             }\r
207         }\r
208 \r
209         private Point _scrollPosition;\r
210 \r
211         private void ClearResult()\r
212         {\r
213             _scrollPosition = AutoScrollPosition;\r
214             SetPanelVisible(0);\r
215             _information.Visible = false;\r
216             _rankLabel.Text = "";\r
217             _supportLabel.Text = "";\r
218         }\r
219 \r
220         private void ShowData(BattleState state)\r
221         {\r
222             var result = _data.GetResult(state);\r
223             ShowDamage(result.Damage);\r
224             ShowResultRank(result.Rank);\r
225             _information.Show(_data);\r
226             ShowSupportType(_data.SupportType);\r
227             UpdateCellInfo(_cellInfo);\r
228             AutoScrollPosition = new Point(-_scrollPosition.X, -_scrollPosition.Y);\r
229         }\r
230 \r
231         private void ShowDamage(BattleInfo.BattleResult result)\r
232         {\r
233             SuspendLayout();\r
234             SetEachResult(_friendLabels, result.Friend);\r
235             SetEachResult(_enemyLabels, result.Enemy);\r
236             var lines = Max(Ships(result.Friend).Length, Ships(result.Enemy).Length);\r
237             SetPanelVisible(lines);\r
238             ResumeLayout(); // スクロールバーの有無を決定する\r
239             AdjustPanelWidth(lines);\r
240         }\r
241 \r
242         private void SetEachResult(IReadOnlyList<ShipLabels> labelsList, BattleInfo.BattleResult.Combined fleet)\r
243         {\r
244             var ships = Ships(fleet);\r
245             for (var i = 0; i < ships.Length; i++)\r
246             {\r
247                 var labels = labelsList[i];\r
248                 if (i == fleet.Main.Length)\r
249                 {\r
250                     labels.Name.Text = "護衛";\r
251                     labels.Hp.Reset();\r
252                     continue;\r
253                 }\r
254                 var ship = ships[i];\r
255                 if (ShipMaster.IsEnemyId(ship.Spec.Id))\r
256                 {\r
257                     labels.Hp.Set(ship);\r
258                     labels.Name.SetName(ShortenName(ship.Name));\r
259                 }\r
260                 else\r
261                 {\r
262                     labels.Set(ship);\r
263                 }\r
264                 ToolTip.SetToolTip(labels.Name, GetEquipString(ship));\r
265             }\r
266             for (var i = ships.Length; i < labelsList.Count; i++)\r
267                 labelsList[i].Reset();\r
268         }\r
269 \r
270         private static ShipStatus[] Ships(BattleInfo.BattleResult.Combined fleet)\r
271         {\r
272             if (fleet.Guard.Length == 0)\r
273                 return fleet.Main;\r
274             return fleet.Main.Concat(new[] {new ShipStatus()}.Concat(fleet.Guard)).ToArray();\r
275         }\r
276 \r
277         private static string GetEquipString(ShipStatus ship)\r
278         {\r
279             var result =\r
280                 (from i in Enumerable.Range(0, ship.Slot.Count)\r
281                     let item = ship.Slot[i]\r
282                     where !item.Empty\r
283                     select item.Spec.Name +\r
284                            (item.Spec.IsAircraft && ship.OnSlot.Length > 0 && ship.Spec.MaxEq.Length > 0\r
285                                ? $"{ship.OnSlot[i]}/{ship.Spec.MaxEq[i]}"\r
286                                : ""));\r
287             if (ship.SlotEx.Id > 0)\r
288                 result = result.Concat(new[] {ship.SlotEx.Spec.Name});\r
289             return string.Join("\r\n", result);\r
290         }\r
291 \r
292         private static string ShortenName(string name)\r
293         {\r
294             return new Regex(@"\(elite\)|\(flagship\)").Replace(name,\r
295                 match => match.Value == "(elite)" ? "(e)" : "(f)");\r
296         }\r
297 \r
298         private void ShowResultRank(BattleResultRank rank)\r
299         {\r
300             var result = new[] {"完全S", "勝利S", "勝利A", "勝利B", "敗北C", "敗北D", "敗北E"};\r
301             _rankLabel.Text = result[(int)rank];\r
302         }\r
303 \r
304         private void ShowSupportType(int support)\r
305         {\r
306             _supportLabel.Text = new[] {"", "空支援", "砲支援", "雷支援", "潜支援"}[support];\r
307         }\r
308 \r
309         public void UpdateCellInfo(CellInfo cellInfo)\r
310         {\r
311             _cellLabel.Text = (Spoilers & Spoiler.NextCell) == 0 ? cellInfo.Current : cellInfo.Next;\r
312             _cellLabel.Location = new Point(ClientSize.Width - _cellLabel.Width - 2, _cellLabel.Location.Y);\r
313         }\r
314 \r
315         private const int LineHeight = 16;\r
316         private const int LabelHeight = 12;\r
317         private const int LabelMargin = (LineHeight - LabelHeight) / 2;\r
318         private const int TopMargin = 4;\r
319 \r
320         private void CreateLabels()\r
321         {\r
322             _phaseLabel = new Label\r
323             {\r
324                 Location = new Point(4, TopMargin),\r
325                 Size = new Size(31, LabelHeight + 2)\r
326             };\r
327             _phaseLabel.Click += PhaseLabelClick;\r
328             Controls.Add(_phaseLabel);\r
329             _rankLabel = new Label\r
330             {\r
331                 Location = new Point(37, TopMargin),\r
332                 Size = new Size(42, LabelHeight)\r
333             };\r
334             Controls.Add(_rankLabel);\r
335             _supportLabel = new Label\r
336             {\r
337                 Location = new Point(77, TopMargin),\r
338                 AutoSize = true\r
339             };\r
340             Controls.Add(_supportLabel);\r
341             _cellLabel = new Label\r
342             {\r
343                 Location = new Point(0, TopMargin),\r
344                 AutoSize = true\r
345             };\r
346             Controls.Add(_cellLabel);\r
347             for (var i = 0; i < 13; i++)\r
348             {\r
349                 var friend = new ShipLabels\r
350                 {\r
351                     Name = new ShipLabel.Name(new Point(1, LabelMargin), ShipNameWidth.BattleResult),\r
352                     Hp = new ShipLabel.Hp(new Point(101, 0), LineHeight),\r
353                     BackPanel = new Panel\r
354                     {\r
355                         Location = new Point(0, LineHeight * i + 38),\r
356                         Size = new Size(0, LineHeight),\r
357                         BackColor = CustomColors.ColumnColors.DarkFirst(i),\r
358                         Visible = false\r
359                     }\r
360                 };\r
361                 _friendLabels.Add(friend);\r
362                 friend.Arrange(this, CustomColors.ColumnColors.DarkFirst(i));\r
363                 _hpLabels.Add(friend.Hp);\r
364                 friend.Hp.Click += HpLabelClickHandler;\r
365                 var enemy = new ShipLabels\r
366                 {\r
367                     Name = new ShipLabel.Name(new Point(164, LabelMargin), ShipNameWidth.Max),\r
368                     Hp = new ShipLabel.Hp\r
369                     {\r
370                         Location = new Point(119, 0),\r
371                         AutoSize = true,\r
372                         MinimumSize = new Size(0, LineHeight),\r
373                         TextAlign = ContentAlignment.MiddleLeft\r
374                     }\r
375                 };\r
376                 _enemyLabels.Add(enemy);\r
377                 enemy.Arrange(friend.BackPanel, CustomColors.ColumnColors.DarkFirst(i));\r
378             }\r
379         }\r
380 \r
381         private void SetPanelVisible(int showPanels)\r
382         {\r
383             for (var i = 0; i < _friendLabels.Count; i++)\r
384                 _friendLabels[i].BackPanel.Visible = i < showPanels;\r
385         }\r
386 \r
387         private void AdjustPanelWidth(int lines)\r
388         {\r
389             var panelWidth = Max(ClientSize.Width, // スクロールバーの有無を反映した横幅\r
390                 _enemyLabels[0].Name.Location.X + _enemyLabels.Max(labels => labels.Name.Size.Width) - 1); // 敵の名前の右端\r
391             for (var i = 0; i < lines; i++)\r
392                 _friendLabels[i].BackPanel.Width = panelWidth;\r
393         }\r
394 \r
395         protected override void OnMouseWheel(MouseEventArgs e)\r
396         {\r
397             if (VScroll && (ModifierKeys & Keys.Shift) == Keys.Shift)\r
398             {\r
399                 VScroll = false;\r
400                 OnMouseWheel(e);\r
401                 VScroll = true;\r
402                 return;\r
403             }\r
404             base.OnMouseWheel(e);\r
405         }\r
406 \r
407         private sealed class InformationPanel : PanelWithToolTip\r
408         {\r
409             private readonly Formation _formation;\r
410             private readonly FighterPower _fighterPower;\r
411 \r
412             public InformationPanel()\r
413             {\r
414                 _formation = new Formation\r
415                 {\r
416                     Friend = new Label\r
417                     {\r
418                         Location = new Point(47, LabelMargin),\r
419                         Size = new Size(29, LabelHeight)\r
420                     },\r
421                     Enemy = new Label\r
422                     {\r
423                         Location = new Point(75, LabelMargin),\r
424                         Size = new Size(29, LabelHeight)\r
425                     },\r
426                     State = new Label\r
427                     {\r
428                         Location = new Point(1, LabelMargin),\r
429                         Size = new Size(48, LabelHeight),\r
430                         TextAlign = ContentAlignment.MiddleCenter\r
431                     }\r
432                 };\r
433                 Controls.AddRange(_formation.Controls);\r
434                 _fighterPower = new FighterPower\r
435                 {\r
436                     ToolTip = ToolTip,\r
437                     Friend = new Label\r
438                     {\r
439                         Location = new Point(162, LabelMargin),\r
440                         Size = new Size(23, LabelHeight),\r
441                         TextAlign = ContentAlignment.MiddleRight\r
442                     },\r
443                     Enemy = new Label\r
444                     {\r
445                         Location = new Point(183, LabelMargin),\r
446                         Size = new Size(23, LabelHeight),\r
447                         TextAlign = ContentAlignment.MiddleRight\r
448                     },\r
449                     State = new Label\r
450                     {\r
451                         Location = new Point(110, LabelMargin),\r
452                         Size = new Size(53, LabelHeight)\r
453                     }\r
454                 };\r
455                 Controls.AddRange(_fighterPower.Controls);\r
456                 Location = new Point(0, 20);\r
457                 Size = new Size(206, 16);\r
458                 BackColor = CustomColors.ColumnColors.Bright;\r
459                 Visible = false;\r
460             }\r
461 \r
462             public void Show(BattleData data)\r
463             {\r
464                 SetData(data);\r
465                 Visible = true;\r
466             }\r
467 \r
468             private void SetData(BattleData data)\r
469             {\r
470                 _formation.SetFormation(data.Formation);\r
471                 _fighterPower.SetFighterPower(data);\r
472             }\r
473 \r
474             private class StateLabels\r
475             {\r
476                 public Label Friend;\r
477                 public Label Enemy;\r
478                 public Label State;\r
479 \r
480                 public Control[] Controls => new Control[] {Friend, Enemy, State};\r
481             }\r
482 \r
483             private class Formation : StateLabels\r
484             {\r
485                 public void SetFormation(int[] formation)\r
486                 {\r
487                     Friend.Text = FormationName(formation[0]);\r
488                     Enemy.Text = FormationName(formation[1]);\r
489                     State.Text = new[] {"同航戦", "反航戦", "T字有利", "T字不利"}[formation[2] - 1];\r
490                 }\r
491 \r
492                 private static string FormationName(int formation)\r
493                 {\r
494                     switch (formation)\r
495                     {\r
496                         case 1:\r
497                             return "単縦";\r
498                         case 2:\r
499                             return "複縦";\r
500                         case 3:\r
501                             return "輪形";\r
502                         case 4:\r
503                             return "梯形";\r
504                         case 5:\r
505                             return "単横";\r
506                         case 6:\r
507                             return "警戒";\r
508                         case 11:\r
509                             return "第一";\r
510                         case 12:\r
511                             return "第二";\r
512                         case 13:\r
513                             return "第三";\r
514                         case 14:\r
515                             return "第四";\r
516                         default:\r
517                             return "";\r
518                     }\r
519                 }\r
520             }\r
521 \r
522             private class FighterPower : StateLabels\r
523             {\r
524                 public ToolTip ToolTip { private get; set; }\r
525 \r
526                 public void SetFighterPower(BattleData data)\r
527                 {\r
528                     if (data.AirControlLevel == -1)\r
529                     {\r
530                         foreach (var control in Controls)\r
531                             control.Text = "";\r
532                         return;\r
533                     }\r
534                     SetFriend(data.FighterPower);\r
535                     SetEnemy(data.EnemyFighterPower);\r
536                     SetAirControlLevel(data.AirControlLevel);\r
537                 }\r
538 \r
539                 private void SetFriend(Range fighterPower)\r
540                 {\r
541                     Friend.Text = fighterPower.Min.ToString();\r
542                     ToolTip.SetToolTip(Friend, fighterPower.Diff ? fighterPower.RangeString : "");\r
543                 }\r
544 \r
545                 private void SetEnemy(EnemyFighterPower enemy)\r
546                 {\r
547                     Enemy.Text = enemy.AirCombat + enemy.UnknownMark;\r
548                     ToolTip.SetToolTip(Enemy,\r
549                         enemy.AirCombat == enemy.Interception ? "" : "防空:" + enemy.Interception + enemy.UnknownMark);\r
550                 }\r
551 \r
552                 private void SetAirControlLevel(int level)\r
553                 {\r
554                     State.Text =\r
555                         new[] {"", "制空均衡", "制空確保", "航空優勢", "航空劣勢", "制空喪失"}[level + 1];\r
556                 }\r
557             }\r
558         }\r
559     }\r
560 }