OSDN Git Service

連合艦隊で開幕雷撃で戦闘が終わるとエラーになるのを直す
[kancollesniffer/KancolleSniffer.git] / KancolleSniffer / BattleInfo.cs
1 // Copyright (C) 2014, 2015 Kazuhiro Fujieda <fujieda@users.osdn.me>\r
2 // \r
3 // This program is part of KancolleSniffer.\r
4 //\r
5 // KancolleSniffer is free software: you can redistribute it and/or modify\r
6 // it under the terms of the GNU General Public License as published by\r
7 // the Free Software Foundation, either version 3 of the License, or\r
8 // (at your option) any later version.\r
9 //\r
10 // This program is distributed in the hope that it will be useful,\r
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of\r
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
13 // GNU General Public License for more details.\r
14 //\r
15 // You should have received a copy of the GNU General Public License\r
16 // along with this program; if not, see <http://www.gnu.org/licenses/>.\r
17 \r
18 using System.Collections.Generic;\r
19 using System.Linq;\r
20 using static System.Math;\r
21 \r
22 namespace KancolleSniffer\r
23 {\r
24     public enum BattleResultRank\r
25     {\r
26         P,\r
27         S,\r
28         A,\r
29         B,\r
30         C,\r
31         D,\r
32         E\r
33     }\r
34 \r
35     public class BattleInfo\r
36     {\r
37         private readonly ShipInfo _shipInfo;\r
38         private readonly ItemInfo _itemInfo;\r
39         private int _fleet;\r
40         private Record[] _friend;\r
41         private Record[] _guard;\r
42         private int[] _enemyHp;\r
43         private int[] _enemyStartHp;\r
44         private readonly List<int> _escapingShips = new List<int>();\r
45 \r
46         public const int IncollectFighterPowerFlag = 0x10000;\r
47         public bool InBattle { get; set; }\r
48         public string Formation { get; private set; }\r
49         public int EnemyFighterPower { get; private set; }\r
50         public bool HasDamagedShip { get; set; }\r
51         public string[] DamagedShipNames { get; private set; }\r
52         public int AirControlLevel { get; private set; }\r
53         public BattleResultRank ResultRank { get; private set; }\r
54         public ShipStatus[] EnemyResultStatus { get; private set; }\r
55 \r
56         public BattleInfo(ShipInfo shipInfo, ItemInfo itemInfo)\r
57         {\r
58             _shipInfo = shipInfo;\r
59             _itemInfo = itemInfo;\r
60         }\r
61 \r
62         public void InspectBattle(dynamic json)\r
63         {\r
64             InBattle = true;\r
65             Formation = FormationName(json);\r
66             EnemyFighterPower = CalcEnemyFighterPower(json);\r
67             AirControlLevel = CheckAirControlLevel(json);\r
68             ShowResult(false); // 昼戦の結果を夜戦のときに表示する\r
69             SetupResult(json);\r
70             if (IsNightBattle(json))\r
71                 CalcHougekiDamage(json.api_hougeki, _friend, _enemyHp);\r
72             else\r
73                 CalcDamage(json);\r
74             ClearOverKill(_enemyHp);\r
75             ResultRank = CalcResultRank();\r
76         }\r
77 \r
78         private int DeckId(dynamic json)\r
79         {\r
80             if (json.api_dock_id()) // 昼戦はtypoしている\r
81                 return (int)json.api_dock_id - 1;\r
82             if (json.api_deck_id is string) // 通常の夜戦では文字列\r
83                 return int.Parse(json.api_deck_id) - 1;\r
84             return (int)json.api_deck_id - 1;\r
85         }\r
86 \r
87         private string FormationName(dynamic json)\r
88         {\r
89             if (!json.api_formation()) // 演習の夜戦\r
90                 return "";\r
91             switch ((int)json.api_formation[2])\r
92             {\r
93                 case 1:\r
94                     return "同航戦";\r
95                 case 2:\r
96                     return "反航戦";\r
97                 case 3:\r
98                     return "T字有利";\r
99                 case 4:\r
100                     return "T字不利";\r
101             }\r
102             return "";\r
103         }\r
104 \r
105         private void SetupResult(dynamic json)\r
106         {\r
107             if (_friend != null)\r
108                 return;\r
109             var combined = json.api_nowhps_combined();\r
110             var nowhps = (int[])json.api_nowhps;\r
111             _fleet = combined ? 0 : DeckId(json);\r
112             var fstats = _shipInfo.GetShipStatuses(_fleet);\r
113             _friend = Record.Setup(\r
114                 nowhps, (int[])json.api_maxhps,\r
115                 fstats.Select(s => s.Slot).ToArray(),\r
116                 fstats.Select(s => s.SlotEx).ToArray());\r
117             _enemyHp = nowhps.Skip(7).TakeWhile(hp => hp != -1).ToArray();\r
118             _enemyStartHp = (int[])_enemyHp.Clone();\r
119             EnemyResultStatus =\r
120                 (from id in ((int[])json.api_ship_ke).Skip(1)\r
121                     where id != -1\r
122                     select new ShipStatus {Id = id, Spec = _shipInfo.GetSpec(id)}).ToArray();\r
123             if (combined)\r
124             {\r
125                 var gstats = _shipInfo.GetShipStatuses(1);\r
126                 _guard = Record.Setup(\r
127                     (int[])json.api_nowhps_combined,\r
128                     (int[])json.api_maxhps_combined,\r
129                     gstats.Select(s => s.Slot).ToArray(),\r
130                     gstats.Select(s => s.SlotEx).ToArray());\r
131             }\r
132             else\r
133             {\r
134                 _guard = new Record[0];\r
135             }\r
136         }\r
137 \r
138         public void CleanupResult()\r
139         {\r
140             _friend = null;\r
141         }\r
142 \r
143         private int CheckAirControlLevel(dynamic json)\r
144         {\r
145             if (!json.api_kouku())\r
146                 return -1;\r
147             var stage1 = json.api_kouku.api_stage1;\r
148             if (stage1 == null)\r
149                 return -1;\r
150             if (stage1.api_f_count == 0 && stage1.api_e_count == 0)\r
151                 return -1;\r
152             return (int)stage1.api_disp_seiku;\r
153         }\r
154 \r
155         private int CalcEnemyFighterPower(dynamic json)\r
156         {\r
157             var missing = 0;\r
158             var maxEq = ((int[])json.api_ship_ke).Skip(1).SelectMany(id =>\r
159             {\r
160                 var r = _shipInfo.GetSpec(id).MaxEq;\r
161                 if (r != null)\r
162                     return r;\r
163                 missing = IncollectFighterPowerFlag;\r
164                 return new int[5];\r
165             });\r
166             var equips = ((int[][])json.api_eSlot).SelectMany(x => x);\r
167             return (from slot in equips.Zip(maxEq, (id, max) => new {id, max})\r
168                 let spec = _itemInfo.GetSpecByItemId(slot.id)\r
169                 where spec.CanAirCombat\r
170                 select (int)Floor(spec.AntiAir * Sqrt(slot.max))).DefaultIfEmpty().Sum() | missing;\r
171         }\r
172 \r
173         private void CalcDamage(dynamic json, bool surfaceFleet = false)\r
174         {\r
175             var combined = json.api_nowhps_combined();\r
176             if (json.api_kouku.api_stage3 != null)\r
177                 CalcSimpleDamage(json.api_kouku.api_stage3, _friend, _enemyHp);\r
178             if (json.api_kouku.api_stage3_combined() && json.api_kouku.api_stage3_combined != null)\r
179                 CalcSimpleDamage(json.api_kouku.api_stage3_combined.api_fdam, _guard);\r
180             if (json.api_kouku2()) // 航空戦2回目\r
181             {\r
182                 if (json.api_kouku2.api_stage3 != null)\r
183                     CalcSimpleDamage(json.api_kouku2.api_stage3, _friend, _enemyHp);\r
184                 if (json.api_kouku2.api_stage3_combined() && json.api_kouku2.api_stage3_combined != null)\r
185                     CalcSimpleDamage(json.api_kouku2.api_stage3_combined.api_fdam, _guard);\r
186             }\r
187             if (!json.api_opening_atack()) // 航空戦のみ\r
188                 return;\r
189             if (json.api_support_info() && json.api_support_info != null)\r
190                 CalcSupportDamage(json.api_support_info);\r
191             if (json.api_opening_atack != null)\r
192             {\r
193                 var friend = combined ? _guard : _friend; // 雷撃の対象は護衛\r
194                 CalcSimpleDamage(json.api_opening_atack, friend, _enemyHp);\r
195             }\r
196             if (json.api_hougeki1() && json.api_hougeki1 != null)\r
197             {\r
198                 var friend = combined && !surfaceFleet ? _guard : _friend; // 空母機動部隊は一巡目が護衛\r
199                 CalcHougekiDamage(json.api_hougeki1, friend, _enemyHp);\r
200             }\r
201             if (json.api_hougeki2() && json.api_hougeki2 != null)\r
202             {\r
203                 CalcHougekiDamage(json.api_hougeki2, _friend, _enemyHp);\r
204             }\r
205             if (json.api_hougeki3() && json.api_hougeki3 != null)\r
206             {\r
207                 var friend = combined && surfaceFleet ? _guard : _friend; // 水上打撃部隊は三順目が護衛\r
208                 CalcHougekiDamage(json.api_hougeki3, friend, _enemyHp);\r
209             }\r
210             if (json.api_raigeki() && json.api_raigeki != null)\r
211             {\r
212                 var friend = combined ? _guard : _friend;\r
213                 CalcSimpleDamage(json.api_raigeki, friend, _enemyHp);\r
214             }\r
215         }\r
216 \r
217         private void CalcSupportDamage(dynamic json)\r
218         {\r
219             if (json.api_support_hourai != null)\r
220                 CalcSimpleDamage(json.api_support_hourai.api_damage, _enemyHp);\r
221             else if (json.api_support_airatack != null)\r
222             {\r
223                 var stage3 = json.api_support_airatack.api_stage3;\r
224                 if (stage3 != null)\r
225                     CalcSimpleDamage(stage3.api_edam, _enemyHp);\r
226             }\r
227         }\r
228 \r
229         private bool IsNightBattle(dynamic json) => json.api_hougeki();\r
230 \r
231         private void CalcSimpleDamage(dynamic json, Record[] friend, int[] enemy)\r
232         {\r
233             CalcSimpleDamage(json.api_fdam, friend);\r
234             CalcSimpleDamage(json.api_edam, enemy);\r
235         }\r
236 \r
237         private void CalcSimpleDamage(dynamic rawDamage, Record[] result)\r
238         {\r
239             var damage = (int[])rawDamage;\r
240             for (var i = 0; i < result.Length; i++)\r
241                 result[i].ApplyDamage(damage[i + 1]);\r
242         }\r
243 \r
244         private void CalcSimpleDamage(dynamic rawDamage, int[] result)\r
245         {\r
246             var damage = (int[])rawDamage;\r
247             for (var i = 0; i < result.Length; i++)\r
248                 result[i] -= damage[i + 1];\r
249         }\r
250 \r
251         private void CalcHougekiDamage(dynamic hougeki, Record[] friend, int[] enemy)\r
252         {\r
253             var targets = ((dynamic[])hougeki.api_df_list).Skip(1).SelectMany(x => (int[])x);\r
254             var damages = ((dynamic[])hougeki.api_damage).Skip(1).SelectMany(x => (double[])x);\r
255             foreach (var hit in targets.Zip(damages, (t, d) => new {t, d}))\r
256             {\r
257                 if (hit.t == -1)\r
258                     continue;\r
259                 if (hit.t <= 6)\r
260                     friend[hit.t - 1].ApplyDamage((int)hit.d);\r
261                 else\r
262                     enemy[(hit.t - 1) % 6] -= (int)hit.d;\r
263             }\r
264         }\r
265 \r
266         private void ClearOverKill(int[] result)\r
267         {\r
268             for (var i = 0; i < result.Length; i++)\r
269                 if (result[i] < 0)\r
270                     result[i] = 0;\r
271         }\r
272 \r
273         public void InspectBattleResult(dynamic json)\r
274         {\r
275             ShowResult();\r
276             CleanupResult();\r
277         }\r
278 \r
279         public void InspectPracticeResult(dynamic json)\r
280         {\r
281             ShowResult(false);\r
282             CleanupResult();\r
283         }\r
284 \r
285         private void ShowResult(bool warnDamagedShip = true)\r
286         {\r
287             if (_friend == null)\r
288                 return;\r
289             var ships = _shipInfo.GetShipStatuses(_fleet);\r
290             foreach (var e in ships.Zip(_friend, (ship, now) => new {ship, now}))\r
291                 e.now.UpdateShipStatus(e.ship);\r
292             if (warnDamagedShip)\r
293                 UpdateDamgedShipNames(ships);\r
294             SetEnemyResultStatus();\r
295         }\r
296 \r
297         private void SetEnemyResultStatus()\r
298         {\r
299             for (var i = 0; i < EnemyResultStatus.Length; i++)\r
300             {\r
301                 EnemyResultStatus[i].MaxHp = _enemyStartHp[i];\r
302                 EnemyResultStatus[i].NowHp = _enemyHp[i];\r
303             }\r
304         }\r
305 \r
306         private void UpdateDamgedShipNames(IEnumerable<ShipStatus> ships)\r
307         {\r
308             DamagedShipNames =\r
309                 (from s in ships where s.DamageLevel == ShipStatus.Damage.Badly && !s.Escaped select s.Name).ToArray();\r
310             HasDamagedShip = DamagedShipNames.Any();\r
311         }\r
312 \r
313         public void InspectCombinedBattle(dynamic json, bool surfaceFleet)\r
314         {\r
315             InBattle = true;\r
316             Formation = FormationName(json);\r
317             EnemyFighterPower = CalcEnemyFighterPower(json);\r
318             AirControlLevel = CheckAirControlLevel(json);\r
319             _fleet = 10;\r
320             ShowResultCombined(false);\r
321             SetupResult(json);\r
322             if (IsNightBattle(json))\r
323                 CalcHougekiDamage(json.api_hougeki, _guard, _enemyHp);\r
324             else\r
325                 CalcDamage(json, surfaceFleet);\r
326             ClearOverKill(_enemyHp);\r
327             ResultRank = CalcResultRank();\r
328         }\r
329 \r
330         public void InspectCombinedBattleResult(dynamic json)\r
331         {\r
332             _escapingShips.Clear();\r
333             ShowResultCombined();\r
334             CleanupResult();\r
335             if ((int)json.api_escape_flag == 0)\r
336                 return;\r
337             var damaged = (int)json.api_escape.api_escape_idx[0] - 1;\r
338             _escapingShips.Add(_shipInfo.GetDeck(damaged / 6)[damaged % 6]);\r
339             var escort = (int)json.api_escape.api_tow_idx[0] - 1;\r
340             _escapingShips.Add(_shipInfo.GetDeck(escort / 6)[escort % 6]);\r
341         }\r
342 \r
343         private void ShowResultCombined(bool warnDamagedShip = true)\r
344         {\r
345             if (_friend == null)\r
346                 return;\r
347             var ships = _shipInfo.GetShipStatuses(0).Concat(_shipInfo.GetShipStatuses(1)).ToArray();\r
348             foreach (var e in ships.Zip(_friend.Concat(_guard), (ship, now) => new {ship, now}))\r
349                 e.now.UpdateShipStatus(e.ship);\r
350             if (warnDamagedShip)\r
351                 UpdateDamgedShipNames(ships);\r
352             SetEnemyResultStatus();\r
353         }\r
354 \r
355         public void CauseCombinedBattleEscape()\r
356         {\r
357             _shipInfo.SetEscapedShips(_escapingShips);\r
358             UpdateDamgedShipNames(_shipInfo.GetShipStatuses(0).Concat(_shipInfo.GetShipStatuses(1)));\r
359         }\r
360 \r
361         private class Record\r
362         {\r
363             private int _maxHp;\r
364             private ItemStatus[] _slot;\r
365             private ItemStatus _slotEx;\r
366             public int NowHp;\r
367             public int StartHp;\r
368             public int Damage;\r
369 \r
370             public static Record[] Setup(int[] rawHp, int[] rawMax, ItemStatus[][] slots, ItemStatus[] slotEx)\r
371             {\r
372                 var hp = rawHp.Skip(1).Take(6).TakeWhile(h => h != -1).ToArray();\r
373                 var max = rawMax.Skip(1).Take(6).TakeWhile(h => h != -1).ToArray();\r
374                 var r = new Record[hp.Length];\r
375                 for (var i = 0; i < hp.Length; i++)\r
376                 {\r
377                     r[i] = new Record\r
378                     {\r
379                         NowHp = hp[i],\r
380                         StartHp = hp[i],\r
381                         _maxHp = max[i],\r
382                         _slot = slots[i].ToArray(),\r
383                         _slotEx = slotEx[i],\r
384                     };\r
385                 }\r
386                 return r;\r
387             }\r
388 \r
389             public void ApplyDamage(int damage)\r
390             {\r
391                 if (NowHp > damage)\r
392                 {\r
393                     NowHp -= damage;\r
394                     Damage += damage;\r
395                     return;\r
396                 }\r
397                 Damage += NowHp;\r
398                 NowHp = 0;\r
399                 if (_slotEx.Spec.Id == 42) // ダメコン\r
400                 {\r
401                     _slotEx = new ItemStatus();\r
402                     NowHp = (int)(_maxHp * 0.2);\r
403                     return;\r
404                 }\r
405                 if (_slotEx.Spec.Id == 43) // 女神\r
406                 {\r
407                     _slotEx = new ItemStatus();\r
408                     NowHp = _maxHp;\r
409                     return;\r
410                 }\r
411                 for (var j = 0; j < _slot.Length; j++)\r
412                 {\r
413                     var id = _slot[j].Spec.Id;\r
414                     if (id == 42) // ダメコン\r
415                     {\r
416                         _slot[j] = new ItemStatus();\r
417                         NowHp = (int)(_maxHp * 0.2);\r
418                         break;\r
419                     }\r
420                     if (id == 43) // 女神\r
421                     {\r
422                         _slot[j] = new ItemStatus();\r
423                         NowHp = _maxHp;\r
424                         break;\r
425                     }\r
426                 }\r
427             }\r
428 \r
429             public void UpdateShipStatus(ShipStatus ship)\r
430             {\r
431                 ship.NowHp = NowHp;\r
432                 ship.Slot = _slot;\r
433                 ship.SlotEx = _slotEx;\r
434             }\r
435         }\r
436 \r
437         // 以下のコードは航海日誌拡張版の以下のファイルのcalcResultRankを移植したもの\r
438         // https://github.com/nekopanda/logbook/blob/94ceca4be6d4ce79a8759d1ee747fb9827c08edc/main/logbook/dto/BattleExDto.java\r
439         //\r
440         // The MIT License (MIT)\r
441         //\r
442         // Copyright (c) 2014-2015 航海日誌拡張版開発者\r
443         //\r
444         // Permission is hereby granted, free of charge, to any person obtaining a copy\r
445         // of this software and associated documentation files (the "Software"), to deal\r
446         // in the Software without restriction, including without limitation the rights\r
447         // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r
448         // copies of the Software, and to permit persons to whom the Software is\r
449         // furnished to do so, subject to the following conditions:\r
450         //\r
451         // The above copyright notice and this permission notice shall be included in\r
452         // all copies or substantial portions of the Software.\r
453         //\r
454         // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r
455         // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r
456         // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r
457         // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r
458         // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r
459         // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r
460         // THE SOFTWARE.\r
461         //\r
462         private BattleResultRank CalcResultRank()\r
463         {\r
464             var combined = _friend.Concat(_guard).ToArray();\r
465             // 戦闘後に残っている艦数\r
466             var friendNowShips = combined.Count(r => r.NowHp > 0);\r
467             var enemyNowShips = _enemyHp.Count(hp => hp > 0);\r
468             // 総ダメージ\r
469             var friendGauge = combined.Sum(r => r.Damage);\r
470             var enemyGauge = _enemyStartHp.Sum() - _enemyHp.Sum();\r
471             // 轟沈・撃沈数\r
472             var friendSunk = combined.Count(r => r.NowHp == 0);\r
473             var enemySunk = _enemyHp.Count(hp => hp == 0);\r
474 \r
475             var friendGaugeRate = Floor((double)friendGauge / combined.Sum(r => r.StartHp) * 100);\r
476             var enemyGaugeRate = Floor((double)enemyGauge / _enemyStartHp.Sum() * 100);\r
477             var equalOrMore = enemyGaugeRate > (0.9 * friendGaugeRate);\r
478             var superior = enemyGaugeRate > 0 && enemyGaugeRate > (2.5 * friendGaugeRate);\r
479 \r
480             if (friendSunk == 0)\r
481             {\r
482                 if (enemyNowShips == 0)\r
483                 {\r
484                     if (friendGauge == 0)\r
485                         return BattleResultRank.P;\r
486                     return BattleResultRank.S;\r
487                 }\r
488                 if (_enemyHp.Length == 6)\r
489                 {\r
490                     if (enemySunk >= 4)\r
491                         return BattleResultRank.A;\r
492                 }\r
493                 else if (enemySunk * 2 >= _enemyHp.Length)\r
494                 {\r
495                     return BattleResultRank.A;\r
496                 }\r
497                 if (_enemyHp[0] == 0)\r
498                     return BattleResultRank.B;\r
499                 if (superior)\r
500                     return BattleResultRank.B;\r
501             }\r
502             else\r
503             {\r
504                 if (enemyNowShips == 0)\r
505                     return BattleResultRank.B;\r
506                 if (_enemyHp[0] == 0 && friendSunk < enemySunk)\r
507                     return BattleResultRank.B;\r
508                 if (superior)\r
509                     return BattleResultRank.B;\r
510                 if (_enemyHp[0] == 0)\r
511                     return BattleResultRank.C;\r
512             }\r
513             if (enemyGauge > 0 && equalOrMore)\r
514                 return BattleResultRank.C;\r
515             if (friendSunk > 0 && friendNowShips == 1)\r
516                 return BattleResultRank.E;\r
517             return BattleResultRank.D;\r
518         }\r
519     }\r
520 }