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