OSDN Git Service

編成指定のある出撃任務のカウンタを実装する
[kancollesniffer/KancolleSniffer.git] / KancolleSniffer / BattleInfo.cs
1 // Copyright (C) 2014, 2015 Kazuhiro Fujieda <fujieda@users.osdn.me>\r
2 // \r
3 // Licensed under the Apache License, Version 2.0 (the "License");\r
4 // you may not use this file except in compliance with the License.\r
5 // You may obtain a copy of the License at\r
6 //\r
7 //    http://www.apache.org/licenses/LICENSE-2.0\r
8 //\r
9 // Unless required by applicable law or agreed to in writing, software\r
10 // distributed under the License is distributed on an "AS IS" BASIS,\r
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
12 // See the License for the specific language governing permissions and\r
13 // limitations under the License.\r
14 \r
15 using System;\r
16 using System.Collections.Generic;\r
17 using System.Linq;\r
18 using static System.Math;\r
19 \r
20 namespace KancolleSniffer\r
21 {\r
22     public enum BattleResultRank\r
23     {\r
24         P,\r
25         S,\r
26         A,\r
27         B,\r
28         C,\r
29         D,\r
30         E\r
31     }\r
32 \r
33     public enum BattleState\r
34     {\r
35         None,\r
36         Day,\r
37         Night,\r
38         Result,\r
39         Unknown\r
40     }\r
41 \r
42     public class EnemyFighterPower\r
43     {\r
44         public bool HasUnknown { get; set; }\r
45         public string UnknownMark => HasUnknown ? "+" : "";\r
46         public int AirCombat { get; set; }\r
47         public int Interception { get; set; }\r
48     }\r
49 \r
50     public class BattleInfo\r
51     {\r
52         private readonly ShipInfo _shipInfo;\r
53         private readonly ItemInfo _itemInfo;\r
54         private int _fleet;\r
55         private Record[] _friend;\r
56         private Record[] _guard;\r
57         private Record[] _enemy;\r
58         private Record[] _enemyGuard;\r
59         private readonly List<int> _escapingShips = new List<int>();\r
60         private bool _lastCell;\r
61 \r
62         public BattleState BattleState { get; set; }\r
63         public int[] Formation { get; private set; }\r
64         public int[] FighterPower { get; private set; }\r
65         public EnemyFighterPower EnemyFighterPower { get; private set; }\r
66         public int AirControlLevel { get; private set; }\r
67         public BattleResultRank ResultRank { get; private set; }\r
68         public RankPair DisplayedResultRank { get; } = new RankPair();\r
69         public BattleResult Result { get; set; }\r
70         public bool EnemyIsCombined => _enemyGuard.Length > 0;\r
71         public List<AirBattleResult> AirBattleResults { get; } = new List<AirBattleResult>();\r
72 \r
73         public class RankPair\r
74         {\r
75             public char Assumed { get; set; }\r
76             public char Actual { get; set; }\r
77             public bool IsError => Assumed != Actual;\r
78         }\r
79 \r
80         public class BattleResult\r
81         {\r
82             public class Combined\r
83             {\r
84                 public ShipStatus[] Main { get; set; }\r
85                 public ShipStatus[] Guard { get; set; }\r
86             }\r
87 \r
88             public Combined Friend { get; set; }\r
89             public Combined Enemy { get; set; }\r
90         }\r
91 \r
92         public BattleInfo(ShipInfo shipInfo, ItemInfo itemInfo)\r
93         {\r
94             _shipInfo = shipInfo;\r
95             _itemInfo = itemInfo;\r
96         }\r
97 \r
98         public void InspectBattle(string url, string request, dynamic json)\r
99         {\r
100             if (json.api_formation())\r
101                 Formation = ((dynamic[])json.api_formation).Select(f => f is string ? (int)int.Parse(f) : (int)f)\r
102                     .ToArray();\r
103             AirControlLevel = CheckAirControlLevel(json);\r
104             ShowResult(false); // 昼戦の結果を夜戦のときに表示する\r
105             SetupResult(request, json, url.Contains("practice"));\r
106             FighterPower = CalcFighterPower();\r
107             EnemyFighterPower = CalcEnemyFighterPower(json);\r
108             BattleState = IsNightBattle(json) ? BattleState.Night : BattleState.Day;\r
109             CalcDamage(json);\r
110             ResultRank = url.EndsWith("ld_airbattle") ? CalcLdAirBattleRank() : CalcResultRank();\r
111             SetResult();\r
112         }\r
113 \r
114         private bool IsNightBattle(dynamic json) => json.api_hougeki();\r
115 \r
116         public static int DeckId(dynamic json)\r
117         {\r
118             if (json.api_dock_id()) // 昼戦はtypoしている\r
119                 return (int)json.api_dock_id - 1;\r
120             if (json.api_deck_id is string) // 通常の夜戦と連合艦隊(味方のみ)では文字列\r
121                 return int.Parse(json.api_deck_id) - 1;\r
122             return (int)json.api_deck_id - 1;\r
123         }\r
124 \r
125         private void SetupResult(string request, dynamic json, bool practice)\r
126         {\r
127             if (_friend != null)\r
128                 return;\r
129             _shipInfo.SaveBattleStartStatus();\r
130             _fleet = DeckId(json);\r
131             var fstats = _shipInfo.GetShipStatuses(_fleet);\r
132             FlagshipRecovery(request, fstats[0]);\r
133             _friend = Record.Setup(fstats, practice);\r
134             _guard = json.api_f_nowhps_combined()\r
135                 ? Record.Setup(_shipInfo.GetShipStatuses(1), practice)\r
136                 : new Record[0];\r
137             _enemy = Record.Setup((int[])json.api_e_nowhps,\r
138                 ((int[])json.api_ship_ke).Select(_shipInfo.GetSpec).ToArray(),\r
139                 ((int[][])json.api_eSlot).Select(slot => slot.Select(_itemInfo.GetSpecByItemId).ToArray()).ToArray(),\r
140                 practice);\r
141             _enemyGuard = json.api_ship_ke_combined()\r
142                 ? Record.Setup((int[])json.api_e_nowhps_combined,\r
143                     ((int[])json.api_ship_ke_combined).Select(_shipInfo.GetSpec).ToArray(),\r
144                     ((int[][])json.api_eSlot).Select(slot => slot.Select(_itemInfo.GetSpecByItemId).ToArray())\r
145                     .ToArray(), practice)\r
146                 : new Record[0];\r
147         }\r
148 \r
149         private void SetResult()\r
150         {\r
151             Result = new BattleResult\r
152             {\r
153                 Friend = new BattleResult.Combined\r
154                 {\r
155                     Main = _friend.Select(r => r.SnapShot).ToArray(),\r
156                     Guard = _guard.Select(r => r.SnapShot).ToArray()\r
157                 },\r
158                 Enemy = new BattleResult.Combined\r
159                 {\r
160                     Main = _enemy.Select(r => r.SnapShot).ToArray(),\r
161                     Guard = _enemyGuard.Select(r => r.SnapShot).ToArray()\r
162                 }\r
163             };\r
164         }\r
165 \r
166         private void FlagshipRecovery(string request, ShipStatus flagship)\r
167         {\r
168             var type = int.Parse(HttpUtility.ParseQueryString(request)["api_recovery_type"] ?? "0");\r
169             switch (type)\r
170             {\r
171                 case 0:\r
172                     return;\r
173                 case 1:\r
174                     flagship.NowHp = flagship.MaxHp / 2;\r
175                     ConsumeSlotItem(flagship, 42); // ダメコン\r
176                     break;\r
177                 case 2:\r
178                     flagship.NowHp = flagship.MaxHp;\r
179                     ConsumeSlotItem(flagship, 43); // 女神\r
180                     break;\r
181             }\r
182             if (type != 0)\r
183                 _shipInfo.SetBadlyDamagedShips();\r
184         }\r
185 \r
186         private static void ConsumeSlotItem(ShipStatus ship, int id)\r
187         {\r
188             if (ship.SlotEx.Spec.Id == id)\r
189             {\r
190                 ship.SlotEx = new ItemStatus();\r
191                 return;\r
192             }\r
193             for (var i = 0; i < ship.Slot.Length; i++)\r
194             {\r
195                 if (ship.Slot[i].Spec.Id == id)\r
196                 {\r
197                     ship.Slot[i] = new ItemStatus();\r
198                     break;\r
199                 }\r
200             }\r
201         }\r
202 \r
203         public void CleanupResult()\r
204         {\r
205             _friend = null;\r
206             _lastCell = false;\r
207         }\r
208 \r
209         private int CheckAirControlLevel(dynamic json)\r
210         {\r
211             if (!json.api_kouku())\r
212                 return -1;\r
213             var stage1 = json.api_kouku.api_stage1;\r
214             if (stage1 == null)\r
215                 return -1;\r
216             if (stage1.api_f_count == 0 && stage1.api_e_count == 0)\r
217                 return -1;\r
218             return (int)stage1.api_disp_seiku;\r
219         }\r
220 \r
221         private int[] CalcFighterPower()\r
222         {\r
223             if (_guard.Length > 0 && _enemyGuard.Length > 0)\r
224                 return _shipInfo.GetFighterPower(0).Zip(_shipInfo.GetFighterPower(1), (a, b) => a + b).ToArray();\r
225             return _shipInfo.GetFighterPower(_fleet);\r
226         }\r
227 \r
228         private EnemyFighterPower CalcEnemyFighterPower(dynamic json)\r
229         {\r
230             var result = new EnemyFighterPower();\r
231             var ships = (int[])json.api_ship_ke;\r
232             if (json.api_ship_ke_combined() && _guard.Length > 0)\r
233                 ships = ships.Concat((int[])json.api_ship_ke_combined).ToArray();\r
234             var maxEq = ships.SelectMany(id =>\r
235             {\r
236                 var r = _shipInfo.GetSpec(id).MaxEq;\r
237                 if (r != null)\r
238                     return r;\r
239                 result.HasUnknown = true;\r
240                 return new int[5];\r
241             });\r
242             var equips = ((int[][])json.api_eSlot).SelectMany(x => x);\r
243             if (json.api_eSlot_combined() && _guard.Length > 0)\r
244                 equips = equips.Concat(((int[][])json.api_eSlot_combined).SelectMany(x => x));\r
245             foreach (var entry in from slot in equips.Zip(maxEq, (id, max) => new {id, max})\r
246                 let spec = _itemInfo.GetSpecByItemId(slot.id)\r
247                 let perSlot = (int)Floor(spec.AntiAir * Sqrt(slot.max))\r
248                 select new {spec, perSlot})\r
249             {\r
250                 if (entry.spec.CanAirCombat)\r
251                     result.AirCombat += entry.perSlot;\r
252                 if (entry.spec.IsAircraft)\r
253                     result.Interception += entry.perSlot;\r
254             }\r
255             return result;\r
256         }\r
257 \r
258         private enum CombatType\r
259         {\r
260             AtOnce,\r
261             ByTurn,\r
262             Support,\r
263             Aircraft,\r
264             AirBase,\r
265             Friend\r
266         }\r
267 \r
268         private class Phase\r
269         {\r
270             public string Api { get; }\r
271             public CombatType Type { get; }\r
272             public string Name { get; }\r
273 \r
274             public Phase(string api, CombatType type, string name = "")\r
275             {\r
276                 Api = api;\r
277                 Type = type;\r
278                 Name = name;\r
279             }\r
280         }\r
281 \r
282         private void CalcDamage(dynamic json)\r
283         {\r
284             AirBattleResults.Clear();\r
285             var phases = new[]\r
286             {\r
287                 new Phase("air_base_injection", CombatType.Aircraft, "AB噴式"),\r
288                 new Phase("injection_kouku", CombatType.Aircraft, "噴式"),\r
289                 new Phase("air_base_attack", CombatType.AirBase),\r
290                 new Phase("n_support_info", CombatType.Support),\r
291                 new Phase("n_hougeki1", CombatType.ByTurn),\r
292                 new Phase("n_hougeki2", CombatType.ByTurn),\r
293                 new Phase("kouku", CombatType.Aircraft, "航空戦"),\r
294                 new Phase("kouku2", CombatType.Aircraft, "航空戦2"),\r
295                 new Phase("support_info", CombatType.Support),\r
296                 new Phase("opening_taisen", CombatType.ByTurn),\r
297                 new Phase("opening_atack", CombatType.AtOnce),\r
298                 new Phase("friendly_battle", CombatType.Friend),\r
299                 new Phase("hougeki", CombatType.ByTurn),\r
300                 new Phase("hougeki1", CombatType.ByTurn),\r
301                 new Phase("hougeki2", CombatType.ByTurn),\r
302                 new Phase("hougeki3", CombatType.ByTurn),\r
303                 new Phase("raigeki", CombatType.AtOnce)\r
304             };\r
305             foreach (var phase in phases)\r
306                 CalcDamageByType(json, phase);\r
307         }\r
308 \r
309         private void CalcDamageByType(dynamic json, Phase phase)\r
310         {\r
311             var api = "api_" + phase.Api;\r
312             if (!json.IsDefined(api) || json[api] == null)\r
313                 return;\r
314             switch (phase.Type)\r
315             {\r
316                 case CombatType.AtOnce:\r
317                     CalcDamageAtOnce(json[api]);\r
318                     break;\r
319                 case CombatType.ByTurn:\r
320                     CalcDamageByTurn(json[api]);\r
321                     break;\r
322                 case CombatType.Support:\r
323                     CalcSupportDamage(json[api]);\r
324                     break;\r
325                 case CombatType.Aircraft:\r
326                     AddAirBattleResult(json[api], phase.Name);\r
327                     CalcKoukuDamage(json[api]);\r
328                     break;\r
329                 case CombatType.AirBase:\r
330                     CalcAirBaseAttackDamage(json[api]);\r
331                     break;\r
332                 case CombatType.Friend:\r
333                     CalcFriendAttackDamage(json[api]);\r
334                     break;\r
335             }\r
336         }\r
337 \r
338         private void CalcSupportDamage(dynamic json)\r
339         {\r
340             if (json.api_support_hourai != null)\r
341             {\r
342                 CalcRawDamageAtOnce(json.api_support_hourai.api_damage, _enemy, _enemyGuard);\r
343             }\r
344             else if (json.api_support_airatack != null)\r
345             {\r
346                 CalcRawDamageAtOnce(json.api_support_airatack.api_stage3.api_edam, _enemy, _enemyGuard);\r
347             }\r
348         }\r
349 \r
350         private void CalcAirBaseAttackDamage(dynamic json)\r
351         {\r
352             var i = 1;\r
353             foreach (var entry in json)\r
354             {\r
355                 AddAirBattleResult(entry, "基地" + i++);\r
356                 CalcKoukuDamage(entry);\r
357             }\r
358         }\r
359 \r
360         private void CalcFriendAttackDamage(dynamic json)\r
361         {\r
362             CalcDamageByTurn(json.api_hougeki, true);\r
363         }\r
364 \r
365         private void AddAirBattleResult(dynamic json, string phaseName)\r
366         {\r
367             var stage1 = json.api_stage1;\r
368             if (stage1 == null || (stage1.api_f_count == 0 && stage1.api_e_count == 0))\r
369                 return;\r
370             var result = new AirBattleResult\r
371             {\r
372                 PhaseName = phaseName,\r
373                 AirControlLevel = json.api_stage1.api_disp_seiku() ? (int)json.api_stage1.api_disp_seiku : 0,\r
374                 Stage1 = new AirBattleResult.StageResult\r
375                 {\r
376                     FriendCount = (int)json.api_stage1.api_f_count,\r
377                     FriendLost = (int)json.api_stage1.api_f_lostcount,\r
378                     EnemyCount = (int)json.api_stage1.api_e_count,\r
379                     EnemyLost = (int)json.api_stage1.api_e_lostcount\r
380                 },\r
381                 Stage2 = json.api_stage2 == null\r
382                     ? new AirBattleResult.StageResult\r
383                     {\r
384                         FriendCount = 0,\r
385                         FriendLost = 0,\r
386                         EnemyCount = 0,\r
387                         EnemyLost = 0\r
388                     }\r
389                     : new AirBattleResult.StageResult\r
390                     {\r
391                         FriendCount = (int)json.api_stage2.api_f_count,\r
392                         FriendLost = (int)json.api_stage2.api_f_lostcount,\r
393                         EnemyCount = (int)json.api_stage2.api_e_count,\r
394                         EnemyLost = (int)json.api_stage2.api_e_lostcount\r
395                     }\r
396             };\r
397             if (json.api_stage2 != null && json.api_stage2.api_air_fire())\r
398             {\r
399                 var airfire = json.api_stage2.api_air_fire;\r
400                 var idx = (int)airfire.api_idx;\r
401                 result.AirFire = new AirBattleResult.AirFireResult\r
402                 {\r
403                     ShipName = idx < _friend.Length ? _friend[idx].Name : _guard[idx - 6].Name,\r
404                     Kind = (int)airfire.api_kind,\r
405                     Items = ((int[])airfire.api_use_items).Select(id => _itemInfo.GetSpecByItemId(id).Name).ToArray()\r
406                 };\r
407             }\r
408             AirBattleResults.Add(result);\r
409         }\r
410 \r
411         private void CalcKoukuDamage(dynamic json)\r
412         {\r
413             if (json.api_stage3() && json.api_stage3 != null)\r
414                 CalcDamageAtOnce(json.api_stage3, _friend, _enemy);\r
415             if (json.api_stage3_combined() && json.api_stage3_combined != null)\r
416                 CalcDamageAtOnce(json.api_stage3_combined, _guard, _enemyGuard);\r
417         }\r
418 \r
419         private void CalcDamageAtOnce(dynamic json)\r
420         {\r
421             CalcDamageAtOnce(json, _friend, _guard, _enemy, _enemyGuard);\r
422         }\r
423 \r
424         private void CalcDamageAtOnce(dynamic json, Record[] friend, Record[] enemy)\r
425         {\r
426             CalcDamageAtOnce(json, friend, null, enemy, null);\r
427         }\r
428 \r
429         private void CalcDamageAtOnce(dynamic json,\r
430             Record[] friend, Record[] guard, Record[] enemy, Record[] enemyGuard)\r
431         {\r
432             if (json.api_fdam() && json.api_fdam != null)\r
433                 CalcRawDamageAtOnce(json.api_fdam, friend, guard);\r
434             if (json.api_edam() && json.api_edam != null)\r
435                 CalcRawDamageAtOnce(json.api_edam, enemy, enemyGuard);\r
436         }\r
437 \r
438         private void CalcRawDamageAtOnce(dynamic rawDamage, Record[] friend, Record[] guard = null)\r
439         {\r
440             var damage = (int[])rawDamage;\r
441             for (var i = 0; i < friend.Length; i++)\r
442                 friend[i].ApplyDamage(damage[i]);\r
443             if (guard == null)\r
444                 return;\r
445             for (var i = 0; i < guard.Length; i++)\r
446                 guard[i].ApplyDamage(damage[i + 6]);\r
447         }\r
448 \r
449         private void CalcDamageByTurn(dynamic json, bool ignoreFriendDamage = false)\r
450         {\r
451             if (!(json.api_df_list() && json.api_df_list != null &&\r
452                   json.api_damage() && json.api_damage != null &&\r
453                   json.api_at_eflag() && json.api_at_eflag != null))\r
454                 return;\r
455 \r
456             var targets = (int[][])json.api_df_list;\r
457             var damages = (int[][])json.api_damage;\r
458             var eflags = (int[])json.api_at_eflag;\r
459             var records = new[] {new Record[12], new Record[12]};\r
460             Array.Copy(_friend, records[1], _friend.Length);\r
461             Array.Copy(_guard, 0, records[1], 6, _guard.Length);\r
462             Array.Copy(_enemy, records[0], _enemy.Length);\r
463             Array.Copy(_enemyGuard, 0, records[0], 6, _enemyGuard.Length);\r
464             for (var i = 0; i < eflags.Length; i++)\r
465             {\r
466                 // 一度に複数の目標を狙う攻撃はないものと仮定する\r
467                 var hit = new {t = targets[i][0], d = damages[i].Sum(d => d >= 0 ? d : 0)};\r
468                 if (hit.t == -1)\r
469                     continue;\r
470                 if (ignoreFriendDamage && eflags[i] == 1)\r
471                     continue;\r
472                 records[eflags[i]][hit.t].ApplyDamage(hit.d);\r
473             }\r
474         }\r
475 \r
476         public void InspectMapStart(dynamic json)\r
477         {\r
478             InspectMapNext(json);\r
479         }\r
480 \r
481         public void InspectMapNext(dynamic json)\r
482         {\r
483             _lastCell = (int)json.api_next == 0;\r
484         }\r
485 \r
486         public void InspectBattleResult(dynamic json)\r
487         {\r
488             BattleState = BattleState.Result;\r
489             ShowResult(!_lastCell);\r
490             _shipInfo.SaveBattleResult();\r
491             VerifyResultRank(json);\r
492             CleanupResult();\r
493             SetEscapeShips(json);\r
494         }\r
495 \r
496         private void VerifyResultRank(dynamic json)\r
497         {\r
498             if (_friend == null)\r
499                 return;\r
500             if (!json.api_win_rank())\r
501                 return;\r
502             var assumed = "PSABCDE"[(int)ResultRank];\r
503             if (assumed == 'P')\r
504                 assumed = 'S';\r
505             var actual = ((string)json.api_win_rank)[0];\r
506             DisplayedResultRank.Assumed = assumed;\r
507             DisplayedResultRank.Actual = actual;\r
508         }\r
509 \r
510         public void InspectPracticeResult(dynamic json)\r
511         {\r
512             BattleState = BattleState.Result;\r
513             ShowResult(false);\r
514             VerifyResultRank(json);\r
515             CleanupResult();\r
516         }\r
517 \r
518         private void ShowResult(bool warnDamagedShip = true)\r
519         {\r
520             if (_friend == null)\r
521                 return;\r
522             var ships = _guard.Length > 0\r
523                 ? _shipInfo.GetShipStatuses(0).Concat(_shipInfo.GetShipStatuses(1)).ToArray()\r
524                 : _shipInfo.GetShipStatuses(_fleet);\r
525             foreach (var entry in ships.Zip(_friend.Concat(_guard), (ship, now) => new {ship, now}))\r
526                 entry.now.UpdateShipStatus(entry.ship);\r
527             if (warnDamagedShip)\r
528                 _shipInfo.SetBadlyDamagedShips();\r
529             else\r
530                 _shipInfo.ClearBadlyDamagedShips();\r
531         }\r
532 \r
533         public void SetEscapeShips(dynamic json)\r
534         {\r
535             _escapingShips.Clear();\r
536             if (!json.api_escape_flag() || (int)json.api_escape_flag == 0)\r
537                 return;\r
538             var damaged = (int)json.api_escape.api_escape_idx[0] - 1;\r
539             if (json.api_escape.api_tow_idx())\r
540             {\r
541                 _escapingShips.Add(_shipInfo.GetDeck(damaged / 6)[damaged % 6]);\r
542                 var escort = (int)json.api_escape.api_tow_idx[0] - 1;\r
543                 _escapingShips.Add(_shipInfo.GetDeck(escort / 6)[escort % 6]);\r
544             }\r
545             else\r
546             {\r
547                 _escapingShips.Add(_shipInfo.GetDeck(2)[damaged]);\r
548             }\r
549         }\r
550 \r
551         public void CauseEscape()\r
552         {\r
553             _shipInfo.SetEscapedShips(_escapingShips);\r
554             _shipInfo.SetBadlyDamagedShips();\r
555         }\r
556 \r
557         private class Record\r
558         {\r
559             private ShipStatus _status;\r
560             private bool _practice;\r
561             public ShipStatus SnapShot => (ShipStatus)_status.Clone();\r
562             public int NowHp => _status.NowHp;\r
563             public bool Escaped => _status.Escaped;\r
564             public ShipStatus.Damage DamageLevel => _status.DamageLevel;\r
565             public string Name => _status.Name;\r
566             public int StartHp { get; private set; }\r
567 \r
568             public static Record[] Setup(ShipStatus[] ships, bool practice) =>\r
569             (from s in ships\r
570                 select new Record {_status = (ShipStatus)s.Clone(), _practice = practice, StartHp = s.NowHp}).ToArray();\r
571 \r
572             public static Record[] Setup(int[] nowhps, ShipSpec[] ships, ItemSpec[][] slots, bool practice)\r
573             {\r
574                 return Enumerable.Range(0, nowhps.Length).Select(i =>\r
575                     new Record\r
576                     {\r
577                         StartHp = nowhps[i],\r
578                         _status = new ShipStatus\r
579                         {\r
580                             Id = ships[i].Id,\r
581                             NowHp = nowhps[i],\r
582                             MaxHp = nowhps[i],\r
583                             Spec = ships[i],\r
584                             Slot = slots[i].Select(spec => new ItemStatus {Id = spec.Id, Spec = spec}).ToArray(),\r
585                             SlotEx = new ItemStatus(0)\r
586                         },\r
587                         _practice = practice\r
588                     }).ToArray();\r
589             }\r
590 \r
591             public void ApplyDamage(int damage)\r
592             {\r
593                 if (_status.NowHp > damage)\r
594                 {\r
595                     _status.NowHp -= damage;\r
596                     return;\r
597                 }\r
598                 _status.NowHp = 0;\r
599                 if (_practice)\r
600                     return;\r
601                 foreach (var item in new[] {_status.SlotEx}.Concat(_status.Slot))\r
602                 {\r
603                     if (item.Spec.Id == 42)\r
604                     {\r
605                         _status.NowHp = (int)(_status.MaxHp * 0.2);\r
606                         ConsumeSlotItem(_status, 42);\r
607                         break;\r
608                     }\r
609                     if (item.Spec.Id == 43)\r
610                     {\r
611                         _status.NowHp = _status.MaxHp;\r
612                         ConsumeSlotItem(_status, 43);\r
613                         break;\r
614                     }\r
615                 }\r
616             }\r
617 \r
618             public void UpdateShipStatus(ShipStatus ship)\r
619             {\r
620                 ship.NowHp = NowHp;\r
621                 ship.Slot = _status.Slot;\r
622                 ship.SlotEx = _status.SlotEx;\r
623             }\r
624         }\r
625 \r
626         private BattleResultRank CalcLdAirBattleRank()\r
627         {\r
628             var combined = _friend.Concat(_guard).ToArray();\r
629             var friendNowShips = combined.Count(r => r.NowHp > 0);\r
630             var friendGauge = combined.Sum(r => r.StartHp - r.NowHp);\r
631             var friendSunk = combined.Count(r => r.NowHp == 0);\r
632             var friendGaugeRate = Floor((double)friendGauge / combined.Sum(r => r.StartHp) * 100);\r
633 \r
634             if (friendSunk == 0)\r
635             {\r
636                 if (friendGauge == 0)\r
637                     return BattleResultRank.P;\r
638                 if (friendGaugeRate < 10)\r
639                     return BattleResultRank.A;\r
640                 if (friendGaugeRate < 20)\r
641                     return BattleResultRank.B;\r
642                 if (friendGaugeRate < 50)\r
643                     return BattleResultRank.C;\r
644                 return BattleResultRank.D;\r
645             }\r
646             if (friendSunk < friendNowShips)\r
647                 return BattleResultRank.D;\r
648             return BattleResultRank.E;\r
649         }\r
650 \r
651         private BattleResultRank CalcResultRank()\r
652         {\r
653             var friend = _friend.Concat(_guard).ToArray();\r
654             var enemy = _enemy.Concat(_enemyGuard).ToArray();\r
655 \r
656             var friendCount = friend.Length;\r
657             var friendStartHpTotal = 0;\r
658             var friendNowHpTotal = 0;\r
659             var friendSunk = 0;\r
660             foreach (var ship in friend)\r
661             {\r
662                 if (ship.Escaped)\r
663                     continue;\r
664                 friendStartHpTotal += ship.StartHp;\r
665                 friendNowHpTotal += ship.NowHp;\r
666                 if (ship.NowHp == 0)\r
667                     friendSunk++;\r
668             }\r
669             var friendGaugeRate = (int)((double)(friendStartHpTotal - friendNowHpTotal) / friendStartHpTotal * 100);\r
670 \r
671             var enemyCount = enemy.Length;\r
672             var enemyStartHpTotal = enemy.Sum(r => r.StartHp);\r
673             var enemyNowHpTotal = enemy.Sum(r => r.NowHp);\r
674             var enemySunk = enemy.Count(r => r.NowHp == 0);\r
675             var enemyGaugeRate = (int)((double)(enemyStartHpTotal - enemyNowHpTotal) / enemyStartHpTotal * 100);\r
676 \r
677             if (friendSunk == 0 && enemySunk == enemyCount)\r
678             {\r
679                 if (friendNowHpTotal >= friendStartHpTotal)\r
680                     return BattleResultRank.P;\r
681                 return BattleResultRank.S;\r
682             }\r
683             if (friendSunk == 0 && enemySunk >= (int)(enemyCount * 0.7) && enemyCount > 1)\r
684                 return BattleResultRank.A;\r
685             if (friendSunk < enemySunk && enemy[0].NowHp == 0)\r
686                 return BattleResultRank.B;\r
687             if (friendCount == 1 && friend[0].DamageLevel == ShipStatus.Damage.Badly)\r
688                 return BattleResultRank.D;\r
689             if (enemyGaugeRate > friendGaugeRate * 2.5)\r
690                 return BattleResultRank.B;\r
691             if (enemyGaugeRate > friendGaugeRate * 0.9)\r
692                 return BattleResultRank.C;\r
693             if (friendCount > 1 && friendCount - 1 == friendSunk)\r
694                 return BattleResultRank.E;\r
695             return BattleResultRank.D;\r
696         }\r
697 \r
698         /// <summary>\r
699         /// テスト専用\r
700         /// </summary>\r
701         public void InjectResultStatus(ShipStatus[] main, ShipStatus[] guard, ShipStatus[] enemy, ShipStatus[] enemyGuard)\r
702         {\r
703             Result = new BattleResult\r
704             {\r
705                 Friend = new BattleResult.Combined { Main = main, Guard = guard},\r
706                 Enemy = new BattleResult.Combined {Main = enemy, Guard = enemyGuard}\r
707             };\r
708         }\r
709     }\r
710 }