protected FightParticipant AddParticipant(string name) { if (Target.Name == name) { return(Target); } FightParticipant p = null; for (int i = 0; i < Participants.Count; i++) { if (Participants[i].Name == name) { p = Participants[i]; break; } } if (p == null) { p = new FightParticipant(); p.Name = name; Participants.Add(p); } return(p); }
public FightInfo(string name) { ID = Guid.NewGuid().ToString(); Name = name; Target = new FightParticipant() { Name = name }; }
/// <summary> /// Merges damage, heals and spells from a pet into this participant. /// Tanking is not merged. /// All data will be prefixed with "pet:" to distinguish it from the owner. e.g. "pet:slash" vs "slash" /// </summary> public void MergePet(FightParticipant pet) { foreach (var at in pet.AttackTypes) { at.Type = "pet:" + at.Type; //owner.AttackTypes.Add(hit); var match = AttackTypes.FirstOrDefault(x => x.Type == at.Type); if (match != null) { match.Merge(at); } else { AttackTypes.Add(at); } } foreach (var spell in pet.Spells.Where(x => x.Type == "hit")) { spell.Name = "pet:" + spell.Name; var match = Spells.FirstOrDefault(x => x.Type == spell.Type && x.Name == spell.Name); if (match != null) { match.Merge(spell); } else { Spells.Add(spell); } } OutboundHitCount += pet.OutboundHitCount; OutboundHitSum += pet.OutboundHitSum; OutboundMissCount += pet.OutboundMissCount; // merges DPS and HPS intervals (but not TankDPS or InboundHPS) for (var i = 0; i < pet.DPS.Count; i++) { while (DPS.Count <= i) { DPS.Add(0); } DPS[i] += pet.DPS[i]; } for (var i = 0; i < pet.HPS.Count; i++) { while (HPS.Count <= i) { HPS.Add(0); } HPS[i] += pet.HPS[i]; } // removing the pet has the downside of hiding pet tanking //Participants.Remove(pet); // clear the damage on the pet but keep it for tanking stats pet.OutboundHitCount = 0; pet.OutboundHitSum = 0; pet.OutboundMissCount = 0; pet.AttackTypes.Clear(); //pet.Spells.Clear(); pet.Spells.RemoveAll(x => x.Type == "hit"); // leave heals on pet so it shows on the healer list pet.DPS.Clear(); pet.HPS.Clear(); }
/// <summary> /// Merge data from another participant into this participant. /// </summary> public void Merge(FightParticipant p, int intervalOffset = 0, int timeOffset = 0) { OutboundMissCount += p.OutboundMissCount; OutboundHitCount += p.OutboundHitCount; OutboundHitSum += p.OutboundHitSum; OutboundStrikeCount += p.OutboundStrikeCount; InboundMissCount += p.InboundMissCount; InboundHitCount += p.InboundHitCount; InboundHitSum += p.InboundHitSum; InboundMeleeCount += p.InboundMeleeCount; InboundMeleeSum += p.InboundMeleeSum; InboundRiposteSum += p.InboundRiposteSum; //InboundSpellCount += p.InboundSpellCount; //InboundSpellSum += p.InboundSpellSum; InboundStrikeCount += p.InboundStrikeCount; OutboundHealSum += p.OutboundHealSum; InboundHealSum += p.InboundHealSum; InboundFullHealSum += p.InboundFullHealSum; DeathCount += p.DeathCount; // merge intervals starting at 'intervalOffset' base for (var i = 0; i < p.DPS.Count; i++) { while (DPS.Count <= intervalOffset + i) { DPS.Add(0); } DPS[intervalOffset + i] += p.DPS[i]; } for (var i = 0; i < p.TankDPS.Count; i++) { while (TankDPS.Count <= intervalOffset + i) { TankDPS.Add(0); } TankDPS[intervalOffset + i] += p.TankDPS[i]; } for (var i = 0; i < p.HPS.Count; i++) { while (HPS.Count <= intervalOffset + i) { HPS.Add(0); } HPS[intervalOffset + i] += p.HPS[i]; } for (var i = 0; i < p.InboundHPS.Count; i++) { while (InboundHPS.Count <= intervalOffset + i) { InboundHPS.Add(0); } InboundHPS[intervalOffset + i] += p.InboundHPS[i]; } foreach (var at in p.AttackTypes) { var _at = AttackTypes.FirstOrDefault(x => x.Type == at.Type); if (_at == null) { _at = new FightHit(); _at.Type = at.Type; AttackTypes.Add(_at); } _at.Merge(at); } foreach (var dt in p.DefenseTypes) { var _dt = DefenseTypes.FirstOrDefault(x => x.Type == dt.Type); if (_dt == null) { _dt = new FightMiss(); _dt.Type = dt.Type; DefenseTypes.Add(_dt); } _dt.Merge(dt); } foreach (var h in p.Heals) { var _h = Heals.FirstOrDefault(x => x.Target == h.Target); if (_h == null) { _h = new FightHeal(); _h.Target = h.Target; Heals.Add(_h); } _h.Merge(h); } foreach (var s in p.Spells) { var _s = Spells.FirstOrDefault(x => x.Name == s.Name && x.Type == s.Type); if (_s == null) { _s = new FightSpell(); _s.Type = s.Type; _s.Name = s.Name; //_s.Times = // todo Spells.Add(_s); } _s.Merge(s); } // disabled - merging buffs will create duplicates if fights overlap and include the same buff // it would be better to recreate buffs after merging p.Buffs.Clear(); // >= 0 avoids any pre fight buffs //foreach (var b in p.Buffs.Where(x => x.Time >= 0)) //{ // if (timeOffset == 0) // Buffs.Add(b); // else // Buffs.Add(new FightBuff { Name = b.Name, Time = b.Time + timeOffset }); //} }
/// <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];
/// <summary> /// Trim the long tail of participants that fall under a low threshold of activity to make the data structure smaller. /// These players will be consolidated into an "*Other" entry. /// This should be done after all other merging -- possibly just on the server side. /// </summary> public void TrimParticipants() { var other = new FightParticipant(); other.FirstAction = StartedOn; other.LastAction = UpdatedOn; other.Name = "*Other"; // damage: anyone that does less than 5% of top damage dealer var min = Participants.Max(x => x.OutboundHitSum) * 0.05; foreach (var p in Participants.Where(x => x.OutboundHitSum < min)) { // participant names will be anonymized at this point //other.AttackTypes.Add(new FightHit { Type = p.Name, HitCount = p.OutboundHitCount, HitSum = (int)p.OutboundHitSum }); other.OutboundHitSum += p.OutboundHitSum; p.OutboundHitSum = 0; other.OutboundHitCount += p.OutboundHitCount; p.OutboundHitCount = 0; p.DPS.Clear(); p.AttackTypes.Clear(); p.Spells.RemoveAll(x => x.Type == "hit"); } // tanking: anyone that does less than 10% of the top tank // this just ends up being filled with swarm pets and rampage targets min = Participants.Max(x => x.InboundMeleeSum) * 0.1; foreach (var p in Participants.Where(x => x.InboundMeleeSum < min)) { other.InboundMeleeSum += p.InboundMeleeSum; p.InboundMeleeSum = 0; other.InboundMeleeCount += p.InboundMeleeCount; other.InboundMissCount += p.InboundMissCount; p.InboundMeleeCount = 0; p.InboundMissCount = 0; p.TankDPS.Clear(); p.InboundHPS.Clear(); p.DefenseTypes.Clear(); } // healing: anyone that does less than 10% of top healer min = Participants.Max(x => x.OutboundHealSum) * 0.1; foreach (var p in Participants.Where(x => x.OutboundHealSum < min)) { other.OutboundHealSum += p.OutboundHealSum; p.OutboundHealSum = 0; p.HPS.Clear(); foreach (var h in p.Heals) { var ot = other.Heals.Find(x => x.Target == h.Target); if (ot == null) { ot = new FightHeal() { Target = h.Target }; other.Heals.Add(h); } ot.HitSum += h.HitSum; ot.HitCount += h.HitCount; } p.Heals.Clear(); p.Spells.RemoveAll(x => x.Type == "heal"); } if (other.OutboundHitSum > 0 || other.OutboundHealSum > 0 || other.InboundMeleeSum > 0) { Participants.Add(other); } Participants.RemoveAll(x => x.OutboundHealSum == 0 && x.OutboundHitSum == 0 && x.InboundHitSum == 0 && !x.DPS.Any(y => y > 0)); }