OSDN Git Service

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