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         public bool InBattle { get; set; }\r
29         public string Formation { get; private set; }\r
30         public int DelayInFormation { get; private set; }\r
31         public int EnemyAirSuperiority { get; private set; }\r
32         public int DelayInAirSuperiority { get; private set; }\r
33         public bool HasDamagedShip { get; set; }\r
34         public string[] DamagedShipNames { get; private set; }\r
35 \r
36         private struct Delay\r
37         {\r
38             public const int Basic = 4100;\r
39             public const int Formation = 1100;\r
40             public const int Tau = 200;\r
41             public const int SearchAirSuccess = 5700;\r
42             public const int SearchAirFailure = 4900;\r
43             public const int SearchSuccess = 5400;\r
44             public const int Submarine = 2500;\r
45             public const int Emergence = 2000;\r
46             public const int AirFight = 8700;\r
47             public const int AirFightBoth = 2700;\r
48             public const int Support = 9800;\r
49             public const int OpeningAttack = 3500;\r
50             public const int Cutin = 4900;\r
51         }\r
52 \r
53         public BattleInfo(ShipMaster shipMaster, ShipInfo shipInfo, ItemInfo itemInfo)\r
54         {\r
55             _shipMaster = shipMaster;\r
56             _shipInfo = shipInfo;\r
57             _itemInfo = itemInfo;\r
58         }\r
59 \r
60         public void InspectBattle(dynamic json)\r
61         {\r
62             InBattle = true;\r
63             Formation = FormationName(json);\r
64             EnemyAirSuperiority = CalcEnemyAirSuperiority(json);\r
65             SetDelay(json);\r
66             CauseDamage(json);\r
67         }\r
68 \r
69         private string FormationName(dynamic json)\r
70         {\r
71             switch ((int)json.api_formation[2])\r
72             {\r
73                 case 1:\r
74                     return "同航戦";\r
75                 case 2:\r
76                     return "反航戦";\r
77                 case 3:\r
78                     return "T字有利";\r
79                 case 4:\r
80                     return "T字不利";\r
81             }\r
82             return "";\r
83         }\r
84 \r
85         private void SetDelay(dynamic json)\r
86         {\r
87             // 敵出現まで\r
88             var delay = Delay.Basic;\r
89             if ((int)json.api_formation[2] >= 3)\r
90                 delay += Delay.Tau;\r
91             var subm = (SubmarineFlags)CheckSubmarine(json);\r
92             bool success;\r
93             delay += SearchDelay(json, out success) + subm.AddtionalDelay;\r
94             DelayInAirSuperiority = delay + (success ? 0 : Delay.Emergence); // 失敗すると出現が遅れる\r
95             // 敵艦隊発見以降\r
96             delay += Delay.Emergence + Delay.Formation + SupportDelay(json) + CutinDelay(json);\r
97             if (!subm.PreventAirFight)\r
98                 delay += AirFightDelay(json);\r
99             if (!subm.PreventOpeningAttack)\r
100                 delay += OpeningAttackDelay(json);\r
101             DelayInFormation = delay;\r
102         }\r
103 \r
104         private int SearchDelay(dynamic json, out bool success)\r
105         {\r
106             success = false;\r
107             switch ((int)json.api_search[0])\r
108             {\r
109                 case 1: // 索敵機による索敵成功\r
110                 case 2: // 索敵機未帰還あり\r
111                     success = true;\r
112                     return Delay.SearchAirSuccess;\r
113                 case 3: // 索敵機未帰還\r
114                 case 4: // 索敵機による索敵失敗\r
115                     return Delay.SearchAirFailure;\r
116                 case 5: // 索敵力による索敵成功\r
117                     success = true;\r
118                     return Delay.SearchSuccess;\r
119             }\r
120             return 0;\r
121         }\r
122 \r
123         private int OpeningAttackDelay(dynamic json)\r
124         {\r
125             return json.api_opening_flag == 1 ? Delay.OpeningAttack : 0;\r
126         }\r
127 \r
128         private class SubmarineFlags\r
129         {\r
130             public bool[] Friend;\r
131             public bool[] Enemy;\r
132 \r
133             public int AddtionalDelay\r
134             {\r
135                 get { return Friend.Any(x => x) || Enemy.Any(x => x) ? Delay.Submarine : 0; }\r
136                 // どちらかに潜水艦                \r
137             }\r
138 \r
139             public bool PreventAirFight\r
140             {\r
141                 get { return Friend.All(x => x) || Enemy.All(x => x); } // 一方がすべて潜水艦\r
142             }\r
143 \r
144             public bool PreventOpeningAttack\r
145             {\r
146                 get { return Friend.All(x => x) && Enemy.All(x => x); } // 双方すべて潜水艦\r
147             }\r
148         }\r
149 \r
150         private SubmarineFlags CheckSubmarine(dynamic json)\r
151         {\r
152             return new SubmarineFlags\r
153             {\r
154                 Friend = (from status in _shipInfo.GetShipStatuses((int)json.api_dock_id - 1)\r
155                     select _shipMaster[status.ShipId].IsSubmarine).ToArray(),\r
156                 Enemy = (from id in (int[])json.api_ship_ke where id != -1 select _shipMaster[id].IsSubmarine).ToArray()\r
157             };\r
158         }\r
159 \r
160         private int AirFightDelay(dynamic json)\r
161         {\r
162             // 僚艦が偵察機だけだと航空戦に参加しないので、\r
163             // 艦載機の配備ではなく遭遇戦の状況を調べる。\r
164             var f = json.api_kouku.api_stage1.api_f_count > 0;\r
165             var e = json.api_kouku.api_stage1.api_e_count > 0;\r
166             var result = 0;\r
167             if (f || e) // 艦載機発艦\r
168                 result += Delay.AirFight;\r
169             if (f && e) // 双方とも\r
170                 result += Delay.AirFightBoth;\r
171             return result;\r
172         }\r
173 \r
174         private int SupportDelay(dynamic json)\r
175         {\r
176             return json.api_support_flag() && json.api_support_flag == 1 ? Delay.Support : 0;\r
177         }\r
178 \r
179         private int CutinDelay(dynamic json)\r
180         {\r
181             var cutin = 0;\r
182             var maxHps = (int[])json.api_nowhps;\r
183             var nowHps = (int[])json.api_nowhps;\r
184             var planeFrom = (int[][])json.api_kouku.api_plane_from;\r
185             if ((planeFrom[0][0] != -1 || planeFrom[1][0] != -1) && // どちらかに艦載機あり\r
186                 json.api_kouku.api_stage3 != null) // 敵艦載機が全滅しているとnull\r
187             {\r
188                 // 航空戦による中破大破の判定\r
189                 var damages = (int[])json.api_kouku.api_stage3.api_fdam;\r
190                 var newHps = nowHps.Zip(damages, (hp, dmg) => hp - dmg).ToArray();\r
191                 if (IsCutinShown(nowHps, newHps, maxHps))\r
192                     cutin++;\r
193                 nowHps = newHps;\r
194             }\r
195             if ((int)json.api_opening_flag != 0)\r
196             {\r
197                 // 開幕雷撃による中破大破の判定\r
198                 var damages = (int[])json.api_opening_atack.api_fdam;\r
199                 var newHps = nowHps.Zip(damages, (hp, dmg) => hp - dmg).ToArray();\r
200                 if (IsCutinShown(nowHps, newHps, maxHps))\r
201                     cutin++;\r
202             }\r
203             return Delay.Cutin * cutin;\r
204         }\r
205 \r
206         private bool IsCutinShown(int[] nowHps, int[] newHps, int[] maxHps)\r
207         {\r
208             for (var i = 1; i < ShipInfo.MemberCount + 1; i++)\r
209             {\r
210                 if (ShipStatus.CalcDamage(nowHps[i], maxHps[i]) <= ShipStatus.Damage.Small &&\r
211                     ShipStatus.CalcDamage(newHps[i], maxHps[i]) >= ShipStatus.Damage.Half)\r
212                     return true;\r
213             }\r
214             return false;\r
215         }\r
216 \r
217         private int CalcEnemyAirSuperiority(dynamic json)\r
218         {\r
219             var maxEq = ((int[])json.api_ship_ke).Skip(1).SelectMany(id => _shipMaster[id].MaxEq);\r
220             var equips = ((int[][])json.api_eSlot).SelectMany(x => x);\r
221             return (from slot in equips.Zip(maxEq, (id, max) => new {id, max})\r
222                 select (int)Math.Floor(_itemInfo.GetSpecByItemId(slot.id).TyKu * Math.Sqrt(slot.max))).Sum();\r
223         }\r
224 \r
225         private void CauseDamage(dynamic json)\r
226         {\r
227             var ships = _shipInfo.GetShipStatuses((int)json.api_dock_id - 1);\r
228             if (json.api_kouku.api_stage3 != null)\r
229                 CauseSimpleDamage(ships, json.api_kouku.api_stage3.api_fdam);\r
230             if (json.api_opening_atack != null)\r
231                 CauseSimpleDamage(ships, json.api_opening_atack.api_fdam);\r
232             if (json.api_hougeki1 != null)\r
233                 CauseHougekiDamage(ships, json.api_hougeki1.api_df_list, json.api_hougeki1.api_damage);\r
234             if (json.api_hougeki2 != null)\r
235                 CauseHougekiDamage(ships, json.api_hougeki2.api_df_list, json.api_hougeki2.api_damage);\r
236             if (json.api_raigeki != null)\r
237                 CauseSimpleDamage(ships, json.api_raigeki.api_fdam);\r
238             DamagedShipNames =\r
239                 (from ship in ships where ship.DamageLevel == ShipStatus.Damage.Badly select ship.Name).ToArray();\r
240             HasDamagedShip = DamagedShipNames.Any();\r
241         }\r
242 \r
243         private void CauseSimpleDamage(ShipStatus[] ships, dynamic rawDamage)\r
244         {\r
245             var damage = (int[])rawDamage;\r
246             for (var i = 0; i < ships.Length; i++)\r
247                 ships[i].NowHp -= damage[i + 1];\r
248         }\r
249 \r
250         private void CauseHougekiDamage(ShipStatus[] ships, dynamic rawTergets, dynamic rawDamages)\r
251         {\r
252             var targets = ((dynamic[])rawTergets).Skip(1).SelectMany(x => (int[])x);\r
253             var damages = ((dynamic[])rawDamages).Skip(1).SelectMany(x => (double[])x);\r
254             foreach (var hit in targets.Zip(damages, (t, d) => new {t, d}))\r
255             {\r
256                 if (hit.t - 1 < ships.Length)\r
257                     ships[hit.t - 1].NowHp -= (int)hit.d;\r
258             }\r
259         }\r
260     }\r
261 }