Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 51 additions & 9 deletions libs/gw2_analytics/src/gw2_analytics/ei_compare.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,12 @@ def _rotation_unmatched(
return missing, remaining


def _defense_landed_hit(event: DamageEvent) -> bool:
return landed_hit(event) and not (
not event.is_condition and event.result == 10 and event.damage == 0 and event.buff_dmg == 0
)


def _damage_stats(
damage: list[DamageEvent],
crowd_control: list[CCEvent],
Expand Down Expand Up @@ -375,13 +381,16 @@ def down_rows_for_slice(lo: int, hi: int) -> dict[int, object]:
sliced_down_cache[(lo, hi)] = cached
return cached

agents_by_account = {
agent.account_name.lstrip(":"): agent for agent in fight.agents if agent.account_name
}
agents_by_account: dict[str, list[Agent]] = defaultdict(list)
for fight_agent in fight.agents:
if fight_agent.account_name:
agents_by_account[fight_agent.account_name.lstrip(":")].append(fight_agent)
agents_by_instance: dict[int, Agent] = {}
agents_by_instance_entries: dict[int, list[Agent]] = defaultdict(list)
for fight_agent in fight.agents:
if fight_agent.instance_id:
agents_by_instance.setdefault(fight_agent.instance_id, fight_agent)
agents_by_instance_entries[fight_agent.instance_id].append(fight_agent)
agent_ids_by_instance: dict[int, set[int]] = defaultdict(set)
for fight_agent in fight.agents:
if fight_agent.instance_id:
Expand Down Expand Up @@ -425,6 +434,42 @@ def indexed_events(index: dict[int, list[Any]], agent_ids: set[int]) -> list[Any
indexed_event for agent_id in agent_ids for indexed_event in index.get(agent_id, [])
]

def select_player_agent(player: dict[str, Any], account: str) -> Agent | None:
instance_id = player.get("instanceID")
if account.startswith("Non Squad Player") and isinstance(instance_id, int):
anonymous = [
agent
for agent in agents_by_instance_entries.get(instance_id, [])
if agent.account_name is None
]
if anonymous:
return anonymous[0]
candidates = agents_by_account.get(account, [])
expected_name = player.get("name")
if isinstance(expected_name, str):
for agent in candidates:
if agent.name == expected_name:
return agent
expected_profession = player.get("profession")
if isinstance(expected_profession, str):
for agent in candidates:
if spec_display_name(agent.profession, agent.elite) == expected_profession:
return agent
if candidates:
return candidates[-1]
if isinstance(instance_id, int):
return agents_by_instance.get(instance_id)
return None

def player_agent_ids(agent: Agent) -> set[int]:
if not agent.instance_id:
return {agent.id}
entries = agents_by_instance_entries.get(agent.instance_id, [])
accounts = {entry.account_name for entry in entries}
if len(accounts) > 1:
return {agent.id}
return agent_ids_by_instance.get(agent.instance_id, {agent.id})

targets = expected.get("targets") if isinstance(expected.get("targets"), list) else []
buff_map = expected.get("buffMap")
condition_skill_ids: set[int] | None = (
Expand Down Expand Up @@ -542,10 +587,7 @@ def indexed_events(index: dict[int, list[Any]], agent_ids: set[int]) -> list[Any
if not isinstance(player, dict) or not isinstance(player.get("account"), str):
continue
account = player["account"]
instance_id = player.get("instanceID")
agent: Agent | None = agents_by_account.get(account)
if agent is None and isinstance(instance_id, int):
agent = agents_by_instance.get(instance_id)
agent = select_player_agent(player, account)
if agent is None:
compared_players[account] = None
differences[f"players[{account}]"] = {"expected": "present", "actual": None}
Expand Down Expand Up @@ -637,7 +679,7 @@ def in_slice(event: Event, _lo: int = slice_lo, _hi: int = slice_hi) -> bool:
}
defense_values = {
"damageTaken": sum(event.damage for event in taken),
"damageTakenCount": sum(landed_hit(event) for event in taken),
"damageTakenCount": sum(_defense_landed_hit(event) for event in taken),
"conditionDamageTaken": sum(
_condition_damage(event, condition_skill_ids) for event in taken
),
Expand Down Expand Up @@ -709,7 +751,7 @@ def in_slice(event: Event, _lo: int = slice_lo, _hi: int = slice_hi) -> bool:
# exactly the absence window to every one of them -- the
# signature was several buffs on one actor all overcounting by
# the identical amount.
for alias_id in agent_ids:
for alias_id in player_agent_ids(agent):
alias_uptime = tracker.compute_player_uptimes(
alias_id,
duration_ms,
Expand Down
99 changes: 86 additions & 13 deletions libs/gw2_analytics/src/gw2_analytics/rotation.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@
73071: 73037,
63145: 63344,
77095: 76813,
76326: 76326,
76351: 76351,
62769: 62745,
78624: 76752,
Expand Down Expand Up @@ -199,6 +200,7 @@
62847: 50, # Unseen Sword
31658: 50, # Glyph of Equality
5572: 50, # Signet of Air (damage path; + effect-by-dst below)
76315: 50, # Bloodstone Explosion
}
_DAMAGE_CASTS_BY_DAMAGE = {40071: 44428, 46808: 40813}
_HEALING_CASTS = {
Expand All @@ -219,6 +221,29 @@
72115,
}
_MISSILE_CASTS = {26261, 29889, 42163}
_PENDING_EXPECTED_DURATION_SKILLS = {
1097,
5526,
9086,
9098,
9143,
9265,
10237,
14447,
30825,
31710,
31968,
62624,
63066,
63128,
63220,
71892,
71986,
72051,
72940,
76746,
}
_NEXT_ACTIVATION_CLAMP_SKILLS = {63267, 63128}
_ATTUNEMENT_BUFFS = {5575, 5580, 5585, 5586}
_WEAVER_ATTUNEMENTS = _ATTUNEMENT_BUFFS | {
40926,
Expand Down Expand Up @@ -404,6 +429,7 @@
}
_EFFECT_ELITE_GATE = {
"418A090D719AB44AAF1C4AD1473068C4": EliteSpec.HOLOSMITH, # Flash Spark
"9C06D9D9B0E22247A1752C426808CD80": EliteSpec.HARBINGER,
}
_AEGIS_BUFF = 743
_STABILITY_BUFF = 1122
Expand Down Expand Up @@ -679,18 +705,21 @@ def add_instant(source: int, skill_id: int, time_ms: int, icd: int = 50) -> None
if isinstance(event, SkillActivationEvent):
key = (event.source_agent_id, event.skill_id)
if event.activation in (ActivationType.NORMAL, ActivationType.QUICKNESS):
if pending := active.pop((event.source_agent_id, 63267), None):
casts.append(
SkillCast(
source_agent_id=pending.source_agent_id,
skill_id=pending.skill_id,
time_ms=pending.time_ms - origin,
duration_ms=min(
pending.expected_duration_ms,
event.time_ms - pending.time_ms + 10,
),
for pending_skill_id in _NEXT_ACTIVATION_CLAMP_SKILLS:
if pending_skill_id == event.skill_id:
continue
if pending := active.pop((event.source_agent_id, pending_skill_id), None):
casts.append(
SkillCast(
source_agent_id=pending.source_agent_id,
skill_id=pending.skill_id,
time_ms=pending.time_ms - origin,
duration_ms=min(
pending.expected_duration_ms,
event.time_ms - pending.time_ms + 10,
),
)
)
)
if (pending := active.pop(key, None)) and pending.time_ms >= origin:
casts.append(
SkillCast(
Expand Down Expand Up @@ -800,6 +829,19 @@ def add_instant(source: int, skill_id: int, time_ms: int, icd: int = 50) -> None
mapped = None if has_walk_loss else 10687
if mapped is not None and (
(event.skill_id == 73955 and event.kind == "apply" and already_active)
or (
event.skill_id == 73955
and event.kind == "apply"
and sum(
isinstance(other, BoonApplyEvent)
and other.skill_id == 73955
and other.kind == "apply"
and other.source_agent_id == event.source_agent_id
and other.target_agent_id == event.target_agent_id
for other in nearby_events(event.time_ms, 0)
)
>= 3
)
or (event.skill_id in {27581, 73955} and event.kind not in {"apply", "remove_all"})
or (
event.kind != "apply"
Expand Down Expand Up @@ -954,6 +996,7 @@ def add_instant(source: int, skill_id: int, time_ms: int, icd: int = 50) -> None
isinstance(other, BoonApplyEvent)
and other.kind == "apply"
and other.skill_id == 10243
and other.added_active
and other.target_agent_id == caster
and abs(other.time_ms - event.time_ms) < 10
for other in nearby_events(event.time_ms, 9)
Expand All @@ -968,9 +1011,39 @@ def add_instant(source: int, skill_id: int, time_ms: int, icd: int = 50) -> None
else 10192
if distortion_buff_nearby
else 10191
if caster in mesmer_agent_ids
if (
caster in mesmer_agent_ids
and elite_specs.get(caster) is not EliteSpec.CHRONOMANCER
)
else None
)
elif event.guid == "B0CF6359EBF9BF4EB94E1A2A347E5ECD":
if elite_specs.get(caster) is not EliteSpec.CONDUIT:
continue
has_nonself_nearby = any(
isinstance(other, EffectEvent)
and other.guid == event.guid
and other.source_agent_id == caster
and other.target_agent_id != caster
for other in nearby_events(event.time_ms, 2)
)
effect_skill_id = (
77116
if event.target_agent_id != caster
else None
if has_nonself_nearby
else 76818
)
elif event.guid == "239BF9EA9B747B44ACC63B86DC49B0D0":
has_distress_buffs = {73029, 73089} <= {
other.skill_id
for other in nearby_events(event.time_ms, 0)
if isinstance(other, BoonApplyEvent)
and other.kind == "apply"
and other.source_agent_id == caster
and other.target_agent_id == caster
}
effect_skill_id = 73116 if has_distress_buffs else None
else:
effect_skill_id = (
_EFFECT_CASTS_BY_DST.get(event.guid)
Expand Down Expand Up @@ -1078,7 +1151,7 @@ def add_instant(source: int, skill_id: int, time_ms: int, icd: int = 50) -> None
for pending in active.values():
duration = (
pending.expected_duration_ms
if pending.skill_id in {71892, 72940}
if pending.skill_id in _PENDING_EXPECTED_DURATION_SKILLS
else pending.duration_ms
)
casts.append(
Expand Down
Loading
Loading