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