OSDN Git Service

制空値に艦載機の熟練度を反映させる
[kancollesniffer/KancolleSniffer.git] / KancolleSniffer / ShipInfo.cs
1 // Copyright (C) 2013, 2014, 2015 Kazuhiro Fujieda <fujieda@users.osdn.me>\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.Collections.Generic;\r
20 using System.Linq;\r
21 using System.Web;\r
22 using static System.Math;\r
23 \r
24 namespace KancolleSniffer\r
25 {\r
26     public class ShipStatus\r
27     {\r
28         private readonly ItemInfo _itemInfo;\r
29         public int Id { get; set; }\r
30         public int Fleet { get; set; } // ShipListだけで使う\r
31         public ShipSpec Spec { get; set; }\r
32 \r
33         public string Name => Spec.Name;\r
34 \r
35         public int Level { get; set; }\r
36         public int ExpToNext { get; set; }\r
37         public int MaxHp { get; set; }\r
38         public int NowHp { get; set; }\r
39         public int Cond { get; set; }\r
40         public int Fuel { get; set; }\r
41         public int Bull { get; set; }\r
42         public int[] OnSlot { get; set; }\r
43         public int[] Slot { get; set; }\r
44         public int SlotEx { get; set; }\r
45         public int LoS { get; set; }\r
46         public int Firepower { get; set; }\r
47         public int AntiSubmarine { get; set; }\r
48         public bool Escaped { get; set; }\r
49 \r
50         public Damage DamageLevel => CalcDamage(NowHp, MaxHp);\r
51 \r
52         public ShipStatus(ItemInfo itemInfo = null)\r
53         {\r
54             _itemInfo = itemInfo;\r
55             Id = -1;\r
56             Spec = new ShipSpec();\r
57             OnSlot = new int[0];\r
58             Slot = new int[0];\r
59         }\r
60 \r
61         public enum Damage\r
62         {\r
63             Minor,\r
64             Small,\r
65             Half,\r
66             Badly\r
67         }\r
68 \r
69         public static Damage CalcDamage(int now, int max)\r
70         {\r
71             var ratio = max == 0 ? 1 : (double)now / max;\r
72             return ratio > 0.75 ? Damage.Minor : ratio > 0.5 ? Damage.Small : ratio > 0.25 ? Damage.Half : Damage.Badly;\r
73         }\r
74 \r
75         public TimeSpan RepairTime => TimeSpan.FromSeconds(CalcRepairSec(MaxHp - NowHp) + 30);\r
76 \r
77         public int CalcRepairSec(int damage) => (int)(RepairSecPerHp * damage);\r
78 \r
79         public double RepairSecPerHp\r
80         {\r
81             get\r
82             {\r
83                 var weight = Spec.RepairWeight;\r
84                 var level = Level < 12 ? Level * 10 : Level * 5 + Floor(Sqrt(Level - 11)) * 10 + 50;\r
85                 return level * weight;\r
86             }\r
87         }\r
88 \r
89         public void CalcMaterialsToRepair(out int fuel, out int steal)\r
90         {\r
91             var damage = MaxHp - NowHp;\r
92             fuel = (int)(Spec.FuelMax * 0.2 * 0.16 * damage);\r
93             steal = (int)(Spec.FuelMax * 0.2 * 0.3 * damage);\r
94         }\r
95 \r
96         public int RealFirepower\r
97         {\r
98             get\r
99             {\r
100                 if (Spec.IsSubmarine)\r
101                     return 0;\r
102                 if (!Spec.IsAircraftCarrier)\r
103                     return Firepower + 5;\r
104                 var specs = (from id in Slot\r
105                     let spec = _itemInfo.ItemDict[id].Spec\r
106                     where spec.IsAircraft\r
107                     select new {torpedo = spec.Torpedo, bomber = spec.Bomber}).ToArray();\r
108                 var torpedo = specs.Sum(s => s.torpedo);\r
109                 var bomber = specs.Sum(s => s.bomber);\r
110                 if (torpedo == 0 && bomber == 0)\r
111                     return 0;\r
112                 return (int)((Firepower + torpedo) * 1.5 + bomber * 2 + 55);\r
113             }\r
114         }\r
115 \r
116         public int RealAntiSubmarine\r
117         {\r
118             get\r
119             {\r
120                 if (!Spec.IsAntiSubmarine)\r
121                     return 0;\r
122                 if (Spec.IsAircraftCarrier && RealFirepower == 0) // 砲撃戦に参加しない\r
123                     return 0;\r
124                 var sonar = 0;\r
125                 var dc = 0;\r
126                 var aircraft = 0;\r
127                 var all = 0;\r
128                 var vanilla = AntiSubmarine;\r
129                 foreach (var spec in Slot.Select(id => _itemInfo.ItemDict[id].Spec))\r
130                 {\r
131                     vanilla -= spec.AntiSubmarine;\r
132                     if (spec.IsReconSeaplane) // 水偵は除外\r
133                         continue;\r
134                     if (spec.IsSonar)\r
135                         sonar += spec.AntiSubmarine;\r
136                     else if (spec.IsDepthCharge)\r
137                         dc += spec.AntiSubmarine;\r
138                     else if (spec.IsAircraft)\r
139                         aircraft += spec.AntiSubmarine;\r
140                     all += spec.AntiSubmarine;\r
141                 }\r
142                 if (vanilla == 0 && aircraft == 0) // 素対潜0で航空機なしは対潜攻撃なし\r
143                     return 0;\r
144                 var bonus = sonar > 0 && dc > 0 ? 1.15 : 1.0;\r
145                 return (int)(bonus * (vanilla / 5 + all * 2 + (aircraft > 0 ? 10 : 25)));\r
146             }\r
147         }\r
148     }\r
149 \r
150     public struct ChargeStatus\r
151     {\r
152         public int Fuel { get; set; }\r
153         public int Bull { get; set; }\r
154 \r
155         public ChargeStatus(ShipStatus status) : this()\r
156         {\r
157             Fuel = CalcChargeState(status.Fuel, status.Spec.FuelMax);\r
158             Bull = CalcChargeState(status.Bull, status.Spec.BullMax);\r
159         }\r
160 \r
161         public ChargeStatus(int fuel, int bull) : this()\r
162         {\r
163             Fuel = fuel;\r
164             Bull = bull;\r
165         }\r
166 \r
167         private int CalcChargeState(int now, int full)\r
168         {\r
169             if (full == 0 || now == full)\r
170                 return 0;\r
171             var ratio = (double)now / full;\r
172             if (ratio >= 7.0 / 9)\r
173                 return 1;\r
174             if (ratio >= 3.0 / 9)\r
175                 return 2;\r
176             if (ratio > 0)\r
177                 return 3;\r
178             return 4;\r
179         }\r
180     }\r
181 \r
182     public class ShipInfo\r
183     {\r
184         public const int FleetCount = 4;\r
185         public const int MemberCount = 6;\r
186 \r
187         private readonly int[][] _decks = new int[FleetCount][];\r
188         private readonly Dictionary<int, ShipStatus> _shipInfo = new Dictionary<int, ShipStatus>();\r
189         private readonly ShipMaster _shipMaster;\r
190         private readonly ItemInfo _itemInfo;\r
191         private readonly bool[] _inMission = new bool[FleetCount];\r
192         private readonly bool[] _inSortie = new bool[FleetCount];\r
193         private int _hqLevel;\r
194         private readonly List<int> _escapedShips = new List<int>();\r
195         private int _combinedFleetType;\r
196 \r
197         public ShipInfo(ShipMaster shipMaster, ItemInfo itemInfo)\r
198         {\r
199             _shipMaster = shipMaster;\r
200             _itemInfo = itemInfo;\r
201 \r
202             for (var fleet = 0; fleet < FleetCount; fleet++)\r
203             {\r
204                 var deck = new int[MemberCount];\r
205                 for (var i = 0; i < deck.Length; i++)\r
206                     deck[i] = -1;\r
207                 _decks[fleet] = deck;\r
208             }\r
209             ClearShipInfo();\r
210         }\r
211 \r
212         public void InspectShip(dynamic json)\r
213         {\r
214             if (json.api_deck_port()) // port\r
215             {\r
216                 ClearShipInfo();\r
217                 for (var i = 0; i < FleetCount; i++)\r
218                     _inSortie[i] = false;\r
219                 InspectDeck(json.api_deck_port);\r
220                 InspectShipData(json.api_ship);\r
221                 InspectBasic(json.api_basic);\r
222                 _combinedFleetType = json.api_combined_flag() ? (int)json.api_combined_flag : 0;\r
223                 _itemInfo.NowShips = ((object[])json.api_ship).Length;\r
224             }\r
225             else if (json.api_data()) // ship2\r
226             {\r
227                 ClearShipInfo();\r
228                 InspectDeck(json.api_data_deck);\r
229                 InspectShipData(json.api_data);\r
230                 _itemInfo.NowShips = ((object[])json.api_data).Length;\r
231             }\r
232             else if (json.api_ship_data()) // ship3とship_deck\r
233             {\r
234                 // 一隻分のデータしか来ないことがあるので艦娘数を数えない\r
235                 InspectDeck(json.api_deck_data);\r
236                 InspectShipData(json.api_ship_data);\r
237             }\r
238             else if (json.api_ship()) // getship\r
239             {\r
240                 InspectShipData(new[] {json.api_ship});\r
241             }\r
242         }\r
243 \r
244         private void ClearShipInfo()\r
245         {\r
246             _shipInfo.Clear();\r
247             _shipInfo[-1] = new ShipStatus();\r
248         }\r
249 \r
250         public void InspectDeck(dynamic json)\r
251         {\r
252             foreach (var entry in json)\r
253             {\r
254                 var fleet = (int)entry.api_id - 1;\r
255                 var deck = _decks[fleet];\r
256                 for (var i = 0; i < deck.Length; i++)\r
257                     deck[i] = (int)entry.api_ship[i];\r
258                 _inMission[fleet] = (int)entry.api_mission[0] != 0;\r
259             }\r
260         }\r
261 \r
262         private void InspectShipData(dynamic json)\r
263         {\r
264             foreach (var entry in json)\r
265             {\r
266                 _shipInfo[(int)entry.api_id] = new ShipStatus(_itemInfo)\r
267                 {\r
268                     Id = (int)entry.api_id,\r
269                     Spec = _shipMaster[(int)entry.api_ship_id],\r
270                     Level = (int)entry.api_lv,\r
271                     ExpToNext = (int)entry.api_exp[1],\r
272                     MaxHp = (int)entry.api_maxhp,\r
273                     NowHp = (int)entry.api_nowhp,\r
274                     Cond = (int)entry.api_cond,\r
275                     Fuel = (int)entry.api_fuel,\r
276                     Bull = (int)entry.api_bull,\r
277                     OnSlot = (int[])entry.api_onslot,\r
278                     Slot = (int[])entry.api_slot,\r
279                     SlotEx = entry.api_slot_ex() ? (int)entry.api_slot_ex : 0,\r
280                     LoS = (int)entry.api_sakuteki[0],\r
281                     Firepower = (int)entry.api_karyoku[0],\r
282                     AntiSubmarine = (int)entry.api_taisen[0]\r
283                 };\r
284                 _itemInfo.CountNewItems((int[])entry.api_slot);\r
285             }\r
286         }\r
287 \r
288         private void InspectBasic(dynamic json)\r
289         {\r
290             _hqLevel = (int)json.api_level;\r
291         }\r
292 \r
293         public void InspectCharge(dynamic json)\r
294         {\r
295             foreach (var entry in json.api_ship)\r
296             {\r
297                 var status = _shipInfo[(int)entry.api_id];\r
298                 status.Bull = (int)entry.api_bull;\r
299                 status.Fuel = (int)entry.api_fuel;\r
300                 status.OnSlot = (from num in (dynamic[])entry.api_onslot select (int)num).ToArray();\r
301             }\r
302             var material = (int[])json.api_material;\r
303             for (var i = 0; i < material.Length; i++)\r
304                 _itemInfo.MaterialHistory[i].Now = material[i];\r
305         }\r
306 \r
307         public void InspectChange(string request)\r
308         {\r
309             var values = HttpUtility.ParseQueryString(request);\r
310             var fleet = int.Parse(values["api_id"]) - 1;\r
311             var idx = int.Parse(values["api_ship_idx"]);\r
312             var ship = int.Parse(values["api_ship_id"]);\r
313             if (idx == -1)\r
314             {\r
315                 var deck = _decks[fleet];\r
316                 for (var i = 1; i < deck.Length; i++)\r
317                     deck[i] = -1;\r
318                 return;\r
319             }\r
320             if (ship == -1)\r
321             {\r
322                 WithdrowShip(fleet, idx);\r
323                 return;\r
324             }\r
325             int oi;\r
326             var of = FindFleet(ship, out oi);\r
327             var orig = _decks[fleet][idx];\r
328             _decks[fleet][idx] = ship;\r
329             if (of == -1)\r
330                 return;\r
331             // 入れ替えの場合\r
332             if ((_decks[of][oi] = orig) == -1)\r
333                 WithdrowShip(of, oi);\r
334         }\r
335 \r
336         private int FindFleet(int ship, out int idx)\r
337         {\r
338             for (var f = 0; f < _decks.Length; f++)\r
339             {\r
340                 idx = Array.FindIndex(_decks[f], id => id == ship);\r
341                 if (idx < 0)\r
342                     continue;\r
343                 return f;\r
344             }\r
345             idx = -1;\r
346             return -1;\r
347         }\r
348 \r
349         private void WithdrowShip(int fleet, int idx)\r
350         {\r
351             var deck = _decks[fleet];\r
352             for (var i = idx; i < deck.Length - 1; i++)\r
353                 deck[i] = deck[i + 1];\r
354             deck[deck.Length - 1] = -1;\r
355         }\r
356 \r
357         public void InspectPowerup(string request, dynamic json)\r
358         {\r
359             var values = HttpUtility.ParseQueryString(request);\r
360             var ships = values["api_id_items"].Split(',');\r
361             _itemInfo.NowShips -= ships.Length;\r
362             _itemInfo.DeleteItems(ships.SelectMany(s => _shipInfo[int.Parse(s)].Slot).ToArray());\r
363             foreach (var ship in ships)\r
364                 _shipInfo.Remove(int.Parse(ship));\r
365             InspectDeck(json.api_deck);\r
366             InspectShip(json.api_ship);\r
367         }\r
368 \r
369         public void InspectDestroyShip(string request, dynamic json)\r
370         {\r
371             var values = HttpUtility.ParseQueryString(request);\r
372             var ship = int.Parse(values["api_ship_id"]);\r
373             _itemInfo.NowShips--;\r
374             _itemInfo.DeleteItems(_shipInfo[ship].Slot);\r
375             int oi;\r
376             var of = FindFleet(ship, out oi);\r
377             if (of != -1)\r
378                 WithdrowShip(of, oi);\r
379             _shipInfo.Remove(ship);\r
380 \r
381             var material = (int[])json.api_material;\r
382             for (var i = 0; i < material.Length; i++)\r
383                 _itemInfo.MaterialHistory[i].Now = material[i];\r
384         }\r
385 \r
386         public void StartSortie(string request)\r
387         {\r
388             var values = HttpUtility.ParseQueryString(request);\r
389             var fleet = int.Parse(values["api_deck_id"]) - 1;\r
390             if (_combinedFleetType == 0)\r
391             {\r
392                 _inSortie[fleet] = true;\r
393             }\r
394             else\r
395             {\r
396                 _inSortie[0] = _inSortie[1] = true;\r
397             }\r
398         }\r
399 \r
400         public void RepairShip(int id)\r
401         {\r
402             var s = _shipInfo[id];\r
403             s.NowHp = s.MaxHp;\r
404             s.Cond = Max(40, s.Cond);\r
405         }\r
406 \r
407         public ShipStatus[] GetShipStatuses(int fleet)\r
408         {\r
409             return _decks[fleet].Where(id => id != -1).Select(id =>\r
410             {\r
411                 var s = _shipInfo[id];\r
412                 s.Escaped = _escapedShips.Contains(id);\r
413                 return s;\r
414             }).ToArray();\r
415         }\r
416 \r
417         public int[] GetDeck(int fleet) => _decks[fleet];\r
418 \r
419         public ShipStatus this[int idx] => _shipInfo[idx];\r
420 \r
421         public bool InMission(int fleet) => _inMission[fleet];\r
422 \r
423         public bool InSortie(int fleet) => _inSortie[fleet];\r
424 \r
425         public ShipStatus[] ShipList\r
426             => _shipInfo.Values.Where(s => s.Level != 0).Select(s =>\r
427             {\r
428                 int oi;\r
429                 var f = FindFleet(s.Id, out oi);\r
430                 s.Fleet = f;\r
431                 s.Escaped = _escapedShips.Contains(s.Id);\r
432                 return s;\r
433             }).ToArray();\r
434 \r
435         public ChargeStatus[] ChargeStatuses\r
436             => (from deck in _decks\r
437                 let flag = new ChargeStatus(_shipInfo[deck[0]])\r
438                 let others = (from id in deck.Skip(1)\r
439                     select new ChargeStatus(_shipInfo[id]))\r
440                     .Aggregate(\r
441                         (result, next) =>\r
442                             new ChargeStatus(Max(result.Fuel, next.Fuel), Max(result.Bull, next.Bull)))\r
443                 select new ChargeStatus(flag.Fuel != 0 ? flag.Fuel : others.Fuel + 5,\r
444                     flag.Bull != 0 ? flag.Bull : others.Bull + 5)).ToArray();\r
445 \r
446         private readonly Dictionary<int, int> _alvBonus = new Dictionary<int, int>\r
447         {\r
448             {6, 25}, // 艦戦\r
449             {7, 3}, // 艦爆\r
450             {8, 3}, // 艦攻\r
451             {11, 9}  // 水爆\r
452         };\r
453 \r
454         public int GetFighterPower(int fleet, bool withBonus)\r
455             => GetShipStatuses(fleet).Where(s => !s.Escaped).SelectMany(ship =>\r
456                 ship.Slot.Zip(ship.OnSlot, (slot, onslot) =>\r
457                 {\r
458                     var spec = _itemInfo[slot];\r
459                     if (!spec.CanAirCombat)\r
460                         return 0;\r
461                     var item = _itemInfo.ItemDict[slot];\r
462                     var bonus = 0;\r
463                     if (onslot != 0 && item.Alv == 7 && withBonus)\r
464                         _alvBonus.TryGetValue(spec.Type, out bonus);\r
465                     return (int)Floor(spec.AntiAir * Sqrt(onslot)) + bonus;\r
466                 })).Sum();\r
467 \r
468         public ShipStatus[] GetDamagedShipList(DockInfo dockInfo)\r
469             => (from s in ShipList\r
470                 where s.NowHp < s.MaxHp && !dockInfo.InNDock(s.Id)\r
471                 select s).OrderByDescending(s => s.RepairTime).ToArray();\r
472 \r
473 \r
474         public double GetLineOfSights(int fleet)\r
475         {\r
476             var result = 0.0;\r
477             foreach (var s in _decks[fleet].Select(id => _shipInfo[id]))\r
478             {\r
479                 var items = 0;\r
480                 foreach (var spec in s.Slot.Select(t => _itemInfo[t]))\r
481                 {\r
482                     items += spec.LoS;\r
483                     result += spec.LoS * spec.LoSScaleFactor();\r
484                 }\r
485                 result += Sqrt(s.LoS - items) * 1.6841056;\r
486             }\r
487             return result > 0 ? result + (_hqLevel + 4) / 5 * 5 * -0.6142467 : 0.0;\r
488         }\r
489 \r
490         public void SetEscapedShips(List<int> ships)\r
491         {\r
492             _escapedShips.AddRange(ships);\r
493         }\r
494 \r
495         public void ClearEscapedShips()\r
496         {\r
497             _escapedShips.Clear();\r
498         }\r
499     }\r
500 }