OSDN Git Service

戦闘中に表示する艦隊を切り替えると戦闘結果が見えてしまうのを直す
[kancollesniffer/KancolleSniffer.git] / KancolleSniffer / BattleInfo.cs
1 // Copyright (C) 2014 Kazuhiro Fujieda <fujieda@users.sourceforge.jp>\r
2 // \r
3 // This program is part of KancolleSniffer.\r
4 //\r
5 // KancolleSniffer is free software: you can redistribute it and/or modify\r
6 // it under the terms of the GNU General Public License as published by\r
7 // the Free Software Foundation, either version 3 of the License, or\r
8 // (at your option) any later version.\r
9 //\r
10 // This program is distributed in the hope that it will be useful,\r
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of\r
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
13 // GNU General Public License for more details.\r
14 //\r
15 // You should have received a copy of the GNU General Public License\r
16 // along with this program; if not, see <http://www.gnu.org/licenses/>.\r
17 \r
18 using System;\r
19 using System.Linq;\r
20 \r
21 namespace KancolleSniffer\r
22 {\r
23     public class BattleInfo\r
24     {\r
25         private readonly ShipMaster _shipMaster;\r
26         private readonly ShipInfo _shipInfo;\r
27         private readonly ItemInfo _itemInfo;\r
28         private dynamic _prevBattle;\r
29         public bool InBattle { get; set; }\r
30         public string Formation { get; private set; }\r
31         public int DelayInFormation { get; private set; }\r
32         public int EnemyAirSuperiority { get; private set; }\r
33         public int DelayInAirSuperiority { get; private set; }\r
34         public bool HasDamagedShip { get; set; }\r
35         public string[] DamagedShipNames { get; private set; }\r
36 \r
37         private struct Delay\r
38         {\r
39             public const int Basic = 4100;\r
40             public const int Formation = 1100;\r
41             public const int Tau = 200;\r
42             public const int SearchAirSuccess = 5700;\r
43             public const int SearchAirFailure = 4900;\r
44             public const int SearchSuccess = 5400;\r
45             public const int Submarine = 2500;\r
46             public const int Emergence = 2000;\r
47             public const int AirFight = 8700;\r
48             public const int AirFightBoth = 2700;\r
49             public const int Support = 9800;\r
50             public const int OpeningAttack = 3500;\r
51             public const int Cutin = 4500;\r
52         }\r
53 \r
54         public BattleInfo(ShipMaster shipMaster, ShipInfo shipInfo, ItemInfo itemInfo)\r
55         {\r
56             _shipMaster = shipMaster;\r
57             _shipInfo = shipInfo;\r
58             _itemInfo = itemInfo;\r
59         }\r
60 \r
61         public void InspectBattle(dynamic json)\r
62         {\r
63             InBattle = true;\r
64             Formation = FormationName(json);\r
65             EnemyAirSuperiority = CalcEnemyAirSuperiority(json);\r
66             SetDelay(json);\r
67             _prevBattle = json;\r
68         }\r
69 \r
70         public void InspectCombinedBattle(dynamic json)\r
71         {\r
72             InBattle = true;\r
73             Formation = FormationName(json);\r
74             EnemyAirSuperiority = CalcEnemyAirSuperiority(json);\r
75             DelayInFormation = DelayInAirSuperiority = Delay.Basic;\r
76             _prevBattle = json;\r
77         }\r
78 \r
79         private string FormationName(dynamic json)\r
80         {\r
81             if (!json.api_formation()) // 演習の夜戦\r
82                 return "";\r
83             switch ((int)json.api_formation[2])\r
84             {\r
85                 case 1:\r
86                     return "同航戦";\r
87                 case 2:\r
88                     return "反航戦";\r
89                 case 3:\r
90                     return "T字有利";\r
91                 case 4:\r
92                     return "T字不利";\r
93             }\r
94             return "";\r
95         }\r
96 \r
97         private void SetDelay(dynamic json)\r
98         {\r
99             // 敵出現まで\r
100             var delay = Delay.Basic;\r
101             if (json.api_hougeki()) // 夜戦\r
102             {\r
103                 DelayInAirSuperiority = DelayInFormation = delay;\r
104                 return;\r
105             }\r
106             var subm = (SubmarineFlags)CheckSubmarine(json);\r
107             bool success;\r
108             delay += SearchDelay(json, out success) + subm.AddtionalDelay;\r
109             DelayInAirSuperiority = delay + (success ? 0 : Delay.Emergence); // 失敗すると出現が遅れる\r
110             // 敵艦隊発見以降\r
111             delay += Delay.Emergence + Delay.Formation + SupportDelay(json) + CutinDelay(json);\r
112             if ((int)json.api_formation[2] >= 3)\r
113                 delay += Delay.Tau;\r
114             if (!subm.PreventAirFight)\r
115                 delay += AirFightDelay(json);\r
116             if (!subm.PreventOpeningAttack)\r
117                 delay += OpeningAttackDelay(json);\r
118             DelayInFormation = delay;\r
119         }\r
120 \r
121         private int SearchDelay(dynamic json, out bool success)\r
122         {\r
123             success = false;\r
124             switch ((int)json.api_search[0])\r
125             {\r
126                 case 1: // 索敵機による索敵成功\r
127                 case 2: // 索敵機未帰還あり\r
128                     success = true;\r
129                     return Delay.SearchAirSuccess;\r
130                 case 3: // 索敵機未帰還\r
131                 case 4: // 索敵機による索敵失敗\r
132                     return Delay.SearchAirFailure;\r
133                 case 5: // 索敵力による索敵成功\r
134                     success = true;\r
135                     return Delay.SearchSuccess;\r
136             }\r
137             return 0;\r
138         }\r
139 \r
140         private int OpeningAttackDelay(dynamic json)\r
141         {\r
142             return json.api_opening_flag == 1 ? Delay.OpeningAttack : 0;\r
143         }\r
144 \r
145         private class SubmarineFlags\r
146         {\r
147             public bool[] Friend;\r
148             public bool[] Enemy;\r
149 \r
150             public int AddtionalDelay\r
151             {\r
152                 get { return Friend.Any(x => x) || Enemy.Any(x => x) ? Delay.Submarine : 0; }\r
153                 // どちらかに潜水艦                \r
154             }\r
155 \r
156             public bool PreventAirFight\r
157             {\r
158                 get { return Friend.All(x => x) || Enemy.All(x => x); } // 一方がすべて潜水艦\r
159             }\r
160 \r
161             public bool PreventOpeningAttack\r
162             {\r
163                 get { return Friend.All(x => x) && Enemy.All(x => x); } // 双方すべて潜水艦\r
164             }\r
165         }\r
166 \r
167         private SubmarineFlags CheckSubmarine(dynamic json)\r
168         {\r
169             return new SubmarineFlags\r
170             {\r
171                 Friend = (from status in _shipInfo.GetShipStatuses((int)DeckId(json))\r
172                     select _shipMaster[status.ShipId].IsSubmarine).ToArray(),\r
173                 Enemy = (from id in (int[])json.api_ship_ke where id != -1 select _shipMaster[id].IsSubmarine).ToArray()\r
174             };\r
175         }\r
176 \r
177         private int AirFightDelay(dynamic json)\r
178         {\r
179             // 僚艦が偵察機だけだと航空戦に参加しないので、\r
180             // 艦載機の配備ではなく遭遇戦の状況を調べる。\r
181             var f = json.api_kouku.api_stage1.api_f_count > 0;\r
182             var e = json.api_kouku.api_stage1.api_e_count > 0;\r
183             var result = 0;\r
184             if (f || e) // 艦載機発艦\r
185                 result += Delay.AirFight;\r
186             if (f && e) // 双方とも\r
187                 result += Delay.AirFightBoth;\r
188             return result;\r
189         }\r
190 \r
191         private int SupportDelay(dynamic json)\r
192         {\r
193             return json.api_support_flag() && json.api_support_flag == 1 ? Delay.Support : 0;\r
194         }\r
195 \r
196         private int CutinDelay(dynamic json)\r
197         {\r
198             var cutin = 0;\r
199             var maxHps = (int[])json.api_nowhps;\r
200             var nowHps = (int[])json.api_nowhps;\r
201             var planeFrom = (int[][])json.api_kouku.api_plane_from;\r
202             if ((planeFrom[0][0] != -1 || planeFrom[1][0] != -1) && // どちらかに艦載機あり\r
203                 json.api_kouku.api_stage3 != null) // 敵艦載機が全滅しているとnull\r
204             {\r
205                 // 航空戦による中破大破の判定\r
206                 var damages = (int[])json.api_kouku.api_stage3.api_fdam;\r
207                 var newHps = nowHps.Zip(damages, (hp, dmg) => hp - dmg).ToArray();\r
208                 if (IsCutinShown(nowHps, newHps, maxHps))\r
209                     cutin++;\r
210                 nowHps = newHps;\r
211             }\r
212             if ((int)json.api_opening_flag != 0)\r
213             {\r
214                 // 開幕雷撃による中破大破の判定\r
215                 var damages = (int[])json.api_opening_atack.api_fdam;\r
216                 var newHps = nowHps.Zip(damages, (hp, dmg) => hp - dmg).ToArray();\r
217                 if (IsCutinShown(nowHps, newHps, maxHps))\r
218                     cutin++;\r
219             }\r
220             return Delay.Cutin * cutin;\r
221         }\r
222 \r
223         private bool IsCutinShown(int[] nowHps, int[] newHps, int[] maxHps)\r
224         {\r
225             for (var i = 1; i < ShipInfo.MemberCount + 1; i++)\r
226             {\r
227                 if (ShipStatus.CalcDamage(nowHps[i], maxHps[i]) <= ShipStatus.Damage.Small &&\r
228                     ShipStatus.CalcDamage(newHps[i], maxHps[i]) >= ShipStatus.Damage.Half)\r
229                     return true;\r
230             }\r
231             return false;\r
232         }\r
233 \r
234         private int CalcEnemyAirSuperiority(dynamic json)\r
235         {\r
236             var maxEq = ((int[])json.api_ship_ke).Skip(1).SelectMany(id => _shipMaster[id].MaxEq);\r
237             var equips = ((int[][])json.api_eSlot).SelectMany(x => x);\r
238             return (from slot in equips.Zip(maxEq, (id, max) => new {id, max})\r
239                 select (int)Math.Floor(_itemInfo.GetSpecByItemId(slot.id).TyKu * Math.Sqrt(slot.max))).Sum();\r
240         }\r
241 \r
242         public void CauseDamage()\r
243         {\r
244             var json = _prevBattle;\r
245             if (json == null)\r
246                 return;\r
247             var ships = _shipInfo.GetShipStatuses((int)DeckId(json));\r
248             if (json.api_hougeki()) // 夜戦\r
249             {\r
250                 CauseHougekiDamage(ships, json.api_hougeki);\r
251             }\r
252             else // 昼戦\r
253             {\r
254                 if (json.api_kouku.api_stage3 != null)\r
255                     CauseSimpleDamage(ships, json.api_kouku.api_stage3.api_fdam);\r
256                 if (json.api_opening_atack != null)\r
257                     CauseSimpleDamage(ships, json.api_opening_atack.api_fdam);\r
258                 if (json.api_hougeki1 != null)\r
259                     CauseHougekiDamage(ships, json.api_hougeki1);\r
260                 if (json.api_hougeki2 != null)\r
261                     CauseHougekiDamage(ships, json.api_hougeki2);\r
262                 if (json.api_raigeki != null)\r
263                     CauseSimpleDamage(ships, json.api_raigeki.api_fdam);\r
264             }\r
265             DamagedShipNames =\r
266                 (from ship in ships where ship.DamageLevel == ShipStatus.Damage.Badly select ship.Name).ToArray();\r
267             HasDamagedShip = DamagedShipNames.Any();\r
268         }\r
269 \r
270         private int DeckId(dynamic json)\r
271         {\r
272             return (int)(json.api_dock_id() ? json.api_dock_id : json.api_deck_id) - 1; // 昼戦はtypoをしている\r
273         }\r
274 \r
275         private void CauseSimpleDamage(ShipStatus[] ships, dynamic rawDamage)\r
276         {\r
277             var damage = (int[])rawDamage;\r
278             for (var i = 0; i < ships.Length; i++)\r
279                 ships[i].NowHp -= damage[i + 1];\r
280         }\r
281 \r
282         private void CauseHougekiDamage(ShipStatus[] ships, dynamic hougeki)\r
283         {\r
284             var targets = ((dynamic[])hougeki.api_df_list).Skip(1).SelectMany(x => (int[])x);\r
285             var damages = ((dynamic[])hougeki.api_damage).Skip(1).SelectMany(x => (double[])x);\r
286             foreach (var hit in targets.Zip(damages, (t, d) => new {t, d}))\r
287             {\r
288                 if (hit.t - 1 < ships.Length)\r
289                     ships[hit.t - 1].NowHp -= (int)hit.d;\r
290             }\r
291         }\r
292 \r
293         public void CauseDamageCombined()\r
294         {\r
295             var json = _prevBattle;\r
296             if (json == null)\r
297                 return;\r
298             bool midnight = json.api_hougeki();\r
299             var hontai = _shipInfo.GetShipStatuses(0);\r
300             var goei = _shipInfo.GetShipStatuses(1);\r
301             if (midnight)\r
302             {\r
303                 CauseHougekiDamage(goei, json.api_hougeki);\r
304             }\r
305             else // 昼戦\r
306             {\r
307                 var kouku = json.api_kouku;\r
308                 if (kouku.api_stage3 != null)\r
309                     CauseSimpleDamage(hontai, kouku.api_stage3.api_fdam);\r
310                 if (kouku.api_stage3_combined != null)\r
311                     CauseSimpleDamage(goei, kouku.api_stage3_combined.api_fdam);\r
312                 if (json.api_kouku2()) // 航空戦2回目\r
313                 {\r
314                     kouku = json.api_kouku2;\r
315                     if (kouku.api_stage3 != null)\r
316                         CauseSimpleDamage(hontai, kouku.api_stage3.api_fdam);\r
317                     if (kouku.api_stage3_combined != null)\r
318                         CauseSimpleDamage(goei, kouku.api_stage3_combined.api_fdam);\r
319                 }\r
320                 if (!json.api_opening_atack()) // 航空戦のみ\r
321                     return;\r
322                 if (json.api_opening_atack != null)\r
323                     CauseSimpleDamage(goei, json.api_opening_atack.api_fdam);\r
324                 if (json.api_hougeki1 != null)\r
325                     CauseHougekiDamage(goei, json.api_hougeki1);\r
326                 if (json.api_hougeki2() && json.api_hougeki2 != null)\r
327                     CauseHougekiDamage(hontai, json.api_hougeki2);\r
328                 if (json.api_hougeki3() && json.api_hougeki3 != null)\r
329                     CauseHougekiDamage(hontai, json.api_hougeki3);\r
330                 if (json.api_raigeki != null)\r
331                     CauseSimpleDamage(goei, json.api_raigeki.api_fdam);\r
332             }\r
333             DamagedShipNames =\r
334                 (from ship in hontai.Concat(goei) where ship.DamageLevel == ShipStatus.Damage.Badly select ship.Name).ToArray();\r
335             HasDamagedShip = DamagedShipNames.Any();\r
336         }\r
337     }\r
338 }