1 // Copyright (C) 2017 Kazuhiro Fujieda <fujieda@users.osdn.me>
\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
7 // http://www.apache.org/licenses/LICENSE-2.0
\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
16 using System.Collections.Generic;
\r
18 using static System.Math;
\r
20 namespace KancolleSniffer.Model
\r
22 public class ShipStatus : ICloneable
\r
24 public int Id { get; set; }
\r
25 public bool Empty => Id == -1;
\r
26 public Fleet Fleet { get; set; }
\r
27 public int DeckIndex { get; set; }
\r
28 public ShipSpec Spec { get; set; }
\r
30 public string Name => Spec.Name;
\r
32 public int Level { get; set; }
\r
33 public int ExpToNext { get; set; }
\r
34 public int MaxHp { get; set; }
\r
35 public int NowHp { get; set; }
\r
36 public int Speed { get; set; }
\r
37 public int Cond { get; set; }
\r
38 public int Fuel { get; set; }
\r
39 public int Bull { get; set; }
\r
40 public int[] OnSlot { get; set; }
\r
41 public int NdockTime { get; set; }
\r
42 public int[] NdockItem { get; set; }
\r
43 public int LoS { get; set; }
\r
44 public int Firepower { get; set; }
\r
45 public int Torpedo { get; set; }
\r
46 public int AntiSubmarine { get; set; }
\r
47 public int AntiAir { get; set; }
\r
48 public int Lucky { get; set; }
\r
49 public bool Locked { get; set; }
\r
50 public bool Escaped { get; set; }
\r
51 public Attack SpecialAttack { get; set; }
\r
60 public Damage DamageLevel => CalcDamage(NowHp, MaxHp);
\r
62 private IList<ItemStatus> _slot;
\r
63 private ItemStatus _slotEx;
\r
64 public Func<ItemStatus, ItemStatus> GetItem { get; set; } = item => item;
\r
66 public IReadOnlyList<ItemStatus> Slot
\r
68 get => _slot.Select(item => GetItem(item)).ToArray();
\r
69 set => _slot = value.ToArray();
\r
72 public ItemStatus SlotEx
\r
74 get => GetItem(_slotEx);
\r
75 set => _slotEx = value;
\r
78 public void FreeSlot(int idx) => _slot[idx] = new ItemStatus();
\r
80 public IEnumerable<ItemStatus> AllSlot => SlotEx.Id == 0 ? Slot : Slot.Concat(new[] {SlotEx});
\r
85 Spec = new ShipSpec();
\r
86 OnSlot = new int[0];
\r
87 Slot = new ItemStatus[0];
\r
88 SlotEx = new ItemStatus();
\r
100 public void RepairShip()
\r
103 Cond = Max(40, Cond);
\r
106 public static Damage CalcDamage(int now, int max)
\r
108 if (now == 0 && max > 0)
\r
109 return Damage.Sunk;
\r
110 var ratio = max == 0 ? 1 : (double)now / max;
\r
111 return ratio > 0.75 ? Damage.Minor :
\r
112 ratio > 0.5 ? Damage.Small :
\r
113 ratio > 0.25 ? Damage.Half : Damage.Badly;
\r
116 public TimeSpan RepairTime => TimeSpan.FromSeconds((int)(RepairTimePerHp.TotalSeconds * (MaxHp - NowHp)) + 30);
\r
118 public TimeSpan RepairTimePerHp =>
\r
119 TimeSpan.FromSeconds(Spec.RepairWeight *
\r
122 : Level * 5 + Floor(Sqrt(Level - 11)) * 10 + 50));
\r
124 public double EffectiveFirepower
\r
128 if (Spec.IsSubmarine)
\r
130 var isRyuseiAttack = Spec.Id == 352 && // 速吸改
\r
131 Slot.Any(item => item.Spec.Type == 8); // 艦攻装備
\r
132 var levelBonus = AllSlot.Sum(item => item.FirepowerLevelBonus);
\r
133 if (!Spec.IsAircraftCarrier && !isRyuseiAttack)
\r
134 return Firepower + levelBonus + Fleet.CombinedFirepowerBonus + 5;
\r
135 var specs = (from item in Slot where item.Spec.IsAircraft select item.Spec).ToArray();
\r
136 var torpedo = specs.Sum(s => s.Torpedo);
\r
137 var bomber = specs.Sum(s => s.Bomber);
\r
138 if (torpedo == 0 && bomber == 0)
\r
140 return (int)((Firepower + torpedo + levelBonus +
\r
141 (int)(bomber * 1.3) + Fleet.CombinedFirepowerBonus) * 1.5) + 55;
\r
145 public double EffectiveTorpedo
\r
149 if (Spec.IsAircraftCarrier || Torpedo == 0)
\r
151 return Torpedo + AllSlot.Sum(item => item.TorpedoLevelBonus) + Fleet.CombinedTorpedoPenalty + 5;
\r
155 public double EffectiveAntiSubmarine
\r
159 if (!Spec.IsAntiSubmarine)
\r
161 // ReSharper disable once CompareOfFloatsByEqualityOperator
\r
162 if (Spec.IsAircraftCarrier && EffectiveFirepower == 0 && !CanOpeningAntiSubmarineAttack)
\r
164 var check = new AntiSubmarineChecker(Slot);
\r
165 var vanilla = ShipAntiSubmarine;
\r
166 if (vanilla == 0 && !check.Aircraft) // 素対潜0で航空機なしは対潜攻撃なし
\r
169 if (check.DCT && check.DC)
\r
171 if (check.Sonar && (check.DCT || check.DC || check.SpecialDCT))
\r
173 if (check.Sonar && check.DCT && check.DC)
\r
174 bonus = 1.15 * 1.25;
\r
175 var levelBonus = Slot.Sum(item => item.AntiSubmarineLevelBonus);
\r
176 return bonus * (Sqrt(vanilla) * 2 + check.All * 1.5 + levelBonus + (check.Aircraft ? 8 : 13));
\r
180 private class AntiSubmarineChecker
\r
182 public readonly bool Sonar;
\r
183 public readonly bool DCT;
\r
184 public readonly bool DC;
\r
185 public readonly bool SpecialDCT;
\r
186 public readonly bool Aircraft;
\r
187 public readonly double All;
\r
189 public AntiSubmarineChecker(IEnumerable<ItemStatus> items)
\r
191 foreach (var spec in items.Select(item => item.Spec))
\r
197 else if (spec.IsDCT)
\r
201 else if (spec.IsDC)
\r
205 else if (spec.IsSpecialDCT)
\r
209 else if (spec.IsAircraft)
\r
213 All += spec.EffectiveAntiSubmarine;
\r
218 public int ShipAntiSubmarine => AntiSubmarine - Slot.Sum(item => item.Spec.AntiSubmarine);
\r
220 public bool CanOpeningAntiSubmarineAttack
\r
224 var specs = Slot.Select(item => item.Spec).ToArray();
\r
230 case "Samuel B.Roberts改":
\r
238 return specs.Any(spec => spec.IsTorpedoBomber && spec.AntiSubmarine >= 1 ||
\r
239 spec.IsArmyAircraft || spec.IsDiveBomber);
\r
241 case "Gambier Bay":
\r
242 case "Gambier Bay改":
\r
245 if (AntiSubmarine < 50)
\r
247 if (AntiSubmarine >= 50 && AntiSubmarine < 100)
\r
248 return (AntiSubmarine >= 65 || HaveSonar) &&
\r
249 specs.Any(spec => spec.IsTorpedoBomber && spec.AntiSubmarine >= 7 ||
\r
250 spec.IsArmyAircraft);
\r
251 return HaveSonar &&
\r
252 specs.Any(spec => spec.IsTorpedoBomber && spec.AntiSubmarine >= 1 ||
\r
253 spec.IsDiveBomber);
\r
255 if (AntiSubmarine < 50)
\r
257 return HaveSonar &&
\r
258 specs.Any(spec => spec.IsTorpedoBomber && spec.AntiSubmarine >= 7 ||
\r
259 spec.IsArmyAircraft);
\r
261 if (HaveSonar && AntiSubmarine >= 100)
\r
263 if (Spec.ShipType != 1)
\r
265 return Slot.Sum(item => item.Spec.AntiSubmarine) >= 4 && AntiSubmarine >= 75 ||
\r
266 HaveSonar && AntiSubmarine >= 60;
\r
271 public int MissionAntiSubmarine => AntiSubmarine - AllSlot.Sum(item =>
\r
273 switch (item.Spec.Type)
\r
278 return item.Spec.AntiSubmarine;
\r
284 private bool HaveSonar => Slot.Any(item => item.Spec.IsSonar);
\r
286 public double NightBattlePower
\r
290 if (!Spec.IsAircraftCarrier)
\r
291 return Firepower + Torpedo + Slot.Sum(item => item.NightBattleLevelBonus);
\r
293 if (Slot.Any(item => item.Spec.IconType == 45 || item.Spec.IconType == 46) && // 夜戦か夜攻
\r
294 (Spec.Id == 545 || // Saratoga Mk.II
\r
295 Slot.Any(item => item.Spec.Id == 258 || item.Spec.Id == 259))) // 夜間作戦航空要員
\r
297 return Firepower + Slot.Zip(OnSlot, (item, onSlot) =>
\r
300 var spec = item.Spec;
\r
303 case 154: // 零戦62型(爆戦/岩井隊)
\r
304 case 242: // Swordfish
\r
305 case 243: // Swordfish Mk.II(熟練)
\r
306 case 244: // Swordfish Mk.III(熟練)
\r
310 case 254: // F6F-3N
\r
311 case 255: // F6F-5N
\r
312 case 257: // TBD-3D
\r
317 return -spec.Firepower;
\r
319 return spec.Torpedo + a * onSlot +
\r
320 b * (spec.Firepower + spec.Torpedo + spec.Bomber + spec.AntiSubmarine) *
\r
321 Sqrt(onSlot) + Sqrt(item.Level);
\r
326 case 353: // Graf Zeppelin改
\r
327 case 432: // Graf Zeppelin
\r
328 case 433: // Saratoga
\r
330 case 393: // Ark Royal改
\r
331 case 515: // Ark Royal
\r
332 if (Slot.Any(item => new[] {242, 243, 244}.Contains(item.Spec.Id)))
\r
338 return Firepower + Torpedo + Slot.Sum(item => item.NightBattleLevelBonus);
\r
342 public int PreparedDamageControl =>
\r
343 DamageLevel != Damage.Badly
\r
345 : SlotEx.Spec.Id == 42 || SlotEx.Spec.Id == 43
\r
347 : Slot.FirstOrDefault(item => item.Spec.Id == 42 || item.Spec.Id == 43)?.Spec.Id ?? -1;
\r
349 public double TransportPoint
\r
350 => Spec.TransportPoint + AllSlot.Sum(item => item.Spec.TransportPoint);
\r
352 public int EffectiveAntiAirForShip
\r
356 if (AllSlot.All(item => item.Empty || item.Unimplemented))
\r
358 var vanilla = AntiAir - AllSlot.Sum(item => item.Spec.AntiAir);
\r
359 var x = vanilla + AllSlot.Sum(item => item.EffectiveAntiAirForShip);
\r
360 return (int)(x / 2) * 2;
\r
364 public int EffectiveAntiAirForFleet => (int)AllSlot.Sum(item => item.EffectiveAntiAirForFleet);
\r
366 public double AntiAirPropellantBarrageChance
\r
370 var launcherCount = AllSlot.Count(item => item.Spec.Id == 274);
\r
371 if (launcherCount == 0)
\r
373 var iseClass = Spec.ShipClass == 2;
\r
374 var baseChance = (EffectiveAntiAirForShip + 0.9 * Lucky) / 281.0;
\r
375 return (baseChance + 0.15 * (launcherCount - 1) + (iseClass ? 0.25 : 0)) * 100;
\r
379 public int EffectiveFuelMax => Max((int)(Spec.FuelMax * (Level >= 100 ? 0.85 : 1.0)), 1);
\r
381 public int EffectiveBullMax => Max((int)(Spec.BullMax * (Level >= 100 ? 0.85 : 1.0)), 1);
\r
383 public object Clone()
\r
385 var r = (ShipStatus)MemberwiseClone();
\r
386 r.Slot = r.Slot.ToArray(); // 戦闘中のダメコンの消費が見えないように複製する
\r