internal override void CheckSuccess(CombatData combatData, AgentData agentData, FightData fightData, IReadOnlyCollection <AgentItem> playerAgents) { AbstractSingleActor mainTarget = Targets.FirstOrDefault(x => x.ID == GenericTriggerID); if (mainTarget == null) { throw new MissingKeyActorsException("Golem not found"); } long fightEndLogTime = fightData.FightEnd; bool success = false; DeadEvent deadEvt = combatData.GetDeadEvents(mainTarget.AgentItem).LastOrDefault(); if (deadEvt != null) { fightEndLogTime = deadEvt.Time; success = true; } else { IReadOnlyList <HealthUpdateEvent> hpUpdates = combatData.GetHealthUpdateEvents(mainTarget.AgentItem); if (hpUpdates.Count > 0) { AbstractHealthDamageEvent lastDamageTaken = combatData.GetDamageTakenData(mainTarget.AgentItem).LastOrDefault(x => x.HealthDamage > 0); success = hpUpdates.Last().HPPercent < 2.00; if (success && lastDamageTaken != null) { fightEndLogTime = lastDamageTaken.Time; } } } fightData.SetSuccess(success, fightEndLogTime); }
internal override void CheckSuccess(CombatData combatData, AgentData agentData, FightData fightData, IReadOnlyCollection <AgentItem> playerAgents) { base.CheckSuccess(combatData, agentData, fightData, playerAgents); // reward or death worked if (fightData.Success) { return; } AbstractSingleActor skorvald = Targets.FirstOrDefault(x => x.ID == (int)ArcDPSEnums.TargetID.Skorvald); if (skorvald == null) { throw new MissingKeyActorsException("Skorvald not found"); } AbstractHealthDamageEvent lastDamageTaken = combatData.GetDamageTakenData(skorvald.AgentItem).LastOrDefault(x => (x.HealthDamage > 0) && playerAgents.Contains(x.From.GetFinalMaster())); if (lastDamageTaken != null) { BuffApplyEvent invul895Apply = combatData.GetBuffData(895).OfType <BuffApplyEvent>().Where(x => x.To == skorvald.AgentItem && x.Time > lastDamageTaken.Time - 500).LastOrDefault(); if (invul895Apply != null) { fightData.SetSuccess(true, Math.Min(invul895Apply.Time, lastDamageTaken.Time)); } } }
protected void SetSuccessByDeath(CombatData combatData, FightData fightData, HashSet <AgentItem> playerAgents, bool all, List <int> idsToUse) { int success = 0; long maxTime = long.MinValue; foreach (int id in idsToUse) { NPC target = Targets.FirstOrDefault(x => x.ID == id); if (target == null) { return; } DeadEvent killed = combatData.GetDeadEvents(target.AgentItem).LastOrDefault(); if (killed != null) { long time = killed.Time; success++; AbstractHealthDamageEvent lastDamageTaken = combatData.GetDamageTakenData(target.AgentItem).LastOrDefault(x => (x.HealthDamage > 0) && playerAgents.Contains(x.From.GetFinalMaster())); if (lastDamageTaken != null) { time = Math.Min(lastDamageTaken.Time, time); } maxTime = Math.Max(time, maxTime); } } if ((all && success == idsToUse.Count) || (!all && success > 0)) { fightData.SetSuccess(true, maxTime); } }
internal override List <PhaseData> GetPhases(ParsedEvtcLog log, bool requirePhases) { long fightDuration = log.FightData.FightEnd; List <PhaseData> phases = GetInitialPhase(log); NPC mainTarget = Targets.FirstOrDefault(x => x.ID == (int)ArcDPSEnums.TargetID.Matthias); if (mainTarget == null) { throw new MissingKeyActorsException("Matthias not found"); } phases[0].AddTarget(mainTarget); if (!requirePhases) { return(phases); } // Special buff cast check AbstractBuffEvent heatWave = log.CombatData.GetBuffData(34526).FirstOrDefault(); if (heatWave != null) { phases.Add(new PhaseData(0, heatWave.Time - 1)); AbstractHealthDamageEvent downPour = log.CombatData.GetDamageData(mainTarget.AgentItem).FirstOrDefault(x => x.SkillId == 34554); if (downPour != null) { phases.Add(new PhaseData(heatWave.Time, downPour.Time - 1)); IReadOnlyList <AbstractCastEvent> castLogs = mainTarget.GetCastEvents(log, 0, log.FightData.FightEnd); AbstractCastEvent abo = castLogs.FirstOrDefault(x => x.SkillId == 34427); if (abo != null) { phases.Add(new PhaseData(downPour.Time, abo.Time - 1)); AbstractBuffEvent invulRemove = log.CombatData.GetBuffData(mainTarget.AgentItem).FirstOrDefault(x => x.Time >= abo.Time && x.Time <= abo.Time + 10000 && x.BuffID == 757 && !(x is BuffApplyEvent)); if (invulRemove != null) { phases.Add(new PhaseData(invulRemove.Time, fightDuration)); } } else { phases.Add(new PhaseData(downPour.Time, fightDuration)); } } else { phases.Add(new PhaseData(heatWave.Time, fightDuration)); } } else { phases.Add(new PhaseData(0, fightDuration)); } string[] namesMat = new[] { "Ice Phase", "Fire Phase", "Storm Phase", "Abomination Phase" }; for (int i = 1; i < phases.Count; i++) { phases[i].Name = namesMat[i - 1]; phases[i].DrawStart = i > 1; phases[i].AddTarget(mainTarget); } return(phases); }
protected override bool Keep(AbstractHealthDamageEvent c, ParsedEvtcLog log) { if (!c.HasHit) { return(false); } return(base.Keep(c, log)); }
protected virtual bool Keep(AbstractHealthDamageEvent c, ParsedEvtcLog log) { if (_triggerCondition != null) { return(_triggerCondition(c, log)); } return(true); }
protected override bool Keep(AbstractHealthDamageEvent c, ParsedEvtcLog log) { if (c.From == ParserHelper._unknownAgent || !base.Keep(c, log) || GetFirstHit(c.From, log) != c) { return(false); } return(true); }
protected double ComputeGain(int stack, AbstractHealthDamageEvent dl, ParsedEvtcLog log) { if (DLChecker != null && !DLChecker(dl, log)) { return(-1.0); } double gain = GainComputer.ComputeGain(GainPerStack, stack); return(gain > 0.0 ? gain * dl.HealthDamage : -1.0); }
private AbstractHealthDamageEvent GetFirstHit(AgentItem src, ParsedEvtcLog log) { if (!_firstHits.TryGetValue(src, out AbstractHealthDamageEvent evt)) { AbstractHealthDamageEvent res = log.CombatData.GetDamageData(src).Where(x => MechanicIDs.Contains(x.SkillId) && x.To.Type == AgentItem.AgentType.Player && base.Keep(x, log)).FirstOrDefault(); _firstHits[src] = res; return(res); } return(evt); }
protected double ComputeGainPlayer(int stack, AbstractHealthDamageEvent dl, ParsedEvtcLog log) { if (DLChecker != null && !DLChecker(dl, log)) { return(-1.0); } double gain = _gainComputerPlayer.ComputeGain(1.0, stack); return(gain > 0.0 ? 1.0 : -1.0); }
protected double ComputeGain(int stack, AbstractHealthDamageEvent dl, ParsedEvtcLog log) { if (DLChecker != null && !DLChecker(dl, log)) { return(-1.0); } // When gain per stack is 0, we only count hits done under the buff or in its absence double gain = GainComputer.ComputeGain(GainPerStack == 0.0 ? 1.0 : GainPerStack, stack); return(gain > 0.0 ? (GainPerStack == 0.0 ? 0.0 : gain *dl.HealthDamage) : -1.0); }
internal override void CheckSuccess(CombatData combatData, AgentData agentData, FightData fightData, IReadOnlyCollection <AgentItem> playerAgents) { base.CheckSuccess(combatData, agentData, fightData, playerAgents); if (!fightData.Success) { AbstractSingleActor target = Targets.FirstOrDefault(x => x.ID == (int)ArcDPSEnums.TargetID.ConjuredAmalgamate); AbstractSingleActor leftArm = Targets.FirstOrDefault(x => x.ID == (int)ArcDPSEnums.TargetID.CALeftArm); AbstractSingleActor rightArm = Targets.FirstOrDefault(x => x.ID == (int)ArcDPSEnums.TargetID.CARightArm); if (target == null) { throw new MissingKeyActorsException("Conjured Amalgamate not found"); } AgentItem zommoros = agentData.GetNPCsByID(21118).LastOrDefault(); if (zommoros == null) { return; } SpawnEvent npcSpawn = combatData.GetSpawnEvents(zommoros).LastOrDefault(); AbstractHealthDamageEvent lastDamageTaken = combatData.GetDamageTakenData(target.AgentItem).LastOrDefault(x => (x.HealthDamage > 0) && playerAgents.Contains(x.From.GetFinalMaster())); if (lastDamageTaken == null) { return; } if (rightArm != null) { AbstractHealthDamageEvent lastDamageTakenArm = combatData.GetDamageTakenData(rightArm.AgentItem).LastOrDefault(x => (x.HealthDamage > 0) && playerAgents.Contains(x.From.GetFinalMaster())); if (lastDamageTakenArm != null) { lastDamageTaken = lastDamageTaken.Time > lastDamageTakenArm.Time ? lastDamageTaken : lastDamageTakenArm; } } if (leftArm != null) { AbstractHealthDamageEvent lastDamageTakenArm = combatData.GetDamageTakenData(leftArm.AgentItem).LastOrDefault(x => (x.HealthDamage > 0) && playerAgents.Contains(x.From.GetFinalMaster())); if (lastDamageTakenArm != null) { lastDamageTaken = lastDamageTaken.Time > lastDamageTakenArm.Time ? lastDamageTaken : lastDamageTakenArm; } } if (npcSpawn != null) { fightData.SetSuccess(true, lastDamageTaken.Time); } } }
protected static void SetSuccessByCombatExit(List <AbstractSingleActor> targets, CombatData combatData, FightData fightData, IReadOnlyCollection <AgentItem> playerAgents) { if (targets.Count == 0) { return; } var targetExits = new List <ExitCombatEvent>(); var lastTargetDamages = new List <AbstractHealthDamageEvent>(); foreach (AbstractSingleActor t in targets) { EnterCombatEvent enterCombat = combatData.GetEnterCombatEvents(t.AgentItem).LastOrDefault(); ExitCombatEvent exitCombat; if (enterCombat != null) { exitCombat = combatData.GetExitCombatEvents(t.AgentItem).Where(x => x.Time > enterCombat.Time).LastOrDefault(); } else { exitCombat = combatData.GetExitCombatEvents(t.AgentItem).LastOrDefault(); } AbstractHealthDamageEvent lastDamage = combatData.GetDamageTakenData(t.AgentItem).LastOrDefault(x => (x.HealthDamage > 0) && playerAgents.Contains(x.From.GetFinalMaster())); if (exitCombat == null || lastDamage == null || combatData.GetAnimatedCastData(t.AgentItem).Any(x => x.Time > exitCombat.Time + ParserHelper.ServerDelayConstant) || combatData.GetDamageData(t.AgentItem).Any(x => x.Time > exitCombat.Time + ParserHelper.ServerDelayConstant && playerAgents.Contains(x.To))) { return; } targetExits.Add(exitCombat); lastTargetDamages.Add(lastDamage); } ExitCombatEvent lastTargetExit = targetExits.Count > 0 ? targetExits.MaxBy(x => x.Time) : null; AbstractHealthDamageEvent lastDamageTaken = lastTargetDamages.Count > 0 ? lastTargetDamages.MaxBy(x => x.Time) : null; // Make sure the last damage has been done before last combat exit if (lastTargetExit != null && lastDamageTaken != null && lastTargetExit.Time + 150 >= lastDamageTaken.Time) { if (!AtLeastOnePlayerAlive(combatData, fightData, lastTargetExit.Time, playerAgents)) { return; } fightData.SetSuccess(true, lastDamageTaken.Time); } }
protected static void SetSuccessByCombatExit(List <NPC> targets, CombatData combatData, FightData fightData, HashSet <AgentItem> playerAgents) { if (targets.Count == 0) { return; } var targetExits = new List <ExitCombatEvent>(); var lastTargetDamages = new List <AbstractHealthDamageEvent>(); foreach (NPC t in targets) { EnterCombatEvent enterCombat = combatData.GetEnterCombatEvents(t.AgentItem).LastOrDefault(); if (enterCombat != null) { targetExits.AddRange(combatData.GetExitCombatEvents(t.AgentItem).Where(x => x.Time > enterCombat.Time)); } else { targetExits.AddRange(combatData.GetExitCombatEvents(t.AgentItem)); } AbstractHealthDamageEvent lastDamage = combatData.GetDamageTakenData(t.AgentItem).LastOrDefault(x => (x.HealthDamage > 0) && playerAgents.Contains(x.From.GetFinalMaster())); if (lastDamage != null) { lastTargetDamages.Add(lastDamage); } } ExitCombatEvent lastTargetExit = targetExits.Count > 0 ? targetExits.MaxBy(x => x.Time) : null; AbstractHealthDamageEvent lastDamageTaken = lastTargetDamages.Count > 0 ? lastTargetDamages.MaxBy(x => x.Time) : null; // Make sure the last damage has been done before last combat exit if (lastTargetExit != null && lastDamageTaken != null && lastTargetExit.Time + 100 >= lastDamageTaken.Time) { if (!AtLeastOnePlayerAlive(combatData, fightData, lastTargetExit.Time, playerAgents)) { return; } fightData.SetSuccess(true, lastDamageTaken.Time); } }
internal override void CheckSuccess(CombatData combatData, AgentData agentData, FightData fightData, IReadOnlyCollection <AgentItem> playerAgents) { // no bouny chest detection, the reward is delayed AbstractSingleActor soowon = Targets.FirstOrDefault(x => x.ID == (int)ArcDPSEnums.TargetID.TheDragonVoidSooWon); if (soowon != null) { AttackTargetEvent attackTargetEvent = combatData.GetAttackTargetEvents(soowon.AgentItem).FirstOrDefault(); var targetables = combatData.GetTargetableEvents(attackTargetEvent.AttackTarget).Where(x => x.Time >= soowon.FirstAware).ToList(); var targetOffs = targetables.Where(x => !x.Targetable).ToList(); if (targetOffs.Count == 2) { AbstractHealthDamageEvent lastDamageTaken = combatData.GetDamageTakenData(soowon.AgentItem).LastOrDefault(x => (x.HealthDamage > 0) && playerAgents.Contains(x.From.GetFinalMaster())); if (lastDamageTaken != null) { if (!AtLeastOnePlayerAlive(combatData, fightData, Math.Min(targetOffs[1].Time + 100, fightData.FightEnd), playerAgents)) { return; } fightData.SetSuccess(true, targetOffs[1].Time); } } } }
internal override void CheckSuccess(CombatData combatData, AgentData agentData, FightData fightData, HashSet <AgentItem> playerAgents) { NPC mainTarget = Targets.FirstOrDefault(x => x.ID == GenericTriggerID); if (mainTarget == null) { throw new MissingKeyActorsException("Golem not found"); } AbstractHealthDamageEvent lastDamageTaken = combatData.GetDamageTakenData(mainTarget.AgentItem).LastOrDefault(x => x.HealthDamage > 0); long fightEndLogTime = fightData.FightEnd; bool success = false; if (lastDamageTaken != null) { fightEndLogTime = lastDamageTaken.Time; } IReadOnlyList <HealthUpdateEvent> hpUpdates = combatData.GetHealthUpdateEvents(mainTarget.AgentItem); if (hpUpdates.Count > 0) { success = hpUpdates.Last().HPPercent < 2.00; } fightData.SetSuccess(success, fightEndLogTime); }
internal override void CheckSuccess(CombatData combatData, AgentData agentData, FightData fightData, IReadOnlyCollection <AgentItem> playerAgents) { // check reward NPC mainTarget = Targets.FirstOrDefault(x => x.ID == GenericTriggerID); if (mainTarget == null) { throw new MissingKeyActorsException("Main target of the fight not found"); } RewardEvent reward = combatData.GetRewardEvents().LastOrDefault(); AbstractHealthDamageEvent lastDamageTaken = combatData.GetDamageTakenData(mainTarget.AgentItem).LastOrDefault(x => (x.HealthDamage > 0) && playerAgents.Contains(x.From.GetFinalMaster())); if (lastDamageTaken != null) { if (reward != null && Math.Abs(lastDamageTaken.Time - reward.Time) < 100) { fightData.SetSuccess(true, Math.Min(lastDamageTaken.Time, reward.Time)); } else { SetSuccessByDeath(combatData, fightData, playerAgents, true, GenericTriggerID); } } }
internal DeathRecap(ParsedEvtcLog log, IReadOnlyList <AbstractHealthDamageEvent> damageLogs, DeadEvent dead, IReadOnlyList <DownEvent> downs, IReadOnlyList <AliveEvent> ups, long lastDeathTime) { DeathTime = dead.Time; DownEvent downed; AliveEvent upped = ups.LastOrDefault(x => x.Time <= dead.Time && x.Time >= lastDeathTime); if (upped != null) { downed = downs.LastOrDefault(x => x.Time <= dead.Time && x.Time >= upped.Time); } else { downed = downs.LastOrDefault(x => x.Time <= dead.Time && x.Time >= lastDeathTime); } if (downed != null) { var damageToDown = damageLogs.Where(x => x.Time > lastDeathTime && x.Time <= downed.Time && (x.HasHit || x.HasDowned)).ToList(); ToDown = damageToDown.Count > 0 ? new List <DeathRecapDamageItem>() : null; int damage = 0; for (int i = damageToDown.Count - 1; i >= 0; i--) { AbstractHealthDamageEvent dl = damageToDown[i]; AgentItem ag = dl.From; var item = new DeathRecapDamageItem() { Time = (int)dl.Time, IndirectDamage = dl is NonDirectHealthDamageEvent, ID = dl.SkillId, Damage = dl.HealthDamage, Src = log.FindActor(ag)?.Character }; damage += dl.HealthDamage; ToDown.Add(item); if (damage > 20000) { break; } } var damageToKill = damageLogs.Where(x => x.Time > downed.Time && x.Time <= dead.Time && (x.HasHit || x.HasKilled)).ToList(); ToKill = damageToKill.Count > 0 ? new List <DeathRecapDamageItem>() : null; for (int i = damageToKill.Count - 1; i >= 0; i--) { AbstractHealthDamageEvent dl = damageToKill[i]; AgentItem ag = dl.From; var item = new DeathRecapDamageItem() { Time = (int)dl.Time, IndirectDamage = dl is NonDirectHealthDamageEvent, ID = dl.SkillId, Damage = dl.HealthDamage, Src = log.FindActor(ag)?.Character }; ToKill.Add(item); } } else { ToDown = null; var damageToKill = damageLogs.Where(x => x.Time > lastDeathTime && x.Time <= dead.Time && (x.HasHit || x.HasKilled)).ToList(); ToKill = damageToKill.Count > 0 ? new List <DeathRecapDamageItem>() : null; int damage = 0; for (int i = damageToKill.Count - 1; i >= 0; i--) { AbstractHealthDamageEvent dl = damageToKill[i]; AgentItem ag = dl.From; var item = new DeathRecapDamageItem() { Time = (int)dl.Time, IndirectDamage = dl is NonDirectHealthDamageEvent, ID = dl.SkillId, Damage = dl.HealthDamage, Src = log.FindActor(ag)?.Character }; damage += dl.HealthDamage; ToKill.Add(item); if (damage > 20000) { break; } } } }
private static void FallBackPhases(AbstractSingleActor target, List <PhaseData> phases, ParsedEvtcLog log, bool firstPhaseAt0) { IReadOnlyCollection <AgentItem> pAgents = log.PlayerAgents; // clean Nikare related bugs switch (phases.Count) { case 2: { PhaseData p1 = phases[0]; PhaseData p2 = phases[1]; // P1 and P2 merged if (p1.Start == p2.Start) { AbstractHealthDamageEvent hit = log.CombatData.GetDamageTakenData(target.AgentItem).FirstOrDefault(x => x.Time >= p1.End + 5000 && pAgents.Contains(x.From.GetFinalMaster()) && x.HealthDamage > 0 && x is DirectHealthDamageEvent); if (hit != null) { p2.OverrideStart(hit.Time); } else { p2.OverrideStart(p1.End); } } } break; case 3: { PhaseData p1 = phases[0]; PhaseData p2 = phases[1]; PhaseData p3 = phases[2]; // P1 and P2 merged if (p1.Start == p2.Start) { AbstractHealthDamageEvent hit = log.CombatData.GetDamageTakenData(target.AgentItem).FirstOrDefault(x => x.Time >= p1.End + 5000 && pAgents.Contains(x.From.GetFinalMaster()) && x.HealthDamage > 0 && x is DirectHealthDamageEvent); if (hit != null) { p2.OverrideStart(hit.Time); } else { p2.OverrideStart(p1.End); } } // P1/P2 and P3 are merged if (p1.Start == p3.Start || p2.Start == p3.Start) { AbstractHealthDamageEvent hit = log.CombatData.GetDamageTakenData(target.AgentItem).FirstOrDefault(x => x.Time >= p2.End + 5000 && pAgents.Contains(x.From.GetFinalMaster()) && x.HealthDamage > 0 && x is DirectHealthDamageEvent); if (hit != null) { p3.OverrideStart(hit.Time); } else { p3.OverrideStart(p2.End); } } } break; default: break; } if (!firstPhaseAt0 && phases.Count > 0 && phases.First().Start == 0) { PhaseData p1 = phases[0]; AbstractHealthDamageEvent hit = log.CombatData.GetDamageTakenData(target.AgentItem).FirstOrDefault(x => x.Time >= 0 && pAgents.Contains(x.From.GetFinalMaster()) && x.HealthDamage > 0 && x is DirectHealthDamageEvent); if (hit != null) { p1.OverrideStart(hit.Time); } } }
internal DeathRecap(IReadOnlyList <AbstractHealthDamageEvent> damageLogs, DeadEvent dead, IReadOnlyList <DownEvent> downs, IReadOnlyList <AliveEvent> ups, long lastDeathTime) { DeathTime = dead.Time; DownEvent downed; AliveEvent upped = ups.LastOrDefault(x => x.Time <= dead.Time && x.Time >= lastDeathTime); if (upped != null) { downed = downs.LastOrDefault(x => x.Time <= dead.Time && x.Time >= upped.Time); } else { downed = downs.LastOrDefault(x => x.Time <= dead.Time && x.Time >= lastDeathTime); } if (downed != null) { var damageToDown = damageLogs.Where(x => x.Time > lastDeathTime && x.Time <= downed.Time && x.HasHit && x.HealthDamage > 0).ToList(); ToDown = damageToDown.Count > 0 ? new List <DeathRecapDamageItem>() : null; int damage = 0; for (int i = damageToDown.Count - 1; i >= 0; i--) { AbstractHealthDamageEvent dl = damageToDown[i]; AgentItem ag = dl.From; var item = new DeathRecapDamageItem() { Time = (int)dl.Time, IndirectDamage = dl is NonDirectHealthDamageEvent, ID = dl.SkillId, Damage = dl.HealthDamage, Src = ag != null?ag.Name.Replace("\u0000", "").Split(':')[0] : "" }; damage += dl.HealthDamage; ToDown.Add(item); if (damage > 20000) { break; } } var damageToKill = damageLogs.Where(x => x.Time > downed.Time && x.Time <= dead.Time && x.HasHit && x.HealthDamage > 0).ToList(); ToKill = damageToKill.Count > 0 ? new List <DeathRecapDamageItem>() : null; for (int i = damageToKill.Count - 1; i >= 0; i--) { AbstractHealthDamageEvent dl = damageToKill[i]; AgentItem ag = dl.From; var item = new DeathRecapDamageItem() { Time = (int)dl.Time, IndirectDamage = dl is NonDirectHealthDamageEvent, ID = dl.SkillId, Damage = dl.HealthDamage, Src = ag != null?ag.Name.Replace("\u0000", "").Split(':')[0] : "" }; ToKill.Add(item); } } else { ToDown = null; var damageToKill = damageLogs.Where(x => x.Time > lastDeathTime && x.Time <= dead.Time && x.HasHit && x.HealthDamage > 0).ToList(); ToKill = damageToKill.Count > 0 ? new List <DeathRecapDamageItem>() : null; int damage = 0; for (int i = damageToKill.Count - 1; i >= 0; i--) { AbstractHealthDamageEvent dl = damageToKill[i]; AgentItem ag = dl.From; var item = new DeathRecapDamageItem() { Time = (int)dl.Time, IndirectDamage = dl is NonDirectHealthDamageEvent, ID = dl.SkillId, Damage = dl.HealthDamage, Src = ag != null?ag.Name.Replace("\u0000", "").Split(':')[0] : "" }; damage += dl.HealthDamage; ToKill.Add(item); if (damage > 20000) { break; } } } }