OSDN Git Service

制空値のボーナスをItemInfoで計算する
[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.GetStatus(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.GetStatus(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 = new 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(ItemInfo itemInfo)\r
198         {\r
199             _itemInfo = itemInfo;\r
200 \r
201             for (var fleet = 0; fleet < FleetCount; fleet++)\r
202             {\r
203                 var deck = new int[MemberCount];\r
204                 for (var i = 0; i < deck.Length; i++)\r
205                     deck[i] = -1;\r
206                 _decks[fleet] = deck;\r
207             }\r
208             ClearShipInfo();\r
209         }\r
210 \r
211         public void InspectMaster(dynamic json)\r
212         {\r
213             _shipMaster.Inspect(json);\r
214         }\r
215 \r
216         public void InspectShip(dynamic json)\r
217         {\r
218             if (json.api_deck_port()) // port\r
219             {\r
220                 ClearShipInfo();\r
221                 for (var i = 0; i < FleetCount; i++)\r
222                     _inSortie[i] = false;\r
223                 InspectDeck(json.api_deck_port);\r
224                 InspectShipData(json.api_ship);\r
225                 InspectBasic(json.api_basic);\r
226                 _combinedFleetType = json.api_combined_flag() ? (int)json.api_combined_flag : 0;\r
227                 _itemInfo.NowShips = ((object[])json.api_ship).Length;\r
228             }\r
229             else if (json.api_data()) // ship2\r
230             {\r
231                 ClearShipInfo();\r
232                 InspectDeck(json.api_data_deck);\r
233                 InspectShipData(json.api_data);\r
234                 _itemInfo.NowShips = ((object[])json.api_data).Length;\r
235             }\r
236             else if (json.api_ship_data()) // ship3とship_deck\r
237             {\r
238                 // 一隻分のデータしか来ないことがあるので艦娘数を数えない\r
239                 InspectDeck(json.api_deck_data);\r
240                 InspectShipData(json.api_ship_data);\r
241             }\r
242             else if (json.api_ship()) // getship\r
243             {\r
244                 InspectShipData(new[] {json.api_ship});\r
245             }\r
246         }\r
247 \r
248         private void ClearShipInfo()\r
249         {\r
250             _shipInfo.Clear();\r
251             _shipInfo[-1] = new ShipStatus();\r
252         }\r
253 \r
254         public void InspectDeck(dynamic json)\r
255         {\r
256             foreach (var entry in json)\r
257             {\r
258                 var fleet = (int)entry.api_id - 1;\r
259                 var deck = _decks[fleet];\r
260                 for (var i = 0; i < deck.Length; i++)\r
261                     deck[i] = (int)entry.api_ship[i];\r
262                 _inMission[fleet] = (int)entry.api_mission[0] != 0;\r
263             }\r
264         }\r
265 \r
266         private void InspectShipData(dynamic json)\r
267         {\r
268             foreach (var entry in json)\r
269             {\r
270                 _shipInfo[(int)entry.api_id] = new ShipStatus(_itemInfo)\r
271                 {\r
272                     Id = (int)entry.api_id,\r
273                     Spec = _shipMaster[(int)entry.api_ship_id],\r
274                     Level = (int)entry.api_lv,\r
275                     ExpToNext = (int)entry.api_exp[1],\r
276                     MaxHp = (int)entry.api_maxhp,\r
277                     NowHp = (int)entry.api_nowhp,\r
278                     Cond = (int)entry.api_cond,\r
279                     Fuel = (int)entry.api_fuel,\r
280                     Bull = (int)entry.api_bull,\r
281                     OnSlot = (int[])entry.api_onslot,\r
282                     Slot = (int[])entry.api_slot,\r
283                     SlotEx = entry.api_slot_ex() ? (int)entry.api_slot_ex : 0,\r
284                     LoS = (int)entry.api_sakuteki[0],\r
285                     Firepower = (int)entry.api_karyoku[0],\r
286                     AntiSubmarine = (int)entry.api_taisen[0]\r
287                 };\r
288                 _itemInfo.CountNewItems((int[])entry.api_slot);\r
289             }\r
290         }\r
291 \r
292         private void InspectBasic(dynamic json)\r
293         {\r
294             _hqLevel = (int)json.api_level;\r
295         }\r
296 \r
297         public void InspectCharge(dynamic json)\r
298         {\r
299             foreach (var entry in json.api_ship)\r
300             {\r
301                 var status = _shipInfo[(int)entry.api_id];\r
302                 status.Bull = (int)entry.api_bull;\r
303                 status.Fuel = (int)entry.api_fuel;\r
304                 status.OnSlot = (from num in (dynamic[])entry.api_onslot select (int)num).ToArray();\r
305             }\r
306         }\r
307 \r
308         public void InspectChange(string request)\r
309         {\r
310             var values = HttpUtility.ParseQueryString(request);\r
311             var fleet = int.Parse(values["api_id"]) - 1;\r
312             var idx = int.Parse(values["api_ship_idx"]);\r
313             var ship = int.Parse(values["api_ship_id"]);\r
314             if (idx == -1)\r
315             {\r
316                 var deck = _decks[fleet];\r
317                 for (var i = 1; i < deck.Length; i++)\r
318                     deck[i] = -1;\r
319                 return;\r
320             }\r
321             if (ship == -1)\r
322             {\r
323                 WithdrowShip(fleet, idx);\r
324                 return;\r
325             }\r
326             int oi;\r
327             var of = FindFleet(ship, out oi);\r
328             var orig = _decks[fleet][idx];\r
329             _decks[fleet][idx] = ship;\r
330             if (of == -1)\r
331                 return;\r
332             // 入れ替えの場合\r
333             if ((_decks[of][oi] = orig) == -1)\r
334                 WithdrowShip(of, oi);\r
335         }\r
336 \r
337         private int FindFleet(int ship, out int idx)\r
338         {\r
339             for (var f = 0; f < _decks.Length; f++)\r
340             {\r
341                 idx = Array.FindIndex(_decks[f], id => id == ship);\r
342                 if (idx < 0)\r
343                     continue;\r
344                 return f;\r
345             }\r
346             idx = -1;\r
347             return -1;\r
348         }\r
349 \r
350         private void WithdrowShip(int fleet, int idx)\r
351         {\r
352             var deck = _decks[fleet];\r
353             for (var i = idx; i < deck.Length - 1; i++)\r
354                 deck[i] = deck[i + 1];\r
355             deck[deck.Length - 1] = -1;\r
356         }\r
357 \r
358         public void InspectPowerup(string request, dynamic json)\r
359         {\r
360             var values = HttpUtility.ParseQueryString(request);\r
361             var ships = values["api_id_items"].Split(',');\r
362             _itemInfo.NowShips -= ships.Length;\r
363             _itemInfo.DeleteItems(ships.SelectMany(s => _shipInfo[int.Parse(s)].Slot).ToArray());\r
364             foreach (var ship in ships)\r
365                 _shipInfo.Remove(int.Parse(ship));\r
366             InspectDeck(json.api_deck);\r
367             InspectShip(json.api_ship);\r
368         }\r
369 \r
370         public void InspectDestroyShip(string request, dynamic json)\r
371         {\r
372             var values = HttpUtility.ParseQueryString(request);\r
373             var ship = int.Parse(values["api_ship_id"]);\r
374             _itemInfo.NowShips--;\r
375             _itemInfo.DeleteItems(_shipInfo[ship].Slot);\r
376             int oi;\r
377             var of = FindFleet(ship, out oi);\r
378             if (of != -1)\r
379                 WithdrowShip(of, oi);\r
380             _shipInfo.Remove(ship);\r
381         }\r
382 \r
383         public void StartSortie(string request)\r
384         {\r
385             var values = HttpUtility.ParseQueryString(request);\r
386             var fleet = int.Parse(values["api_deck_id"]) - 1;\r
387             if (_combinedFleetType == 0)\r
388             {\r
389                 _inSortie[fleet] = true;\r
390             }\r
391             else\r
392             {\r
393                 _inSortie[0] = _inSortie[1] = true;\r
394             }\r
395         }\r
396 \r
397         public void RepairShip(int id)\r
398         {\r
399             var s = _shipInfo[id];\r
400             s.NowHp = s.MaxHp;\r
401             s.Cond = Max(40, s.Cond);\r
402         }\r
403 \r
404         public ShipStatus[] GetShipStatuses(int fleet)\r
405         {\r
406             return _decks[fleet].Where(id => id != -1).Select(id =>\r
407             {\r
408                 var s = _shipInfo[id];\r
409                 s.Escaped = _escapedShips.Contains(id);\r
410                 return s;\r
411             }).ToArray();\r
412         }\r
413 \r
414         public int[] GetDeck(int fleet) => _decks[fleet];\r
415 \r
416         public ShipStatus this[int idx] => _shipInfo[idx];\r
417 \r
418         public ShipSpec GetSpec(int id) => _shipMaster[id];\r
419 \r
420         public bool InMission(int fleet) => _inMission[fleet];\r
421 \r
422         public bool InSortie(int fleet) => _inSortie[fleet];\r
423 \r
424         public ShipStatus[] ShipList\r
425             => _shipInfo.Values.Where(s => s.Level != 0).Select(s =>\r
426             {\r
427                 int oi;\r
428                 var f = FindFleet(s.Id, out oi);\r
429                 s.Fleet = f;\r
430                 s.Escaped = _escapedShips.Contains(s.Id);\r
431                 return s;\r
432             }).ToArray();\r
433 \r
434         public ChargeStatus[] ChargeStatuses\r
435             => (from deck in _decks\r
436                 let flag = new ChargeStatus(_shipInfo[deck[0]])\r
437                 let others = (from id in deck.Skip(1)\r
438                     select new ChargeStatus(_shipInfo[id]))\r
439                     .Aggregate(\r
440                         (result, next) =>\r
441                             new ChargeStatus(Max(result.Fuel, next.Fuel), Max(result.Bull, next.Bull)))\r
442                 select new ChargeStatus(flag.Fuel != 0 ? flag.Fuel : others.Fuel + 5,\r
443                     flag.Bull != 0 ? flag.Bull : others.Bull + 5)).ToArray();\r
444 \r
445         public int GetFighterPower(int fleet, bool withBonus)\r
446             => GetShipStatuses(fleet).Where(s => !s.Escaped).SelectMany(ship =>\r
447                 ship.Slot.Zip(ship.OnSlot, (slot, onslot) =>\r
448                 {\r
449                     var item = _itemInfo.GetStatus(slot);\r
450                     if (!item.Spec.CanAirCombat)\r
451                         return 0;\r
452                     var bonus = onslot != 0 && withBonus ? item.AlvBonus : 0;\r
453                     return (int)Floor(item.Spec.AntiAir * Sqrt(onslot)) + bonus;\r
454                 })).Sum();\r
455 \r
456         public ShipStatus[] GetDamagedShipList(DockInfo dockInfo)\r
457             => (from s in ShipList\r
458                 where s.NowHp < s.MaxHp && !dockInfo.InNDock(s.Id)\r
459                 select s).OrderByDescending(s => s.RepairTime).ToArray();\r
460 \r
461 \r
462         public double GetLineOfSights(int fleet)\r
463         {\r
464             var result = 0.0;\r
465             foreach (var s in _decks[fleet].Select(id => _shipInfo[id]))\r
466             {\r
467                 var items = 0;\r
468                 foreach (var spec in s.Slot.Select(id => _itemInfo.GetStatus(id).Spec))\r
469                 {\r
470                     items += spec.LoS;\r
471                     result += spec.LoS * spec.LoSScaleFactor();\r
472                 }\r
473                 result += Sqrt(s.LoS - items) * 1.6841056;\r
474             }\r
475             return result > 0 ? result + (_hqLevel + 4) / 5 * 5 * -0.6142467 : 0.0;\r
476         }\r
477 \r
478         public void SetEscapedShips(List<int> ships)\r
479         {\r
480             _escapedShips.AddRange(ships);\r
481         }\r
482 \r
483         public void ClearEscapedShips()\r
484         {\r
485             _escapedShips.Clear();\r
486         }\r
487     }\r
488 }