OSDN Git Service

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