OSDN Git Service

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