OSDN Git Service

航空戦の結果の処理をBattleInfoからAirBattleResultに分離する
[kancollesniffer/KancolleSniffer.git] / KancolleSniffer / Model / 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;\r
16 using System.Collections.Generic;\r
17 using System.Linq;\r
18 using KancolleSniffer.Util;\r
19 using static System.Math;\r
20 \r
21 namespace KancolleSniffer.Model\r
22 {\r
23     public enum BattleResultRank\r
24     {\r
25         P,\r
26         S,\r
27         A,\r
28         B,\r
29         C,\r
30         D,\r
31         E\r
32     }\r
33 \r
34     public enum BattleState\r
35     {\r
36         None,\r
37         Day,\r
38         Night,\r
39         SpNight,\r
40         Result,\r
41         Unknown\r
42     }\r
43 \r
44     public class EnemyFighterPower\r
45     {\r
46         public bool HasUnknown { get; set; }\r
47         public string UnknownMark => HasUnknown ? "+" : "";\r
48         public int AirCombat { get; set; }\r
49         public int Interception { get; set; }\r
50     }\r
51 \r
52     public class BattleInfo : Sniffer.IPort\r
53     {\r
54         private readonly ShipInfo _shipInfo;\r
55         private readonly ItemInfo _itemInfo;\r
56         private readonly AirBase _airBase;\r
57         private Fleet _fleet;\r
58         private Record[] _friend;\r
59         private Record[] _guard;\r
60         private Record[] _enemy;\r
61         private Record[] _enemyGuard;\r
62         private readonly List<int> _escapingShips = new List<int>();\r
63         private bool _lastCell;\r
64 \r
65         public BattleState BattleState { get; set; }\r
66         public int[] Formation { get; private set; }\r
67         public Range FighterPower { get; private set; }\r
68         public EnemyFighterPower EnemyFighterPower { get; private set; }\r
69         public int AirControlLevel { get; private set; }\r
70         public BattleResultRank ResultRank { get; private set; }\r
71         public RankPair DisplayedResultRank { get; } = new RankPair();\r
72         public BattleResult Result { get; set; }\r
73         public bool EnemyIsCombined => _enemyGuard.Length > 0;\r
74         public AirBattleResult AirBattleResult;\r
75         public int SupportType { get; private set; }\r
76 \r
77         public class RankPair\r
78         {\r
79             public char Assumed { get; set; } = 'X';\r
80             public char Actual { get; set; }\r
81             public bool IsError => Assumed != Actual;\r
82         }\r
83 \r
84         public class BattleResult\r
85         {\r
86             public class Combined\r
87             {\r
88                 public ShipStatus[] Main { get; set; }\r
89                 public ShipStatus[] Guard { get; set; }\r
90             }\r
91 \r
92             public Combined Friend { get; set; }\r
93             public Combined Enemy { get; set; }\r
94         }\r
95 \r
96         public BattleInfo(ShipInfo shipInfo, ItemInfo itemInfo, AirBase airBase)\r
97         {\r
98             _shipInfo = shipInfo;\r
99             _itemInfo = itemInfo;\r
100             _airBase = airBase;\r
101             AirBattleResult = new AirBattleResult(GetAirFireShipName, GetItemNames);\r
102         }\r
103 \r
104         private string GetAirFireShipName(int idx)\r
105         {\r
106             return idx < _friend.Length ? _friend[idx].Name : _guard[idx - 6].Name;\r
107         }\r
108 \r
109         private string[] GetItemNames(int[] ids)\r
110         {\r
111             return ids.Select(id => _itemInfo.GetSpecByItemId(id).Name).ToArray();\r
112         }\r
113 \r
114         public void Port()\r
115         {\r
116             CleanupResult();\r
117             BattleState = BattleState.None;\r
118         }\r
119 \r
120         public void InspectBattle(string url, string request, dynamic json)\r
121         {\r
122             SetFormation(json);\r
123             if (BattleState == BattleState.None)\r
124                 SetAirControlLevel(json);\r
125             SetSupportType(json);\r
126             ClearDamagedShipWarning();\r
127             ShowResult(); // 昼戦の結果を夜戦のときに表示する\r
128             SetupDamageRecord(request, json, url.Contains("practice"));\r
129             SetFighterPower();\r
130             SetEnemyFighterPower();\r
131             BattleState = url.Contains("sp_midnight") ? BattleState.SpNight :\r
132                 url.Contains("midnight") ? BattleState.Night : BattleState.Day;\r
133             CalcDamage(json);\r
134             ResultRank = url.Contains("/ld_") ? CalcLdResultRank() : CalcResultRank();\r
135             SetResult();\r
136         }\r
137 \r
138         private void SetFormation(dynamic json)\r
139         {\r
140             if (json.api_formation())\r
141                 Formation = (int[])json.api_formation;\r
142         }\r
143 \r
144         private void SetAirControlLevel(dynamic json)\r
145         {\r
146             AirControlLevel = -1;\r
147             if (!json.api_kouku())\r
148                 return;\r
149             var stage1 = json.api_kouku.api_stage1;\r
150             if (stage1 == null || stage1.api_f_count == 0 && stage1.api_e_count == 0)\r
151                 return;\r
152             AirControlLevel = (int)stage1.api_disp_seiku;\r
153         }\r
154 \r
155         private void SetSupportType(dynamic json)\r
156         {\r
157             SupportType = json.api_support_flag() ? (int)json.api_support_flag :\r
158                 json.api_n_support_flag() ? (int)json.api_n_support_flag : 0;\r
159         }\r
160 \r
161         private void SetupDamageRecord(string request, dynamic json, bool practice)\r
162         {\r
163             if (_friend != null)\r
164                 return;\r
165             _shipInfo.SaveBattleStartStatus();\r
166             SetupFriendDamageRecord(request, json, practice);\r
167             SetupEnemyDamageRecord(json, practice);\r
168         }\r
169 \r
170         private void SetupFriendDamageRecord(string request, dynamic json, bool practice)\r
171         {\r
172             _fleet = _shipInfo.Fleets[(int)json.api_deck_id - 1];\r
173             FlagshipRecovery(request, _fleet.ActualShips[0]);\r
174             _friend = Record.Setup(_fleet.ActualShips, practice);\r
175             _guard = json.api_f_nowhps_combined()\r
176                 ? Record.Setup(_shipInfo.Fleets[1].ActualShips, practice)\r
177                 : new Record[0];\r
178         }\r
179 \r
180         private void SetupEnemyDamageRecord(dynamic json, bool practice)\r
181         {\r
182             _enemy = Record.Setup((int[])json.api_e_nowhps,\r
183                 EnemyShipSpecs(json.api_ship_ke),\r
184                 EnemySlots(json.api_eSlot), practice);\r
185             _enemyGuard = json.api_ship_ke_combined()\r
186                 ? Record.Setup((int[])json.api_e_nowhps_combined,\r
187                     EnemyShipSpecs(json.api_ship_ke_combined),\r
188                     EnemySlots(json.api_eSlot_combined), practice)\r
189                 : new Record[0];\r
190         }\r
191 \r
192         private ShipSpec[] EnemyShipSpecs(dynamic ships)\r
193         {\r
194             return ((int[])ships).Select(_shipInfo.GetSpec).ToArray();\r
195         }\r
196 \r
197         private ItemSpec[][] EnemySlots(dynamic slots)\r
198         {\r
199             return ((int[][])slots).Select(slot => slot.Select(_itemInfo.GetSpecByItemId).ToArray()).ToArray();\r
200         }\r
201 \r
202         private void SetResult()\r
203         {\r
204             Result = new BattleResult\r
205             {\r
206                 Friend = new BattleResult.Combined\r
207                 {\r
208                     Main = _friend.Select(r => r.SnapShot).ToArray(),\r
209                     Guard = _guard.Select(r => r.SnapShot).ToArray()\r
210                 },\r
211                 Enemy = new BattleResult.Combined\r
212                 {\r
213                     Main = _enemy.Select(r => r.SnapShot).ToArray(),\r
214                     Guard = _enemyGuard.Select(r => r.SnapShot).ToArray()\r
215                 }\r
216             };\r
217         }\r
218 \r
219         private void FlagshipRecovery(string request, ShipStatus flagship)\r
220         {\r
221             var type = int.Parse(HttpUtility.ParseQueryString(request)["api_recovery_type"] ?? "0");\r
222             switch (type)\r
223             {\r
224                 case 0:\r
225                     return;\r
226                 case 1:\r
227                     flagship.NowHp = flagship.MaxHp / 2;\r
228                     ConsumeSlotItem(flagship, 42); // ダメコン\r
229                     break;\r
230                 case 2:\r
231                     flagship.NowHp = flagship.MaxHp;\r
232                     ConsumeSlotItem(flagship, 43); // 女神\r
233                     break;\r
234             }\r
235             if (type != 0)\r
236                 _shipInfo.SetBadlyDamagedShips();\r
237         }\r
238 \r
239         private static void ConsumeSlotItem(ShipStatus ship, int id)\r
240         {\r
241             if (ship.SlotEx.Spec.Id == id)\r
242             {\r
243                 ship.SlotEx = new ItemStatus();\r
244                 return;\r
245             }\r
246             for (var i = 0; i < ship.Slot.Count; i++)\r
247             {\r
248                 if (ship.Slot[i].Spec.Id == id)\r
249                 {\r
250                     ship.FreeSlot(i);\r
251                     break;\r
252                 }\r
253             }\r
254         }\r
255 \r
256         private void CleanupResult()\r
257         {\r
258             _friend = null;\r
259             _lastCell = false;\r
260         }\r
261 \r
262         private void SetFighterPower()\r
263         {\r
264             var fleets = _shipInfo.Fleets;\r
265             FighterPower = _guard.Length > 0 && _enemyGuard.Length > 0\r
266                 ? fleets[0].FighterPower + fleets[1].FighterPower\r
267                 : _fleet.FighterPower;\r
268         }\r
269 \r
270         private void SetEnemyFighterPower()\r
271         {\r
272             EnemyFighterPower = new EnemyFighterPower();\r
273             foreach (var record in _guard.Length == 0 ? _enemy : _enemy.Concat(_enemyGuard))\r
274             {\r
275                 var ship = record.SnapShot;\r
276                 if (ship.Spec.MaxEq == null)\r
277                 {\r
278                     EnemyFighterPower.HasUnknown = true;\r
279                     continue;\r
280                 }\r
281                 foreach (var entry in ship.Slot.Zip(ship.Spec.MaxEq, (item, maxEq) => new {item.Spec, maxEq}))\r
282                 {\r
283                     var perSlot = (int)Floor(entry.Spec.AntiAir * Sqrt(entry.maxEq));\r
284                     if (entry.Spec.CanAirCombat)\r
285                         EnemyFighterPower.AirCombat += perSlot;\r
286                     if (entry.Spec.IsAircraft)\r
287                         EnemyFighterPower.Interception += perSlot;\r
288                 }\r
289             }\r
290         }\r
291 \r
292         public void InspectMapStart(dynamic json)\r
293         {\r
294             InspectMapNext(json);\r
295         }\r
296 \r
297         public void InspectMapNext(dynamic json)\r
298         {\r
299             _lastCell = (int)json.api_next == 0;\r
300 \r
301             if (!json.api_destruction_battle())\r
302                 return;\r
303             InspectAirRaidBattle((int)json.api_maparea_id, json.api_destruction_battle);\r
304         }\r
305 \r
306         public void InspectAirRaidBattle(int areaId, dynamic json)\r
307         {\r
308             SetFormation(json);\r
309             var attack = json.api_air_base_attack;\r
310             var stage1 = attack.api_stage1;\r
311             AirControlLevel = (int)stage1.api_disp_seiku;\r
312             var ships = (ShipStatus[])CreateShipsForAirBase(json);\r
313             _friend = Record.Setup(ships, false);\r
314             _guard = new Record[0];\r
315             FighterPower = _airBase.GetAirBase(areaId).CalcInterceptionFighterPower();\r
316             SetupEnemyDamageRecord(json, false);\r
317             SetEnemyFighterPower();\r
318             BattleState = BattleState.Day;\r
319             AirBattleResult.Add(json.api_air_base_attack, "空襲");\r
320             CalcKoukuDamage(json.api_air_base_attack);\r
321             SetAirRaidResultRank(json);\r
322             SetResult();\r
323             CleanupResult();\r
324         }\r
325 \r
326         private ShipStatus[] CreateShipsForAirBase(dynamic json)\r
327         {\r
328             var nowHps = (int[])json.api_f_nowhps;\r
329             var maxHps = (int[])json.api_f_maxhps;\r
330             var maxEq = new[] {18, 18, 18, 18};\r
331             var ships = nowHps.Select((hp, n) => new ShipStatus\r
332             {\r
333                 Id = 1,\r
334                 Spec = new ShipSpec {Name = "基地航空隊" + (n + 1), GetMaxEq = () => maxEq},\r
335                 NowHp = nowHps[n],\r
336                 MaxHp = maxHps[n]\r
337             }).ToArray();\r
338             var planes = json.api_air_base_attack.api_map_squadron_plane;\r
339             if (planes == null)\r
340                 return ships;\r
341             foreach (KeyValuePair<string, dynamic> entry in planes)\r
342             {\r
343                 var num = int.Parse(entry.Key) - 1;\r
344                 var slot = new List<ItemStatus>();\r
345                 var onSlot = new List<int>();\r
346                 foreach (var plane in entry.Value)\r
347                 {\r
348                     slot.Add(new ItemStatus {Id = 1, Spec = _itemInfo.GetSpecByItemId((int)plane.api_mst_id)});\r
349                     onSlot.Add((int)plane.api_count);\r
350                 }\r
351                 ships[num].Slot = slot;\r
352                 ships[num].OnSlot = onSlot.ToArray();\r
353             }\r
354             return ships;\r
355         }\r
356 \r
357         private void SetAirRaidResultRank(dynamic json)\r
358         {\r
359             switch ((int)json.api_lost_kind)\r
360             {\r
361                 case 1:\r
362                     ResultRank = BattleResultRank.A;\r
363                     break;\r
364                 case 2:\r
365                     ResultRank = BattleResultRank.B;\r
366                     break;\r
367                 case 3:\r
368                     ResultRank = BattleResultRank.C;\r
369                     break;\r
370                 case 4:\r
371                     ResultRank = BattleResultRank.S;\r
372                     break;\r
373             }\r
374         }\r
375 \r
376         private void CalcDamage(dynamic json)\r
377         {\r
378             AirBattleResult.Clear();\r
379             foreach (KeyValuePair<string, dynamic> kv in json)\r
380             {\r
381                 if (kv.Value == null)\r
382                     continue;\r
383                 switch (kv.Key)\r
384                 {\r
385                     case "api_air_base_injection":\r
386                         AirBattleResult.Add(kv.Value, "AB噴式");\r
387                         CalcKoukuDamage(kv.Value);\r
388                         break;\r
389                     case "api_injection_kouku":\r
390                         AirBattleResult.Add(kv.Value, "噴式");\r
391                         CalcKoukuDamage(kv.Value);\r
392                         break;\r
393                     case "api_air_base_attack":\r
394                         CalcAirBaseAttackDamage(kv.Value);\r
395                         break;\r
396                     case "api_n_support_info":\r
397                         CalcSupportDamage(kv.Value);\r
398                         break;\r
399                     case "api_n_hougeki1":\r
400                         CalcDamageByTurn(kv.Value);\r
401                         break;\r
402                     case "api_n_hougeki2":\r
403                         CalcDamageByTurn(kv.Value);\r
404                         break;\r
405                     case "api_kouku":\r
406                         AirBattleResult.Add(kv.Value, "航空戦");\r
407                         CalcKoukuDamage(kv.Value);\r
408                         break;\r
409                     case "api_kouku2":\r
410                         AirBattleResult.Add(kv.Value, "航空戦2");\r
411                         CalcKoukuDamage(kv.Value);\r
412                         break;\r
413                     case "api_support_info":\r
414                         CalcSupportDamage(kv.Value);\r
415                         break;\r
416                     case "api_opening_taisen":\r
417                         CalcDamageByTurn(kv.Value);\r
418                         break;\r
419                     case "api_opening_atack":\r
420                         CalcDamageAtOnce(kv.Value);\r
421                         break;\r
422                     case "api_friendly_battle":\r
423                         CalcFriendAttackDamage(kv.Value);\r
424                         break;\r
425                     case "api_hougeki":\r
426                         CalcDamageByTurn(kv.Value);\r
427                         break;\r
428                     case "api_hougeki1":\r
429                         CalcDamageByTurn(kv.Value);\r
430                         break;\r
431                     case "api_hougeki2":\r
432                         CalcDamageByTurn(kv.Value);\r
433                         break;\r
434                     case "api_hougeki3":\r
435                         CalcDamageByTurn(kv.Value);\r
436                         break;\r
437                     case "api_raigeki":\r
438                         CalcDamageAtOnce(kv.Value);\r
439                         break;\r
440                 }\r
441             }\r
442         }\r
443 \r
444         private void CalcSupportDamage(dynamic json)\r
445         {\r
446             if (json.api_support_hourai != null)\r
447             {\r
448                 CalcRawDamageAtOnce(json.api_support_hourai.api_damage, _enemy, _enemyGuard);\r
449             }\r
450             else if (json.api_support_airatack != null)\r
451             {\r
452                 CalcRawDamageAtOnce(json.api_support_airatack.api_stage3.api_edam, _enemy, _enemyGuard);\r
453             }\r
454         }\r
455 \r
456         private void CalcAirBaseAttackDamage(dynamic json)\r
457         {\r
458             var i = 1;\r
459             foreach (var entry in json)\r
460             {\r
461                 AirBattleResult.Add(entry, "基地" + i++);\r
462                 CalcKoukuDamage(entry);\r
463             }\r
464         }\r
465 \r
466         private void CalcFriendAttackDamage(dynamic json)\r
467         {\r
468             CalcDamageByTurn(json.api_hougeki, true);\r
469         }\r
470 \r
471         private void CalcKoukuDamage(dynamic json)\r
472         {\r
473             if (json.api_stage3() && json.api_stage3 != null)\r
474                 CalcDamageAtOnce(json.api_stage3, _friend, _enemy);\r
475             if (json.api_stage3_combined() && json.api_stage3_combined != null)\r
476                 CalcDamageAtOnce(json.api_stage3_combined, _guard, _enemyGuard);\r
477         }\r
478 \r
479         private void CalcDamageAtOnce(dynamic json)\r
480         {\r
481             CalcDamageAtOnce(json, _friend, _guard, _enemy, _enemyGuard);\r
482         }\r
483 \r
484         private void CalcDamageAtOnce(dynamic json, Record[] friend, Record[] enemy)\r
485         {\r
486             CalcDamageAtOnce(json, friend, null, enemy, null);\r
487         }\r
488 \r
489         private void CalcDamageAtOnce(dynamic json,\r
490             Record[] friend, Record[] guard, Record[] enemy, Record[] enemyGuard)\r
491         {\r
492             if (json.api_fdam() && json.api_fdam != null)\r
493                 CalcRawDamageAtOnce(json.api_fdam, friend, guard);\r
494             if (json.api_edam() && json.api_edam != null)\r
495                 CalcRawDamageAtOnce(json.api_edam, enemy, enemyGuard);\r
496         }\r
497 \r
498         private void CalcRawDamageAtOnce(dynamic rawDamage, Record[] friend, Record[] guard = null)\r
499         {\r
500             var damage = (int[])rawDamage;\r
501             for (var i = 0; i < friend.Length; i++)\r
502             {\r
503                 friend[i].ApplyDamage(damage[i]);\r
504                 friend[i].CheckDamageControl();\r
505             }\r
506             if (guard == null)\r
507                 return;\r
508             for (var i = 0; i < guard.Length; i++)\r
509             {\r
510                 guard[i].ApplyDamage(damage[i + 6]);\r
511                 guard[i].CheckDamageControl();\r
512             }\r
513         }\r
514 \r
515         private void CalcDamageByTurn(dynamic json, bool ignoreFriendDamage = false)\r
516         {\r
517             if (!(json.api_df_list() && json.api_df_list != null &&\r
518                   json.api_damage() && json.api_damage != null &&\r
519                   json.api_at_eflag() && json.api_at_eflag != null))\r
520                 return;\r
521 \r
522             var eFlags = (int[])json.api_at_eflag;\r
523             var sources = (int[])json.api_at_list;\r
524             var types = json.api_at_type() ? (int[])json.api_at_type : (int[])json.api_sp_list;\r
525             var targets = (int[][])json.api_df_list;\r
526             var damages = (int[][])json.api_damage;\r
527             var records = new BothRecord(_friend, _guard, _enemy, _enemyGuard);\r
528             for (var turn = 0; turn < eFlags.Length; turn++)\r
529             {\r
530                 if (ignoreFriendDamage && eFlags[turn] == 1)\r
531                     continue;\r
532                 if (IsSpecialAttack(types[turn]))\r
533                     records.TriggerSpecialAttack(eFlags[turn] ^ 1, sources[turn]);\r
534                 for (var shot = 0; shot < targets[turn].Length; shot++)\r
535                 {\r
536                     var target = targets[turn][shot];\r
537                     var damage = damages[turn][shot];\r
538                     if (target == -1 || damage == -1)\r
539                         continue;\r
540                     records.ApplyDamage(eFlags[turn], target, damage);\r
541                 }\r
542                 records.CheckDamageControl();\r
543             }\r
544         }\r
545 \r
546         private bool IsSpecialAttack(int type)\r
547         {\r
548             // 100: Nelson Touch\r
549             // 101: 長門一斉射\r
550             // 102: 陸奥一斉射\r
551             // 200: 瑞雲一体攻撃\r
552             // 201: 海陸立体攻撃\r
553             return type >= 100 && type < 200;\r
554         }\r
555 \r
556         private class BothRecord\r
557         {\r
558             private readonly Record[][] _records;\r
559 \r
560             public BothRecord(Record[] friend, Record[] guard, Record[] enemy, Record[] enemyGuard)\r
561             {\r
562                 _records = new[] {new Record[12], new Record[12]};\r
563                 Array.Copy(friend, _records[1], friend.Length);\r
564                 Array.Copy(guard, 0, _records[1], 6, guard.Length);\r
565                 Array.Copy(enemy, _records[0], enemy.Length);\r
566                 Array.Copy(enemyGuard, 0, _records[0], 6, enemyGuard.Length);\r
567             }\r
568 \r
569             public void TriggerSpecialAttack(int side, int index)\r
570             {\r
571                 _records[side][index].TriggerSpecialAttack();\r
572             }\r
573 \r
574             public void ApplyDamage(int side, int index, int damage)\r
575             {\r
576                 _records[side][index].ApplyDamage(damage);\r
577             }\r
578 \r
579             public void CheckDamageControl()\r
580             {\r
581                 foreach (var ship in _records[1])\r
582                     ship?.CheckDamageControl();\r
583             }\r
584         }\r
585 \r
586         public void InspectBattleResult(dynamic json)\r
587         {\r
588             BattleState = BattleState.Result;\r
589             if (_friend == null)\r
590                 return;\r
591             ShowResult();\r
592             if (!_lastCell)\r
593                 SetDamagedShipWarning();\r
594             _shipInfo.SaveBattleResult();\r
595             _shipInfo.DropShipId = json.api_get_ship() ? (int)json.api_get_ship.api_ship_id : -1;\r
596             VerifyResultRank(json);\r
597             CleanupResult();\r
598             SetEscapeShips(json);\r
599         }\r
600 \r
601         public void InspectPracticeResult(dynamic json)\r
602         {\r
603             BattleState = BattleState.Result;\r
604             if (_friend == null)\r
605                 return;\r
606             ShowResult();\r
607             VerifyResultRank(json);\r
608             CleanupResult();\r
609         }\r
610 \r
611         private void ShowResult()\r
612         {\r
613             if (_friend == null)\r
614                 return;\r
615             var fleets = _shipInfo.Fleets;\r
616             var ships = _guard.Length > 0\r
617                 ? fleets[0].ActualShips.Concat(fleets[1].ActualShips)\r
618                 : _fleet.ActualShips;\r
619             foreach (var entry in ships.Zip(_friend.Concat(_guard), (ship, now) => new {ship, now}))\r
620                 entry.now.UpdateShipStatus(entry.ship);\r
621         }\r
622 \r
623         private void SetDamagedShipWarning()\r
624         {\r
625             _shipInfo.SetBadlyDamagedShips();\r
626         }\r
627 \r
628         private void ClearDamagedShipWarning()\r
629         {\r
630             _shipInfo.ClearBadlyDamagedShips();\r
631         }\r
632 \r
633         private void VerifyResultRank(dynamic json)\r
634         {\r
635             if (!json.api_win_rank())\r
636                 return;\r
637             var assumed = "PSABCDE"[(int)ResultRank];\r
638             if (assumed == 'P')\r
639                 assumed = 'S';\r
640             var actual = ((string)json.api_win_rank)[0];\r
641             DisplayedResultRank.Assumed = assumed;\r
642             DisplayedResultRank.Actual = actual;\r
643         }\r
644 \r
645         public void SetEscapeShips(dynamic json)\r
646         {\r
647             _escapingShips.Clear();\r
648             if (!json.api_escape_flag() || (int)json.api_escape_flag == 0)\r
649                 return;\r
650             var damaged = (int)json.api_escape.api_escape_idx[0] - 1;\r
651             if (json.api_escape.api_tow_idx())\r
652             {\r
653                 _escapingShips.Add(_shipInfo.Fleets[damaged / 6].Deck[damaged % 6]);\r
654                 var escort = (int)json.api_escape.api_tow_idx[0] - 1;\r
655                 _escapingShips.Add(_shipInfo.Fleets[escort / 6].Deck[escort % 6]);\r
656             }\r
657             else\r
658             {\r
659                 _escapingShips.Add(_shipInfo.Fleets[2].Deck[damaged]);\r
660             }\r
661         }\r
662 \r
663         public void CauseEscape()\r
664         {\r
665             _shipInfo.SetEscapedShips(_escapingShips);\r
666             _shipInfo.SetBadlyDamagedShips();\r
667         }\r
668 \r
669         private class Record\r
670         {\r
671             private ShipStatus _status;\r
672             private bool _practice;\r
673             public ShipStatus SnapShot => (ShipStatus)_status.Clone();\r
674             public int NowHp => _status.NowHp;\r
675             public bool Escaped => _status.Escaped;\r
676             public ShipStatus.Damage DamageLevel => _status.DamageLevel;\r
677             public string Name => _status.Name;\r
678             public int StartHp { get; private set; }\r
679 \r
680             public static Record[] Setup(IEnumerable<ShipStatus> ships, bool practice) =>\r
681                 (from s in ships\r
682                     select new Record {_status = (ShipStatus)s.Clone(), _practice = practice, StartHp = s.NowHp})\r
683                 .ToArray();\r
684 \r
685             public static Record[] Setup(int[] nowHps, ShipSpec[] specs, ItemSpec[][] slots, bool practice)\r
686             {\r
687                 return Enumerable.Range(0, nowHps.Length).Select(i =>\r
688                     new Record\r
689                     {\r
690                         StartHp = nowHps[i],\r
691                         _status = new ShipStatus\r
692                         {\r
693                             Id = specs[i].Id,\r
694                             NowHp = nowHps[i],\r
695                             MaxHp = nowHps[i],\r
696                             Spec = specs[i],\r
697                             Slot = slots[i].Select(spec => new ItemStatus {Id = spec.Id, Spec = spec}).ToArray(),\r
698                             SlotEx = new ItemStatus(0)\r
699                         },\r
700                         _practice = practice\r
701                     }).ToArray();\r
702             }\r
703 \r
704             public void TriggerSpecialAttack()\r
705             {\r
706                 _status.SpecialAttack = ShipStatus.Attack.Fire;\r
707             }\r
708 \r
709             public void ApplyDamage(int damage)\r
710             {\r
711                 _status.NowHp = Max(0, _status.NowHp - damage);\r
712             }\r
713 \r
714             public void CheckDamageControl()\r
715             {\r
716                 if (_status.NowHp > 0 || _practice)\r
717                     return;\r
718                 foreach (var item in new[] {_status.SlotEx}.Concat(_status.Slot))\r
719                 {\r
720                     if (item.Spec.Id == 42)\r
721                     {\r
722                         _status.NowHp = (int)(_status.MaxHp * 0.2);\r
723                         ConsumeSlotItem(_status, 42);\r
724                         break;\r
725                     }\r
726                     if (item.Spec.Id == 43)\r
727                     {\r
728                         _status.NowHp = _status.MaxHp;\r
729                         ConsumeSlotItem(_status, 43);\r
730                         break;\r
731                     }\r
732                 }\r
733             }\r
734 \r
735             public void UpdateShipStatus(ShipStatus ship)\r
736             {\r
737                 ship.NowHp = NowHp;\r
738                 ship.Slot = _status.Slot;\r
739                 ship.SlotEx = _status.SlotEx;\r
740                 ship.SpecialAttack = _status.SpecialAttack == ShipStatus.Attack.Fire\r
741                     ? ShipStatus.Attack.Fired\r
742                     : ShipStatus.Attack.None;\r
743             }\r
744         }\r
745 \r
746         private BattleResultRank CalcLdResultRank()\r
747         {\r
748             var friend = new ResultRankParams(_friend.Concat(_guard).ToArray());\r
749 \r
750             if (friend.Gauge <= 0)\r
751                 return BattleResultRank.P;\r
752             if (friend.GaugeRate < 10)\r
753                 return BattleResultRank.A;\r
754             if (friend.GaugeRate < 20)\r
755                 return BattleResultRank.B;\r
756             if (friend.GaugeRate < 50)\r
757                 return BattleResultRank.C;\r
758             if (friend.GaugeRate < 80)\r
759                 return BattleResultRank.D;\r
760             return BattleResultRank.E;\r
761         }\r
762 \r
763         private BattleResultRank CalcResultRank()\r
764         {\r
765             var friend = new ResultRankParams(_friend.Concat(_guard).ToArray());\r
766             var enemy = new ResultRankParams(_enemy.Concat(_enemyGuard).ToArray());\r
767             if (friend.Sunk == 0 && enemy.Sunk == enemy.Count)\r
768             {\r
769                 if (friend.Gauge <= 0)\r
770                     return BattleResultRank.P;\r
771                 return BattleResultRank.S;\r
772             }\r
773             if (friend.Sunk == 0 && enemy.Sunk >= (int)(enemy.Count * 0.7) && enemy.Count > 1)\r
774                 return BattleResultRank.A;\r
775             if (friend.Sunk < enemy.Sunk && _enemy[0].NowHp == 0)\r
776                 return BattleResultRank.B;\r
777             if (friend.Count == 1 && _friend[0].DamageLevel == ShipStatus.Damage.Badly)\r
778                 return BattleResultRank.D;\r
779             if (enemy.GaugeRate > friend.GaugeRate * 2.5)\r
780                 return BattleResultRank.B;\r
781             if (enemy.GaugeRate > friend.GaugeRate * 0.9)\r
782                 return BattleResultRank.C;\r
783             if (friend.Count > 1 && friend.Count - 1 == friend.Sunk)\r
784                 return BattleResultRank.E;\r
785             return BattleResultRank.D;\r
786         }\r
787 \r
788         private class ResultRankParams\r
789         {\r
790             public readonly int Count;\r
791             public readonly int Sunk;\r
792             public readonly int Gauge;\r
793             public readonly int GaugeRate;\r
794 \r
795             public ResultRankParams(Record[] records)\r
796             {\r
797                 var staying = records.Where(r => !r.Escaped).ToArray();\r
798                 Count = records.Length;\r
799                 Sunk = staying.Count(r => r.NowHp == 0);\r
800                 Gauge = staying.Sum(r => r.StartHp - r.NowHp);\r
801                 GaugeRate = (int)((double)Gauge / records.Sum(r => r.StartHp) * 100);\r
802             }\r
803         }\r
804 \r
805         /// <summary>\r
806         /// テスト専用\r
807         /// </summary>\r
808         public void InjectResultStatus(ShipStatus[] main, ShipStatus[] guard, ShipStatus[] enemy,\r
809             ShipStatus[] enemyGuard)\r
810         {\r
811             Result = new BattleResult\r
812             {\r
813                 Friend = new BattleResult.Combined {Main = main, Guard = guard},\r
814                 Enemy = new BattleResult.Combined {Main = enemy, Guard = enemyGuard}\r
815             };\r
816         }\r
817     }\r
818 }