OSDN Git Service

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