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