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;\r
19 using System.Collections.Generic;\r
20 using System.Linq;\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 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 \r
55         public BattleInfo(ShipMaster shipMaster, ShipInfo shipInfo, ItemInfo itemInfo)\r
56         {\r
57             _shipMaster = shipMaster;\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             _friend = Record.Setup(\r
113                 nowhps, (int[])json.api_maxhps,\r
114                 _shipInfo.GetShipStatuses(_fleet).Select(s => s.Slot).ToArray(),\r
115                 _itemInfo);\r
116             _enemyHp = nowhps.Skip(7).TakeWhile(hp => hp != -1).ToArray();\r
117             _enemyStartHp = (int[])_enemyHp.Clone();\r
118             if (combined)\r
119             {\r
120                 _guard = Record.Setup(\r
121                     (int[])json.api_nowhps_combined,\r
122                     (int[])json.api_maxhps_combined,\r
123                     _shipInfo.GetShipStatuses(1).Select(s => s.Slot).ToArray(),\r
124                     _itemInfo);\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 int CalcEnemyFighterPower(dynamic json)\r
150         {\r
151             var maxEq = ((int[])json.api_ship_ke).Skip(1).SelectMany(id => _shipMaster[id].MaxEq);\r
152             var equips = ((int[][])json.api_eSlot).SelectMany(x => x);\r
153             return (from slot in equips.Zip(maxEq, (id, max) => new {id, max})\r
154                 let spec = _itemInfo.GetSpecByItemId(slot.id)\r
155                 where spec.CanAirCombat\r
156                 select (int)Math.Floor(spec.AntiAir * Math.Sqrt(slot.max))).DefaultIfEmpty().Sum();\r
157         }\r
158 \r
159         private void CalcDamage(dynamic json, bool surfaceFleet = false)\r
160         {\r
161             var combined = json.api_nowhps_combined();\r
162             if (json.api_kouku.api_stage3 != null)\r
163                 CalcSimpleDamage(json.api_kouku.api_stage3, _friend, _enemyHp);\r
164             if (json.api_kouku.api_stage3_combined() && json.api_kouku.api_stage3_combined != null)\r
165                 CalcSimpleDamage(json.api_kouku.api_stage3_combined.api_fdam, _guard);\r
166             if (json.api_kouku2()) // 航空戦2回目\r
167             {\r
168                 if (json.api_kouku2.api_stage3 != null)\r
169                     CalcSimpleDamage(json.api_kouku2.api_stage3, _friend, _enemyHp);\r
170                 if (json.api_kouku2.api_stage3_combined() && json.api_kouku2.api_stage3_combined != null)\r
171                     CalcSimpleDamage(json.api_kouku2.api_stage3_combined.api_fdam, _guard);\r
172             }\r
173             if (!json.api_opening_atack()) // 航空戦のみ\r
174                 return;\r
175             if (json.api_support_info() && json.api_support_info != null)\r
176                 CalcSupportDamage(json.api_support_info);\r
177             if (json.api_opening_atack != null)\r
178             {\r
179                 var friend = combined ? _guard : _friend; // 雷撃の対象は護衛\r
180                 CalcSimpleDamage(json.api_opening_atack, friend, _enemyHp);\r
181             }\r
182             if (json.api_hougeki1 != null)\r
183             {\r
184                 var friend = combined && !surfaceFleet ? _guard : _friend; // 空母機動部隊は一巡目が護衛\r
185                 CalcHougekiDamage(json.api_hougeki1, friend, _enemyHp);\r
186             }\r
187             if (json.api_hougeki2() && json.api_hougeki2 != null)\r
188             {\r
189                 CalcHougekiDamage(json.api_hougeki2, _friend, _enemyHp);\r
190             }\r
191             if (json.api_hougeki3() && json.api_hougeki3 != null)\r
192             {\r
193                 var friend = combined && surfaceFleet ? _guard : _friend; // 水上打撃部隊は三順目が護衛\r
194                 CalcHougekiDamage(json.api_hougeki3, friend, _enemyHp);\r
195             }\r
196             if (json.api_raigeki() && json.api_raigeki != null)\r
197             {\r
198                 var friend = combined ? _guard : _friend;\r
199                 CalcSimpleDamage(json.api_raigeki, friend, _enemyHp);\r
200             }\r
201         }\r
202 \r
203         private void CalcSupportDamage(dynamic json)\r
204         {\r
205             if (json.api_support_hourai != null)\r
206                 CalcSimpleDamage(json.api_support_hourai.api_damage, _enemyHp);\r
207             else if (json.api_support_airatack != null)\r
208             {\r
209                 var stage3 = json.api_support_airatack.api_stage3;\r
210                 if (stage3 != null)\r
211                     CalcSimpleDamage(stage3.api_edam, _enemyHp);\r
212             }\r
213         }\r
214 \r
215         private bool IsNightBattle(dynamic json)\r
216         {\r
217             return json.api_hougeki();\r
218         }\r
219 \r
220         private void CalcSimpleDamage(dynamic json, Record[] friend, int[] enemy)\r
221         {\r
222             CalcSimpleDamage(json.api_fdam, friend);\r
223             CalcSimpleDamage(json.api_edam, enemy);\r
224         }\r
225 \r
226         private void CalcSimpleDamage(dynamic rawDamage, Record[] result)\r
227         {\r
228             var damage = (int[])rawDamage;\r
229             for (var i = 0; i < result.Length; i++)\r
230                 result[i].ApplyDamage(damage[i + 1]);\r
231         }\r
232 \r
233         private void CalcSimpleDamage(dynamic rawDamage, int[] result)\r
234         {\r
235             var damage = (int[])rawDamage;\r
236             for (var i = 0; i < result.Length; i++)\r
237                 result[i] -= damage[i + 1];\r
238         }\r
239 \r
240         private void CalcHougekiDamage(dynamic hougeki, Record[] friend, int[] enemy)\r
241         {\r
242             var targets = ((dynamic[])hougeki.api_df_list).Skip(1).SelectMany(x => (int[])x);\r
243             var damages = ((dynamic[])hougeki.api_damage).Skip(1).SelectMany(x => (double[])x);\r
244             foreach (var hit in targets.Zip(damages, (t, d) => new {t, d}))\r
245             {\r
246                 if (hit.t == -1)\r
247                     continue;\r
248                 if (hit.t <= 6)\r
249                     friend[hit.t - 1].ApplyDamage((int)hit.d);\r
250                 else\r
251                     enemy[(hit.t - 1) % 6] -= (int)hit.d;\r
252             }\r
253         }\r
254 \r
255         private void ClearOverKill(int[] result)\r
256         {\r
257             for (var i = 0; i < result.Length; i++)\r
258                 if (result[i] < 0)\r
259                     result[i] = 0;\r
260         }\r
261 \r
262         public void InspectBattleResult(dynamic json)\r
263         {\r
264             ShowResult();\r
265             CleanupResult();\r
266         }\r
267 \r
268         public void InspectPracticeResult(dynamic json)\r
269         {\r
270             ShowResult(false);\r
271             CleanupResult();\r
272         }\r
273 \r
274         private void ShowResult(bool warnDamagedShip = true)\r
275         {\r
276             if (_friend == null)\r
277                 return;\r
278             var ships = _shipInfo.GetShipStatuses(_fleet);\r
279             foreach (var e in ships.Zip(_friend, (ship, now) => new {ship, now}))\r
280                 e.now.UpdateShipStatus(e.ship);\r
281             if (warnDamagedShip)\r
282                 UpdateDamgedShipNames(ships);\r
283         }\r
284 \r
285         private void UpdateDamgedShipNames(IEnumerable<ShipStatus> ships)\r
286         {\r
287             DamagedShipNames =\r
288                 (from s in ships where s.DamageLevel == ShipStatus.Damage.Badly && !s.Escaped select s.Name).ToArray();\r
289             HasDamagedShip = DamagedShipNames.Any();\r
290         }\r
291 \r
292         public void InspectCombinedBattle(dynamic json, bool surfaceFleet)\r
293         {\r
294             InBattle = true;\r
295             Formation = FormationName(json);\r
296             EnemyFighterPower = CalcEnemyFighterPower(json);\r
297             AirControlLevel = CheckAirControlLevel(json);\r
298             _fleet = 10;\r
299             ShowResultCombined(false);\r
300             SetupResult(json);\r
301             if (IsNightBattle(json))\r
302                 CalcHougekiDamage(json.api_hougeki, _guard, _enemyHp);\r
303             else\r
304                 CalcDamage(json, surfaceFleet);\r
305             ClearOverKill(_enemyHp);\r
306             ResultRank = CalcResultRank();\r
307         }\r
308 \r
309         public void InspectCombinedBattleResult(dynamic json)\r
310         {\r
311             _escapingShips.Clear();\r
312             ShowResultCombined();\r
313             CleanupResult();\r
314             if ((int)json.api_escape_flag == 0)\r
315                 return;\r
316             var damaged = (int)json.api_escape.api_escape_idx[0] - 1;\r
317             _escapingShips.Add(_shipInfo.GetDeck(damaged / 6)[damaged % 6]);\r
318             var escort = (int)json.api_escape.api_tow_idx[0] - 1;\r
319             _escapingShips.Add(_shipInfo.GetDeck(escort / 6)[escort % 6]);\r
320         }\r
321 \r
322         private void ShowResultCombined(bool warnDamagedShip = true)\r
323         {\r
324             if (_friend == null)\r
325                 return;\r
326             var ships = _shipInfo.GetShipStatuses(0).Concat(_shipInfo.GetShipStatuses(1)).ToArray();\r
327             foreach (var e in ships.Zip(_friend.Concat(_guard), (ship, now) => new {ship, now}))\r
328                 e.now.UpdateShipStatus(e.ship);\r
329             if (warnDamagedShip)\r
330                 UpdateDamgedShipNames(ships);\r
331         }\r
332 \r
333         public void CauseCombinedBattleEscape()\r
334         {\r
335             _shipInfo.SetEscapedShips(_escapingShips);\r
336             UpdateDamgedShipNames(_shipInfo.GetShipStatuses(0).Concat(_shipInfo.GetShipStatuses(1)));\r
337         }\r
338 \r
339         private class Record\r
340         {\r
341             private ItemInfo _itemInfo;\r
342             private int _maxHp;\r
343             private int[] _slot;\r
344             public int NowHp;\r
345             public int StartHp;\r
346             public int Damage;\r
347 \r
348             public static Record[] Setup(int[] rawHp, int[] rawMax, int[][] slots, ItemInfo itemInfo)\r
349             {\r
350                 var hp = rawHp.Skip(1).Take(6).TakeWhile(h => h != -1).ToArray();\r
351                 var max = rawMax.Skip(1).Take(6).TakeWhile(h => h != -1).ToArray();\r
352                 var r = new Record[hp.Length];\r
353                 for (var i = 0; i < hp.Length; i++)\r
354                 {\r
355                     r[i] = new Record\r
356                     {\r
357                         NowHp = hp[i],\r
358                         StartHp = hp[i],\r
359                         _maxHp = max[i],\r
360                         _slot = slots[i].ToArray(),\r
361                         _itemInfo = itemInfo\r
362                     };\r
363                 }\r
364                 return r;\r
365             }\r
366 \r
367             public void ApplyDamage(int damage)\r
368             {\r
369                 if (NowHp > damage)\r
370                 {\r
371                     NowHp -= damage;\r
372                     Damage += damage;\r
373                     return;\r
374                 }\r
375                 Damage += NowHp;\r
376                 NowHp = 0;\r
377                 for (var j = 0; j < _slot.Length; j++)\r
378                 {\r
379                     var id = _itemInfo[_slot[j]].Id;\r
380                     if (id == 42) // ダメコン\r
381                     {\r
382                         _slot[j] = -1;\r
383                         NowHp = (int)(_maxHp * 0.2);\r
384                         break;\r
385                     }\r
386                     if (id == 43) // 女神\r
387                     {\r
388                         _slot[j] = -1;\r
389                         NowHp = _maxHp;\r
390                         break;\r
391                     }\r
392                 }\r
393             }\r
394 \r
395             public void UpdateShipStatus(ShipStatus ship)\r
396             {\r
397                 ship.NowHp = NowHp;\r
398                 ship.Slot = _slot;\r
399             }\r
400         }\r
401 \r
402         // 以下のコードは航海日誌拡張版の以下のファイルのcalcResultRankを移植したもの\r
403         // https://github.com/nekopanda/logbook/blob/94ceca4be6d4ce79a8759d1ee747fb9827c08edc/main/logbook/dto/BattleExDto.java\r
404         //\r
405         // The MIT License (MIT)\r
406         //\r
407         // Copyright (c) 2013-2014 sanae_hirotaka\r
408         //\r
409         // Permission is hereby granted, free of charge, to any person obtaining a copy\r
410         // of this software and associated documentation files (the "Software"), to deal\r
411         // in the Software without restriction, including without limitation the rights\r
412         // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r
413         // copies of the Software, and to permit persons to whom the Software is\r
414         // furnished to do so, subject to the following conditions:\r
415         //\r
416         // The above copyright notice and this permission notice shall be included in\r
417         // all copies or substantial portions of the Software.\r
418         //\r
419         // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r
420         // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r
421         // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r
422         // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r
423         // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r
424         // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r
425         // THE SOFTWARE.\r
426         //\r
427         private BattleResultRank CalcResultRank()\r
428         {\r
429             var combined = _friend.Concat(_guard).ToArray();\r
430             // 戦闘後に残っている艦数\r
431             var friendNowShips = combined.Count(r => r.NowHp > 0);\r
432             var enemyNowShips = _enemyHp.Count(hp => hp > 0);\r
433             // 総ダメージ\r
434             var friendGauge = combined.Sum(r => r.Damage);\r
435             var enemyGauge = _enemyStartHp.Sum() - _enemyHp.Sum();\r
436             // 轟沈・撃沈数\r
437             var friendSunk = combined.Count(r => r.NowHp == 0);\r
438             var enemySunk = _enemyHp.Count(hp => hp == 0);\r
439 \r
440             var friendGaugeRate = Math.Floor((double)friendGauge / combined.Sum(r => r.StartHp) * 100);\r
441             var enemyGaugeRate = Math.Floor((double)enemyGauge / _enemyStartHp.Sum() * 100);\r
442             var equalOrMore = enemyGaugeRate > (0.9 * friendGaugeRate);\r
443             var superior = enemyGaugeRate > 0 && enemyGaugeRate > (2.5 * friendGaugeRate);\r
444 \r
445             if (friendSunk == 0)\r
446             {\r
447                 if (enemyNowShips == 0)\r
448                 {\r
449                     if (friendGauge == 0)\r
450                         return BattleResultRank.P;\r
451                     return BattleResultRank.S;\r
452                 }\r
453                 if (_enemyHp.Length == 6)\r
454                 {\r
455                     if (enemySunk >= 4)\r
456                         return BattleResultRank.A;\r
457                 }\r
458                 else if (enemySunk * 2 >= _enemyHp.Length)\r
459                 {\r
460                     return BattleResultRank.A;\r
461                 }\r
462                 if (_enemyHp[0] == 0)\r
463                     return BattleResultRank.B;\r
464                 if (superior)\r
465                     return BattleResultRank.B;\r
466             }\r
467             else\r
468             {\r
469                 if (enemyNowShips == 0)\r
470                     return BattleResultRank.B;\r
471                 if (_enemyHp[0] == 0 && friendSunk < enemySunk)\r
472                     return BattleResultRank.B;\r
473                 if (superior)\r
474                     return BattleResultRank.B;\r
475                 if (_enemyHp[0] == 0)\r
476                     return BattleResultRank.C;\r
477             }\r
478             if (enemyGauge > 0 && equalOrMore)\r
479                 return BattleResultRank.C;\r
480             if (friendSunk > 0 && friendNowShips == 1)\r
481                 return BattleResultRank.E;\r
482             return BattleResultRank.D;\r
483         }\r
484     }\r
485 }