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