OSDN Git Service

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