1 // Copyright (C) 2014, 2015 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
\r
22 public enum BattleResultRank
\r
33 public enum BattleState
\r
42 public class EnemyFighterPower
\r
44 public bool HasUnknown { get; set; }
\r
45 public string UnknownMark => HasUnknown ? "+" : "";
\r
46 public int AirCombat { get; set; }
\r
47 public int Interception { get; set; }
\r
50 public class BattleInfo
\r
52 private readonly ShipInfo _shipInfo;
\r
53 private readonly ItemInfo _itemInfo;
\r
55 private Record[] _friend;
\r
56 private Record[] _guard;
\r
57 private Record[] _enemy;
\r
58 private Record[] _enemyGuard;
\r
59 private readonly List<int> _escapingShips = new List<int>();
\r
60 private bool _lastCell;
\r
62 public BattleState BattleState { get; set; }
\r
63 public int[] Formation { get; private set; }
\r
64 public int[] FighterPower { get; private set; }
\r
65 public EnemyFighterPower EnemyFighterPower { get; private set; }
\r
66 public int AirControlLevel { get; private set; }
\r
67 public BattleResultRank ResultRank { get; private set; }
\r
68 public RankPair DisplayedResultRank { get; } = new RankPair();
\r
69 public BattleResult Result { get; set; }
\r
70 public bool EnemyIsCombined => _enemyGuard.Length > 0;
\r
71 public List<AirBattleResult> AirBattleResults { get; } = new List<AirBattleResult>();
\r
73 public class RankPair
\r
75 public char Assumed { get; set; }
\r
76 public char Actual { get; set; }
\r
77 public bool IsError => Assumed != Actual;
\r
80 public class BattleResult
\r
82 public class Combined
\r
84 public ShipStatus[] Main { get; set; }
\r
85 public ShipStatus[] Guard { get; set; }
\r
88 public Combined Friend { get; set; }
\r
89 public Combined Enemy { get; set; }
\r
92 public BattleInfo(ShipInfo shipInfo, ItemInfo itemInfo)
\r
94 _shipInfo = shipInfo;
\r
95 _itemInfo = itemInfo;
\r
98 public void InspectBattle(string url, string request, dynamic json)
\r
100 if (json.api_formation())
\r
101 Formation = ((dynamic[])json.api_formation).Select(f => f is string ? (int)int.Parse(f) : (int)f)
\r
103 AirControlLevel = CheckAirControlLevel(json);
\r
104 ShowResult(false); // 昼戦の結果を夜戦のときに表示する
\r
105 SetupResult(request, json, url.Contains("practice"));
\r
106 FighterPower = CalcFighterPower();
\r
107 EnemyFighterPower = CalcEnemyFighterPower(json);
\r
108 BattleState = IsNightBattle(json) ? BattleState.Night : BattleState.Day;
\r
110 ResultRank = url.EndsWith("ld_airbattle") ? CalcLdAirBattleRank() : CalcResultRank();
\r
114 private bool IsNightBattle(dynamic json) => json.api_hougeki();
\r
116 public static int DeckId(dynamic json)
\r
118 if (json.api_dock_id()) // 昼戦はtypoしている
\r
119 return (int)json.api_dock_id - 1;
\r
120 if (json.api_deck_id is string) // 通常の夜戦と連合艦隊(味方のみ)では文字列
\r
121 return int.Parse(json.api_deck_id) - 1;
\r
122 return (int)json.api_deck_id - 1;
\r
125 private void SetupResult(string request, dynamic json, bool practice)
\r
127 if (_friend != null)
\r
129 _shipInfo.SaveBattleStartStatus();
\r
130 _fleet = DeckId(json);
\r
131 var fstats = _shipInfo.GetShipStatuses(_fleet);
\r
132 FlagshipRecovery(request, fstats[0]);
\r
133 _friend = Record.Setup(fstats, practice);
\r
134 _guard = json.api_f_nowhps_combined()
\r
135 ? Record.Setup(_shipInfo.GetShipStatuses(1), practice)
\r
137 _enemy = Record.Setup((int[])json.api_e_nowhps,
\r
138 ((int[])json.api_ship_ke).Select(_shipInfo.GetSpec).ToArray(),
\r
139 ((int[][])json.api_eSlot).Select(slot => slot.Select(_itemInfo.GetSpecByItemId).ToArray()).ToArray(),
\r
141 _enemyGuard = json.api_ship_ke_combined()
\r
142 ? Record.Setup((int[])json.api_e_nowhps_combined,
\r
143 ((int[])json.api_ship_ke_combined).Select(_shipInfo.GetSpec).ToArray(),
\r
144 ((int[][])json.api_eSlot).Select(slot => slot.Select(_itemInfo.GetSpecByItemId).ToArray())
\r
145 .ToArray(), practice)
\r
149 private void SetResult()
\r
151 Result = new BattleResult
\r
153 Friend = new BattleResult.Combined
\r
155 Main = _friend.Select(r => r.SnapShot).ToArray(),
\r
156 Guard = _guard.Select(r => r.SnapShot).ToArray()
\r
158 Enemy = new BattleResult.Combined
\r
160 Main = _enemy.Select(r => r.SnapShot).ToArray(),
\r
161 Guard = _enemyGuard.Select(r => r.SnapShot).ToArray()
\r
166 private void FlagshipRecovery(string request, ShipStatus flagship)
\r
168 var type = int.Parse(HttpUtility.ParseQueryString(request)["api_recovery_type"] ?? "0");
\r
174 flagship.NowHp = flagship.MaxHp / 2;
\r
175 ConsumeSlotItem(flagship, 42); // ダメコン
\r
178 flagship.NowHp = flagship.MaxHp;
\r
179 ConsumeSlotItem(flagship, 43); // 女神
\r
183 _shipInfo.SetBadlyDamagedShips();
\r
186 private static void ConsumeSlotItem(ShipStatus ship, int id)
\r
188 if (ship.SlotEx.Spec.Id == id)
\r
190 ship.SlotEx = new ItemStatus();
\r
193 for (var i = 0; i < ship.Slot.Length; i++)
\r
195 if (ship.Slot[i].Spec.Id == id)
\r
197 ship.Slot[i] = new ItemStatus();
\r
203 public void CleanupResult()
\r
209 private int CheckAirControlLevel(dynamic json)
\r
211 if (!json.api_kouku())
\r
213 var stage1 = json.api_kouku.api_stage1;
\r
214 if (stage1 == null)
\r
216 if (stage1.api_f_count == 0 && stage1.api_e_count == 0)
\r
218 return (int)stage1.api_disp_seiku;
\r
221 private int[] CalcFighterPower()
\r
223 if (_guard.Length > 0 && _enemyGuard.Length > 0)
\r
224 return _shipInfo.GetFighterPower(0).Zip(_shipInfo.GetFighterPower(1), (a, b) => a + b).ToArray();
\r
225 return _shipInfo.GetFighterPower(_fleet);
\r
228 private EnemyFighterPower CalcEnemyFighterPower(dynamic json)
\r
230 var result = new EnemyFighterPower();
\r
231 var ships = (int[])json.api_ship_ke;
\r
232 if (json.api_ship_ke_combined() && _guard.Length > 0)
\r
233 ships = ships.Concat((int[])json.api_ship_ke_combined).ToArray();
\r
234 var maxEq = ships.SelectMany(id =>
\r
236 var r = _shipInfo.GetSpec(id).MaxEq;
\r
239 result.HasUnknown = true;
\r
242 var equips = ((int[][])json.api_eSlot).SelectMany(x => x);
\r
243 if (json.api_eSlot_combined() && _guard.Length > 0)
\r
244 equips = equips.Concat(((int[][])json.api_eSlot_combined).SelectMany(x => x));
\r
245 foreach (var entry in from slot in equips.Zip(maxEq, (id, max) => new {id, max})
\r
246 let spec = _itemInfo.GetSpecByItemId(slot.id)
\r
247 let perSlot = (int)Floor(spec.AntiAir * Sqrt(slot.max))
\r
248 select new {spec, perSlot})
\r
250 if (entry.spec.CanAirCombat)
\r
251 result.AirCombat += entry.perSlot;
\r
252 if (entry.spec.IsAircraft)
\r
253 result.Interception += entry.perSlot;
\r
258 private enum CombatType
\r
268 private class Phase
\r
270 public string Api { get; }
\r
271 public CombatType Type { get; }
\r
272 public string Name { get; }
\r
274 public Phase(string api, CombatType type, string name = "")
\r
282 private void CalcDamage(dynamic json)
\r
284 AirBattleResults.Clear();
\r
287 new Phase("air_base_injection", CombatType.Aircraft, "AB噴式"),
\r
288 new Phase("injection_kouku", CombatType.Aircraft, "噴式"),
\r
289 new Phase("air_base_attack", CombatType.AirBase),
\r
290 new Phase("n_support_info", CombatType.Support),
\r
291 new Phase("n_hougeki1", CombatType.ByTurn),
\r
292 new Phase("n_hougeki2", CombatType.ByTurn),
\r
293 new Phase("kouku", CombatType.Aircraft, "航空戦"),
\r
294 new Phase("kouku2", CombatType.Aircraft, "航空戦2"),
\r
295 new Phase("support_info", CombatType.Support),
\r
296 new Phase("opening_taisen", CombatType.ByTurn),
\r
297 new Phase("opening_atack", CombatType.AtOnce),
\r
298 new Phase("friendly_battle", CombatType.Friend),
\r
299 new Phase("hougeki", CombatType.ByTurn),
\r
300 new Phase("hougeki1", CombatType.ByTurn),
\r
301 new Phase("hougeki2", CombatType.ByTurn),
\r
302 new Phase("hougeki3", CombatType.ByTurn),
\r
303 new Phase("raigeki", CombatType.AtOnce)
\r
305 foreach (var phase in phases)
\r
306 CalcDamageByType(json, phase);
\r
309 private void CalcDamageByType(dynamic json, Phase phase)
\r
311 var api = "api_" + phase.Api;
\r
312 if (!json.IsDefined(api) || json[api] == null)
\r
314 switch (phase.Type)
\r
316 case CombatType.AtOnce:
\r
317 CalcDamageAtOnce(json[api]);
\r
319 case CombatType.ByTurn:
\r
320 CalcDamageByTurn(json[api]);
\r
322 case CombatType.Support:
\r
323 CalcSupportDamage(json[api]);
\r
325 case CombatType.Aircraft:
\r
326 AddAirBattleResult(json[api], phase.Name);
\r
327 CalcKoukuDamage(json[api]);
\r
329 case CombatType.AirBase:
\r
330 CalcAirBaseAttackDamage(json[api]);
\r
332 case CombatType.Friend:
\r
333 CalcFriendAttackDamage(json[api]);
\r
338 private void CalcSupportDamage(dynamic json)
\r
340 if (json.api_support_hourai != null)
\r
342 CalcRawDamageAtOnce(json.api_support_hourai.api_damage, _enemy, _enemyGuard);
\r
344 else if (json.api_support_airatack != null)
\r
346 CalcRawDamageAtOnce(json.api_support_airatack.api_stage3.api_edam, _enemy, _enemyGuard);
\r
350 private void CalcAirBaseAttackDamage(dynamic json)
\r
353 foreach (var entry in json)
\r
355 AddAirBattleResult(entry, "基地" + i++);
\r
356 CalcKoukuDamage(entry);
\r
360 private void CalcFriendAttackDamage(dynamic json)
\r
362 CalcDamageByTurn(json.api_hougeki, true);
\r
365 private void AddAirBattleResult(dynamic json, string phaseName)
\r
367 var stage1 = json.api_stage1;
\r
368 if (stage1 == null || (stage1.api_f_count == 0 && stage1.api_e_count == 0))
\r
370 var result = new AirBattleResult
\r
372 PhaseName = phaseName,
\r
373 AirControlLevel = json.api_stage1.api_disp_seiku() ? (int)json.api_stage1.api_disp_seiku : 0,
\r
374 Stage1 = new AirBattleResult.StageResult
\r
376 FriendCount = (int)json.api_stage1.api_f_count,
\r
377 FriendLost = (int)json.api_stage1.api_f_lostcount,
\r
378 EnemyCount = (int)json.api_stage1.api_e_count,
\r
379 EnemyLost = (int)json.api_stage1.api_e_lostcount
\r
381 Stage2 = json.api_stage2 == null
\r
382 ? new AirBattleResult.StageResult
\r
389 : new AirBattleResult.StageResult
\r
391 FriendCount = (int)json.api_stage2.api_f_count,
\r
392 FriendLost = (int)json.api_stage2.api_f_lostcount,
\r
393 EnemyCount = (int)json.api_stage2.api_e_count,
\r
394 EnemyLost = (int)json.api_stage2.api_e_lostcount
\r
397 if (json.api_stage2 != null && json.api_stage2.api_air_fire())
\r
399 var airfire = json.api_stage2.api_air_fire;
\r
400 var idx = (int)airfire.api_idx;
\r
401 result.AirFire = new AirBattleResult.AirFireResult
\r
403 ShipName = idx < _friend.Length ? _friend[idx].Name : _guard[idx - 6].Name,
\r
404 Kind = (int)airfire.api_kind,
\r
405 Items = ((int[])airfire.api_use_items).Select(id => _itemInfo.GetSpecByItemId(id).Name).ToArray()
\r
408 AirBattleResults.Add(result);
\r
411 private void CalcKoukuDamage(dynamic json)
\r
413 if (json.api_stage3() && json.api_stage3 != null)
\r
414 CalcDamageAtOnce(json.api_stage3, _friend, _enemy);
\r
415 if (json.api_stage3_combined() && json.api_stage3_combined != null)
\r
416 CalcDamageAtOnce(json.api_stage3_combined, _guard, _enemyGuard);
\r
419 private void CalcDamageAtOnce(dynamic json)
\r
421 CalcDamageAtOnce(json, _friend, _guard, _enemy, _enemyGuard);
\r
424 private void CalcDamageAtOnce(dynamic json, Record[] friend, Record[] enemy)
\r
426 CalcDamageAtOnce(json, friend, null, enemy, null);
\r
429 private void CalcDamageAtOnce(dynamic json,
\r
430 Record[] friend, Record[] guard, Record[] enemy, Record[] enemyGuard)
\r
432 if (json.api_fdam() && json.api_fdam != null)
\r
433 CalcRawDamageAtOnce(json.api_fdam, friend, guard);
\r
434 if (json.api_edam() && json.api_edam != null)
\r
435 CalcRawDamageAtOnce(json.api_edam, enemy, enemyGuard);
\r
438 private void CalcRawDamageAtOnce(dynamic rawDamage, Record[] friend, Record[] guard = null)
\r
440 var damage = (int[])rawDamage;
\r
441 for (var i = 0; i < friend.Length; i++)
\r
442 friend[i].ApplyDamage(damage[i]);
\r
445 for (var i = 0; i < guard.Length; i++)
\r
446 guard[i].ApplyDamage(damage[i + 6]);
\r
449 private void CalcDamageByTurn(dynamic json, bool ignoreFriendDamage = false)
\r
451 if (!(json.api_df_list() && json.api_df_list != null &&
\r
452 json.api_damage() && json.api_damage != null &&
\r
453 json.api_at_eflag() && json.api_at_eflag != null))
\r
456 var targets = (int[][])json.api_df_list;
\r
457 var damages = (int[][])json.api_damage;
\r
458 var eflags = (int[])json.api_at_eflag;
\r
459 var records = new[] {new Record[12], new Record[12]};
\r
460 Array.Copy(_friend, records[1], _friend.Length);
\r
461 Array.Copy(_guard, 0, records[1], 6, _guard.Length);
\r
462 Array.Copy(_enemy, records[0], _enemy.Length);
\r
463 Array.Copy(_enemyGuard, 0, records[0], 6, _enemyGuard.Length);
\r
464 for (var i = 0; i < eflags.Length; i++)
\r
466 // 一度に複数の目標を狙う攻撃はないものと仮定する
\r
467 var hit = new {t = targets[i][0], d = damages[i].Sum(d => d >= 0 ? d : 0)};
\r
470 if (ignoreFriendDamage && eflags[i] == 1)
\r
472 records[eflags[i]][hit.t].ApplyDamage(hit.d);
\r
476 public void InspectMapStart(dynamic json)
\r
478 InspectMapNext(json);
\r
481 public void InspectMapNext(dynamic json)
\r
483 _lastCell = (int)json.api_next == 0;
\r
486 public void InspectBattleResult(dynamic json)
\r
488 BattleState = BattleState.Result;
\r
489 ShowResult(!_lastCell);
\r
490 _shipInfo.SaveBattleResult();
\r
491 VerifyResultRank(json);
\r
493 SetEscapeShips(json);
\r
496 private void VerifyResultRank(dynamic json)
\r
498 if (_friend == null)
\r
500 if (!json.api_win_rank())
\r
502 var assumed = "PSABCDE"[(int)ResultRank];
\r
503 if (assumed == 'P')
\r
505 var actual = ((string)json.api_win_rank)[0];
\r
506 DisplayedResultRank.Assumed = assumed;
\r
507 DisplayedResultRank.Actual = actual;
\r
510 public void InspectPracticeResult(dynamic json)
\r
512 BattleState = BattleState.Result;
\r
514 VerifyResultRank(json);
\r
518 private void ShowResult(bool warnDamagedShip = true)
\r
520 if (_friend == null)
\r
522 var ships = _guard.Length > 0
\r
523 ? _shipInfo.GetShipStatuses(0).Concat(_shipInfo.GetShipStatuses(1)).ToArray()
\r
524 : _shipInfo.GetShipStatuses(_fleet);
\r
525 foreach (var entry in ships.Zip(_friend.Concat(_guard), (ship, now) => new {ship, now}))
\r
526 entry.now.UpdateShipStatus(entry.ship);
\r
527 if (warnDamagedShip)
\r
528 _shipInfo.SetBadlyDamagedShips();
\r
530 _shipInfo.ClearBadlyDamagedShips();
\r
533 public void SetEscapeShips(dynamic json)
\r
535 _escapingShips.Clear();
\r
536 if (!json.api_escape_flag() || (int)json.api_escape_flag == 0)
\r
538 var damaged = (int)json.api_escape.api_escape_idx[0] - 1;
\r
539 if (json.api_escape.api_tow_idx())
\r
541 _escapingShips.Add(_shipInfo.GetDeck(damaged / 6)[damaged % 6]);
\r
542 var escort = (int)json.api_escape.api_tow_idx[0] - 1;
\r
543 _escapingShips.Add(_shipInfo.GetDeck(escort / 6)[escort % 6]);
\r
547 _escapingShips.Add(_shipInfo.GetDeck(2)[damaged]);
\r
551 public void CauseEscape()
\r
553 _shipInfo.SetEscapedShips(_escapingShips);
\r
554 _shipInfo.SetBadlyDamagedShips();
\r
557 private class Record
\r
559 private ShipStatus _status;
\r
560 private bool _practice;
\r
561 public ShipStatus SnapShot => (ShipStatus)_status.Clone();
\r
562 public int NowHp => _status.NowHp;
\r
563 public bool Escaped => _status.Escaped;
\r
564 public ShipStatus.Damage DamageLevel => _status.DamageLevel;
\r
565 public string Name => _status.Name;
\r
566 public int StartHp { get; private set; }
\r
568 public static Record[] Setup(ShipStatus[] ships, bool practice) =>
\r
570 select new Record {_status = (ShipStatus)s.Clone(), _practice = practice, StartHp = s.NowHp}).ToArray();
\r
572 public static Record[] Setup(int[] nowhps, ShipSpec[] ships, ItemSpec[][] slots, bool practice)
\r
574 return Enumerable.Range(0, nowhps.Length).Select(i =>
\r
577 StartHp = nowhps[i],
\r
578 _status = new ShipStatus
\r
584 Slot = slots[i].Select(spec => new ItemStatus {Id = spec.Id, Spec = spec}).ToArray(),
\r
585 SlotEx = new ItemStatus(0)
\r
587 _practice = practice
\r
591 public void ApplyDamage(int damage)
\r
593 if (_status.NowHp > damage)
\r
595 _status.NowHp -= damage;
\r
601 foreach (var item in new[] {_status.SlotEx}.Concat(_status.Slot))
\r
603 if (item.Spec.Id == 42)
\r
605 _status.NowHp = (int)(_status.MaxHp * 0.2);
\r
606 ConsumeSlotItem(_status, 42);
\r
609 if (item.Spec.Id == 43)
\r
611 _status.NowHp = _status.MaxHp;
\r
612 ConsumeSlotItem(_status, 43);
\r
618 public void UpdateShipStatus(ShipStatus ship)
\r
620 ship.NowHp = NowHp;
\r
621 ship.Slot = _status.Slot;
\r
622 ship.SlotEx = _status.SlotEx;
\r
626 private BattleResultRank CalcLdAirBattleRank()
\r
628 var combined = _friend.Concat(_guard).ToArray();
\r
629 var friendNowShips = combined.Count(r => r.NowHp > 0);
\r
630 var friendGauge = combined.Sum(r => r.StartHp - r.NowHp);
\r
631 var friendSunk = combined.Count(r => r.NowHp == 0);
\r
632 var friendGaugeRate = Floor((double)friendGauge / combined.Sum(r => r.StartHp) * 100);
\r
634 if (friendSunk == 0)
\r
636 if (friendGauge == 0)
\r
637 return BattleResultRank.P;
\r
638 if (friendGaugeRate < 10)
\r
639 return BattleResultRank.A;
\r
640 if (friendGaugeRate < 20)
\r
641 return BattleResultRank.B;
\r
642 if (friendGaugeRate < 50)
\r
643 return BattleResultRank.C;
\r
644 return BattleResultRank.D;
\r
646 if (friendSunk < friendNowShips)
\r
647 return BattleResultRank.D;
\r
648 return BattleResultRank.E;
\r
651 private BattleResultRank CalcResultRank()
\r
653 var friend = _friend.Concat(_guard).ToArray();
\r
654 var enemy = _enemy.Concat(_enemyGuard).ToArray();
\r
656 var friendCount = friend.Length;
\r
657 var friendStartHpTotal = 0;
\r
658 var friendNowHpTotal = 0;
\r
659 var friendSunk = 0;
\r
660 foreach (var ship in friend)
\r
664 friendStartHpTotal += ship.StartHp;
\r
665 friendNowHpTotal += ship.NowHp;
\r
666 if (ship.NowHp == 0)
\r
669 var friendGaugeRate = (int)((double)(friendStartHpTotal - friendNowHpTotal) / friendStartHpTotal * 100);
\r
671 var enemyCount = enemy.Length;
\r
672 var enemyStartHpTotal = enemy.Sum(r => r.StartHp);
\r
673 var enemyNowHpTotal = enemy.Sum(r => r.NowHp);
\r
674 var enemySunk = enemy.Count(r => r.NowHp == 0);
\r
675 var enemyGaugeRate = (int)((double)(enemyStartHpTotal - enemyNowHpTotal) / enemyStartHpTotal * 100);
\r
677 if (friendSunk == 0 && enemySunk == enemyCount)
\r
679 if (friendNowHpTotal >= friendStartHpTotal)
\r
680 return BattleResultRank.P;
\r
681 return BattleResultRank.S;
\r
683 if (friendSunk == 0 && enemySunk >= (int)(enemyCount * 0.7) && enemyCount > 1)
\r
684 return BattleResultRank.A;
\r
685 if (friendSunk < enemySunk && enemy[0].NowHp == 0)
\r
686 return BattleResultRank.B;
\r
687 if (friendCount == 1 && friend[0].DamageLevel == ShipStatus.Damage.Badly)
\r
688 return BattleResultRank.D;
\r
689 if (enemyGaugeRate > friendGaugeRate * 2.5)
\r
690 return BattleResultRank.B;
\r
691 if (enemyGaugeRate > friendGaugeRate * 0.9)
\r
692 return BattleResultRank.C;
\r
693 if (friendCount > 1 && friendCount - 1 == friendSunk)
\r
694 return BattleResultRank.E;
\r
695 return BattleResultRank.D;
\r
701 public void InjectResultStatus(ShipStatus[] main, ShipStatus[] guard, ShipStatus[] enemy, ShipStatus[] enemyGuard)
\r
703 Result = new BattleResult
\r
705 Friend = new BattleResult.Combined { Main = main, Guard = guard},
\r
706 Enemy = new BattleResult.Combined {Main = enemy, Guard = enemyGuard}
\r