1 // Copyright (C) 2018 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 // ReSharper disable CompareOfFloatsByEqualityOperator
\r
22 namespace KancolleSniffer.Model
\r
24 public class ChargeStatus
\r
26 public bool Empty => FuelRatio > 1.0;
\r
28 public double FuelRatio { get; set; }
\r
29 public int Fuel { get; set; }
\r
31 public double BullRatio { get; set; }
\r
32 public int Bull { get; set; }
\r
34 private ChargeStatus()
\r
38 public ChargeStatus(ShipStatus status)
\r
40 if (status.Spec.FuelMax == 0)
\r
46 FuelRatio = (double)status.Fuel / status.Spec.FuelMax;
\r
47 BullRatio = (double)status.Bull / status.Spec.BullMax;
\r
50 public void SetState()
\r
52 Fuel = CalcChargeState(FuelRatio);
\r
53 Bull = CalcChargeState(BullRatio);
\r
56 public void SetOthersState()
\r
63 private static int CalcChargeState(double ratio)
\r
69 if (ratio >= 7.0 / 9)
\r
71 if (ratio >= 3.0 / 9)
\r
78 public static ChargeStatus Min(ChargeStatus a, ChargeStatus b)
\r
80 return new ChargeStatus
\r
82 FuelRatio = Math.Min(a.FuelRatio, b.FuelRatio),
\r
83 BullRatio = Math.Min(a.BullRatio, b.BullRatio)
\r
88 public enum FleetState
\r
96 public enum CombinedType
\r
106 private readonly ShipInventory _shipInventory;
\r
107 private readonly Func<int> _getHqLevel;
\r
108 private int[] _deck = Enumerable.Repeat(-1, ShipInfo.MemberCount).ToArray();
\r
109 public int Number { get; }
\r
110 public FleetState State { get; set; }
\r
111 public CombinedType CombinedType { get; set; }
\r
112 public IReadOnlyList<ShipStatus> Ships { get; private set; }
\r
113 public IReadOnlyList<ShipStatus> ActualShips { get; private set; }
\r
115 public Fleet(ShipInventory shipInventory, int number, Func<int> getHqLevel)
\r
117 _shipInventory = shipInventory;
\r
119 _getHqLevel = getHqLevel;
\r
120 Ships = _deck.Select(id => new ShipStatus()).ToArray();
\r
121 ActualShips = new ShipStatus[0];
\r
124 public IReadOnlyList<int> Deck
\r
126 get => _deck.ToArray();
\r
129 _deck = value.ToArray();
\r
134 public void SetDeck()
\r
136 foreach (var ship in Ships)
\r
138 if (ship.Fleet != this) // 入れ替え操作で他の艦隊に移動しているときには触らない。
\r
141 ship.DeckIndex = -1;
\r
143 Ships = _deck.Select((id, num) =>
\r
145 var ship = _shipInventory[id];
\r
148 ship.DeckIndex = num;
\r
152 ActualShips = Ships.Where(ship => !ship.Empty).ToArray();
\r
155 public int SetShip(int index, int shipId)
\r
157 var prev = _deck[index];
\r
158 _deck[index] = shipId;
\r
163 public void WithdrawAccompanyingShips()
\r
165 for (var i = 1; i < _deck.Length; i++)
\r
170 public void WithdrawShip(int index)
\r
173 for (var src = index + 1; src < _deck.Length; src++)
\r
175 if (_deck[src] != -1)
\r
176 _deck[dst++] = _deck[src];
\r
178 for (; dst < _deck.Length; dst++)
\r
183 public ChargeStatus ChargeStatus
\r
187 var fs = new ChargeStatus(Ships[0]);
\r
188 var others = (from ship in Ships.Skip(1) select new ChargeStatus(ship)).Aggregate(ChargeStatus.Min);
\r
190 others.SetOthersState();
\r
195 fs.Fuel = others.Fuel;
\r
196 fs.FuelRatio = others.FuelRatio;
\r
200 fs.Bull = others.Bull;
\r
201 fs.BullRatio = others.BullRatio;
\r
207 public Range FighterPower
\r
208 => ActualShips.Where(ship => !ship.Escaped).SelectMany(ship =>
\r
209 ship.Slot.Zip(ship.OnSlot, (slot, onSlot) => slot.CalcFighterPower(onSlot)))
\r
210 .Aggregate(new Range(0, 0), (prev, cur) => prev + cur);
\r
212 public double ContactTriggerRate
\r
213 => ActualShips.Where(ship => !ship.Escaped).SelectMany(ship =>
\r
214 ship.Slot.Zip(ship.OnSlot, (slot, onSlot) =>
\r
215 slot.Spec.ContactTriggerRate * slot.Spec.LoS * Sqrt(onSlot))).Sum();
\r
217 public double GetLineOfSights(int factor)
\r
220 var emptyBonus = 6;
\r
221 foreach (var s in ActualShips.Where(s => !s.Escaped))
\r
225 foreach (var item in s.AllSlot)
\r
227 var spec = item.Spec;
\r
228 itemLoS += spec.LoS;
\r
229 result += (spec.LoS + item.LoSLevelBonus) * spec.LoSScaleFactor * factor;
\r
231 result += Sqrt(s.LoS - itemLoS);
\r
233 return result > 0 ? result - Ceiling(_getHqLevel() * 0.4) + emptyBonus * 2 : 0.0;
\r
236 public double AirReconScore =>
\r
237 ActualShips.SelectMany(ship =>
\r
238 ship.OnSlot.Zip(ship.Slot, (slot, item) => item.Spec.CalcAirReconScore(slot))).Sum();
\r
240 public double DaihatsuBonus
\r
244 // ReSharper disable IdentifierTypo
\r
245 var tokudaiBonus = new[,]
\r
247 {0.00, 0.00, 0.00, 0.00, 0.00},
\r
248 {0.02, 0.02, 0.02, 0.02, 0.02},
\r
249 {0.04, 0.04, 0.04, 0.04, 0.04},
\r
250 {0.05, 0.05, 0.052, 0.054, 0.054},
\r
251 {0.054, 0.056, 0.058, 0.059, 0.06}
\r
258 // ReSharper restore IdentifierTypo
\r
259 foreach (var ship in Ships)
\r
261 if (ship.Name == "鬼怒改二")
\r
263 foreach (var item in ship.Slot)
\r
265 switch (item.Spec.Name)
\r
268 level += item.Level;
\r
274 level += item.Level;
\r
279 case "大発動艇(八九式中戦車&陸戦隊)":
\r
280 level += item.Level;
\r
285 level += item.Level;
\r
292 var levelAverage = sum == 0 ? 0.0 : (double)level / sum;
\r
293 bonus = Min(bonus, 0.2);
\r
294 var result = bonus + 0.01 * bonus * levelAverage + tokudaiBonus[Min(tokudai, 4), Min(daihatsu, 4)];
\r
295 return Floor(result * 1000) / 10;
\r
299 public double TransportPoint => Ships.Where(ship => !ship.Escaped).Sum(ship => ship.TransportPoint);
\r
301 public int CombinedFirepowerBonus
\r
305 switch (CombinedType)
\r
307 case CombinedType.None:
\r
309 case CombinedType.Carrier:
\r
310 return Number == 0 ? 2 : 10;
\r
311 case CombinedType.Surface:
\r
312 return Number == 0 ? 10 : -5;
\r
313 case CombinedType.Transport:
\r
314 return Number == 0 ? -5 : 10;
\r
321 public int CombinedTorpedoPenalty => CombinedType != 0 && Number == 1 ? -5 : 0;
\r
323 public double NightContactTriggerRate => (1.0 - NightContactFailRate) * 100;
\r
325 private double NightContactFailRate => Ships.Aggregate(1.0,
\r
326 (perFleet, ship) => ship.OnSlot.Where((onSlot, i) => onSlot > 0 && ship.Slot[i].Spec.IsNightRecon)
\r
327 .Aggregate(perFleet, (perShip, _) => perShip * NightContactFailRateOne(ship)));
\r
329 private static double NightContactFailRateOne(ShipStatus ship) => 1.0 - Floor(Sqrt(ship.Level * 3)) * 4 / 100.0;
\r
331 public string MissionParameter
\r
335 var result = new List<string>();
\r
336 var kira = Ships.Count(ship => ship.Cond > 49);
\r
339 var min = Ships.Where(ship => ship.Cond > 49).Min(ship => ship.Cond);
\r
340 var mark = Ships[0].Cond > 49 ? "+" : "";
\r
341 result.Add($"キラ{kira}{mark}⪰{min}");
\r
343 var drums = Ships.SelectMany(ship => ship.Slot).Count(item => item.Spec.IsDrum);
\r
344 var drumShips = Ships.Count(ship => ship.Slot.Any(item => item.Spec.IsDrum));
\r
346 result.Add($"ド{drums}({drumShips}隻)");
\r
347 if (DaihatsuBonus > 0)
\r
348 result.Add($"ダ{DaihatsuBonus:f1}%");
\r
349 return string.Join(" ", result);
\r