OSDN Git Service

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