OSDN Git Service

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