public override void Merge(FightInfo f) { // add to internal list and delay actual merge until the Finish is called fights.Add(f); // raid needs a timestamp to keep from getting timed out if (StartedOn == DateTime.MinValue || StartedOn > f.StartedOn) { StartedOn = f.StartedOn; } }
private void TrackHit(LogHitEvent hit) { var foe = Chars.GetFoe(hit.Source, hit.Target); if (foe == null) { //Console.WriteLine("*** " + hit); if (hit.Source != hit.Target) { PendingHits.Add(hit); } return; } if (hit.Source.EndsWith("'s corpse")) { if (hit.Target == foe) { // rename source to track damage from dead player hit = new LogHitEvent { Timestamp = hit.Timestamp, Type = hit.Type, Source = hit.Source.Substring(0, hit.Source.Length - 9), Target = hit.Target, Amount = hit.Amount, Mod = hit.Mod, Spell = hit.Spell }; } else { // do not track damage from a dead mob return; } } var f = GetOrAddFight(foe); if (f == null) { return; } f.AddHit(hit); //LastHit = hit; LastFight = f; }
private void TrackMiss(LogMissEvent miss) { var foe = Chars.GetFoe(miss.Source, miss.Target); if (foe == null) { return; } var f = GetOrAddFight(foe); if (f == null) { return; } f.AddMiss(miss); LastFight = f; }
/// <summary> /// Output a short summary of each players damage to a TextWriter. /// </summary> public static void WriteShortSummary(this TextWriter writer, FightInfo fight) { writer.WriteLine(); writer.WriteLine("** {0} ** {1} HP in {2}s at {3}", fight.Name, FormatNum(fight.Target.InboundHitSum), fight.Duration, fight.StartedOn.ToLocalTime().ToShortTimeString(), fight.Zone); if (fight.Participants.Count == 0) { return; } var top = fight.Participants.Max(x => x.OutboundHitSum); foreach (var p in fight.Participants.Take(10).Where(x => x.OutboundHitSum > 0)) { // percent = share of damage vs mob //var pct = (float)p.OutboundHitSum / Target.InboundHitSum; // percent = damage relative to top player var pct = (float)p.OutboundHitSum / top; writer.WriteLine(" {0,-20} --- {1,4:P0} {2,8} / {3,5} DPS", p, pct, FormatNum(p.OutboundHitSum), FormatNum(p.OutboundHitSum / fight.Duration)); } }
/// <summary> /// Output a detailed summary of each players damage, tanking, and spells to a TextWriter. /// </summary> public static void WriteLongSummary(this TextWriter writer, FightInfo fight) { writer.WriteLine(); writer.WriteLine("**{0}** {1:N0} HP in {2}s at {3}", fight.Name, fight.Target.InboundHitSum, fight.Duration, fight.StartedOn.ToLocalTime(), fight.Zone); if (fight.Participants.Count == 0) { return; } var top = fight.Participants.Max(x => x.OutboundHitSum); foreach (var p in fight.Participants) { var pct = (float)p.OutboundHitSum / top; writer.WriteLine(" {0} {1:P0} {2} to {3}", p, pct, p.FirstAction?.ToLocalTime().ToString("T"), p.LastAction?.ToLocalTime().ToString("T")); writer.WriteLine(" {0,-10} {1,11:N0} / {2,6:N0} DPS", "total", p.OutboundHitSum, p.OutboundHitSum / fight.Duration); foreach (var ht in p.AttackTypes) { writer.WriteLine(" {0,-10} {1,11:N0} / {2,6:N0} DPS", ht.Type, ht.HitSum, ht.HitSum / fight.Duration); } foreach (var d in p.DefenseTypes) { writer.WriteLine(" {0,-10} {1,6:N0} of {2} {3:P0}", "*" + d.Type + "*", d.Count, d.Attempts, (double)d.Count / d.Attempts); } writer.WriteLine(" {0,-10} {1,6:N0} of {2} {3:P0}", "*hit*", p.InboundHitCount, p.InboundHitCount + p.InboundMissCount, (double)p.InboundHitCount / (p.InboundHitCount + p.InboundMissCount)); foreach (var s in p.Spells) { writer.WriteLine(" spell {0} {1}: {2:N0}", s.Type, s.Name, s.HitSum); } foreach (var h in p.Heals) { writer.WriteLine(" healed {0}: {1:N0}", h.Target, h.HitSum); } } }
/// <summary> /// Merge data from another fight into this fight. /// The supplied fight must have started after any previously merged fights. /// </summary> public virtual void Merge(FightInfo f) { if (Target == null || String.IsNullOrEmpty(Target.Name)) { Target = new FightParticipant() { Name = "X" }; Name = "X"; Zone = f.Zone; Party = f.Party; Player = f.Player; Server = f.Server; StartedOn = f.StartedOn; UpdatedOn = f.UpdatedOn; Status = FightStatus.Merged; Duration = 1; } MobCount += 1; fights.Add(f); if (Zone != f.Zone) { Zone = "Multiple Zones"; } if (Party == "Group" && f.Party == "Raid") { Party = "Raid"; } //Target.Merge(f.Target, 0); if (StartedOn > f.StartedOn && intervals.Count == 1) { StartedOn = f.StartedOn; } else if (StartedOn > f.StartedOn) { throw new Exception("Fights must be merged in order of start time."); } if (UpdatedOn < f.UpdatedOn) { UpdatedOn = f.UpdatedOn; } /* * for single fights each tick has a 1 to 1 mapping to intervals. * however when merging multiple fights together we have to deal with * ticks that may overlap intervals (which we need to merge) and * gaps between fights (which we want to remove). * * here are some scenarios that will occur: * * 1111 * 12345678 90123 -- intervals we want to assign * --------------- -- the tick timeline * 1111 -- first fight * 22222 -- 2nd overlaps with 1st * 33333 -- 3rd has gap after 2nd * 44 -- 4th ends before 3rd * * fights must be sorted in starting order for this algorithm to work */ // starting interval var interval = 0; // get tick relative to first tick of first fight var head = GetTick(f.StartedOn); var tail = GetTick(f.UpdatedOn); Debug.Assert(tail >= head); // has this tick already been seen? // if so backtrack so we can find where it belongs if (head <= intervals[^ 1]) { interval = intervals.Count - 1; while (intervals[interval] > head) { interval--; } } // if it hasn't been seen then add it as a new interval else { intervals.Add(head); interval = intervals.Count - 1; } // handle the tail var current = intervals[^ 1];