OSDN Git Service

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