diff --git a/libs/gw2_analytics/src/gw2_analytics/ei_compare.py b/libs/gw2_analytics/src/gw2_analytics/ei_compare.py index 087c5f62..8d578aa6 100644 --- a/libs/gw2_analytics/src/gw2_analytics/ei_compare.py +++ b/libs/gw2_analytics/src/gw2_analytics/ei_compare.py @@ -11,6 +11,7 @@ from gw2_analytics.initial_buffs import extract_initial_buffs from gw2_analytics.rotation import build_skill_rotation from gw2_core import ( + Agent, BlockEvent, BoonApplyEvent, BuffApplyEvent, @@ -236,7 +237,10 @@ def compare_elite_insights( # noqa: PLR0912, PLR0915 agents_by_account = { agent.account_name.lstrip(":"): agent for agent in fight.agents if agent.account_name } - agents_by_instance = {agent.instance_id: agent for agent in fight.agents if agent.instance_id} + agents_by_instance: dict[int, Agent] = {} + for fight_agent in fight.agents: + if fight_agent.instance_id: + agents_by_instance.setdefault(fight_agent.instance_id, fight_agent) agent_ids_by_instance: dict[int, set[int]] = defaultdict(set) for fight_agent in fight.agents: if fight_agent.instance_id: @@ -298,7 +302,7 @@ def compare_elite_insights( # noqa: PLR0912, PLR0915 continue account = player["account"] instance_id = player.get("instanceID") - agent = agents_by_account.get(account) + agent: Agent | None = agents_by_account.get(account) if agent is None and isinstance(instance_id, int): agent = agents_by_instance.get(instance_id) if agent is None: @@ -362,6 +366,13 @@ def compare_elite_insights( # noqa: PLR0912, PLR0915 for event in event_list if isinstance(event, DownEvent) and event.source_agent_id in agent_ids ] + outcome_downs = { + event.time_ms + for event in event_list + if isinstance(event, CombatOutcomeEvent) + and event.outcome == "downed" + and event.target_agent_id in agent_ids + } defense_values = { "damageTaken": sum(event.damage for event in taken), "damageTakenCount": sum(_connected(event) for event in taken), @@ -379,36 +390,30 @@ def compare_elite_insights( # noqa: PLR0912, PLR0915 isinstance(event, DodgeEvent) and event.source_agent_id == agent.id for event in event_list ), - "downCount": len( - { - event.time_ms - for event in event_list - if isinstance(event, BoonApplyEvent) - and event.kind == "apply" - and event.skill_id == 770 - and event.target_agent_id in agent_ids - } - ) - or ( - len(downed) - if anonymous + "downCount": ( + len(outcome_downs) + if outcome_downs and not anonymous else len( { event.time_ms for event in event_list - if ( - isinstance(event, CombatOutcomeEvent) - and event.outcome == "downed" - and event.target_agent_id in agent_ids - ) + if isinstance(event, BoonApplyEvent) + and event.kind == "apply" + and event.skill_id == 770 + and event.target_agent_id in agent_ids } ) + ) + or ( + len(downed) + if anonymous + else len(outcome_downs) if not has_downed_buff_applies else 0 ), "downDuration": sum(event.downtime_ms for event in downed), "deadCount": sum( - isinstance(event, DeathEvent) and event.source_agent_id == agent.id + isinstance(event, DeathEvent) and event.source_agent_id in agent_ids for event in event_list ), } diff --git a/libs/gw2_analytics/tests/test_ei_compare.py b/libs/gw2_analytics/tests/test_ei_compare.py new file mode 100644 index 00000000..2eca44dd --- /dev/null +++ b/libs/gw2_analytics/tests/test_ei_compare.py @@ -0,0 +1,113 @@ +from __future__ import annotations + +from typing import Any + +from gw2_analytics.ei_compare import compare_elite_insights +from gw2_core import ( + Agent, + BoonApplyEvent, + CombatOutcomeEvent, + DeathEvent, + EliteSpec, + Fight, + Profession, +) + + +def test_compare_elite_insights_keeps_first_anonymous_agent_for_shared_instance() -> None: + fight = Fight( + id="fight", + agents=[ + Agent( + id=1, + name="Chronomancienne", + profession=Profession.MESMER, + elite=EliteSpec.CHRONOMANCER, + is_player=True, + instance_id=3994, + ), + Agent( + id=2, + name="Virtuose", + profession=Profession.MESMER, + elite=EliteSpec.VIRTUOSO, + is_player=True, + instance_id=3994, + ), + ], + ) + expected: dict[str, Any] = { + "players": [ + { + "account": "Non Squad Player 5", + "instanceID": 3994, + "name": "Chronomancer pl-3994", + "defenses": [{"deadCount": 2}], + } + ] + } + events = [ + DeathEvent(time_ms=1, source_agent_id=1, target_agent_id=0, skill_id=0), + DeathEvent(time_ms=2, source_agent_id=2, target_agent_id=0, skill_id=0), + ] + + result = compare_elite_insights(fight, expected, events) + + assert result["differences"] == {} + + +def test_compare_elite_insights_prefers_outcome_downs_for_named_players() -> None: + fight = Fight( + id="fight", + agents=[ + Agent( + id=1, + name="Player", + profession=Profession.MESMER, + elite=EliteSpec.CHRONOMANCER, + is_player=True, + account_name=":Player.1234", + instance_id=1111, + ) + ], + ) + expected: dict[str, Any] = { + "players": [ + { + "account": "Player.1234", + "name": "Player", + "defenses": [{"downCount": 1}], + } + ] + } + events = [ + CombatOutcomeEvent( + time_ms=100, + source_agent_id=2, + target_agent_id=1, + skill_id=42, + outcome="downed", + ), + BoonApplyEvent( + time_ms=100, + source_agent_id=0, + target_agent_id=1, + skill_id=770, + duration_ms=2_147_483_647, + stacks=1, + kind="apply", + ), + BoonApplyEvent( + time_ms=200, + source_agent_id=0, + target_agent_id=1, + skill_id=770, + duration_ms=2_147_483_647, + stacks=1, + kind="apply", + ), + ] + + result = compare_elite_insights(fight, expected, events) + + assert result["differences"] == {}