diff --git a/libs/gw2_analytics/src/gw2_analytics/ei_compare.py b/libs/gw2_analytics/src/gw2_analytics/ei_compare.py index fa1a918c..429ac658 100644 --- a/libs/gw2_analytics/src/gw2_analytics/ei_compare.py +++ b/libs/gw2_analytics/src/gw2_analytics/ei_compare.py @@ -540,12 +540,16 @@ def player_agent_ids(agent: Agent) -> set[int]: for event in event_list ) account_last_slice: dict[str, object] = {} + account_names: dict[str, set[str]] = defaultdict(set) for entry in expected_players: if isinstance(entry, dict) and isinstance(entry.get("account"), str): first = entry.get("firstAware") previous = account_last_slice.get(entry["account"]) if isinstance(first, int) and (not isinstance(previous, int) or first > previous): account_last_slice[entry["account"]] = first + name = entry.get("name") + if isinstance(name, str): + account_names[entry["account"]].add(name) account_entry_count: Counter[str] = Counter( entry["account"] for entry in expected_players @@ -622,7 +626,9 @@ def in_slice(event: Event, _lo: int = slice_lo, _hi: int = slice_hi) -> bool: "group": 51 if anonymous else int(agent.subgroup or 0), "instanceID": agent.instance_id, "teamID": _team_for_entry( - player.get("firstAware") == account_last_slice.get(account), agent.team_id + len(account_names[account]) > 1 + or player.get("firstAware") == account_last_slice.get(account), + agent.team_id, ), } _compare_fields(prefix, player, values, differences) diff --git a/libs/gw2_analytics/src/gw2_analytics/rotation.py b/libs/gw2_analytics/src/gw2_analytics/rotation.py index 393bb6c8..97597bd6 100644 --- a/libs/gw2_analytics/src/gw2_analytics/rotation.py +++ b/libs/gw2_analytics/src/gw2_analytics/rotation.py @@ -222,6 +222,7 @@ } _MISSILE_CASTS = {26261, 29889, 42163} _PENDING_EXPECTED_DURATION_SKILLS = { + 1066, 1097, 5526, 9086, diff --git a/libs/gw2_analytics/tests/test_ei_compare.py b/libs/gw2_analytics/tests/test_ei_compare.py index 566e08ed..fba0eca4 100644 --- a/libs/gw2_analytics/tests/test_ei_compare.py +++ b/libs/gw2_analytics/tests/test_ei_compare.py @@ -120,6 +120,103 @@ def test_compare_elite_insights_selects_split_account_agent_by_name() -> None: assert result["differences"] == {} +def test_compare_elite_insights_keeps_team_for_character_swap_slices() -> None: + fight = Fight( + id="fight", + header=EvtcHeader(build_version="20260224", agent_count=2, duration_ms=10_000), + agents=[ + Agent( + id=1, + name="First Character", + profession=Profession.NECROMANCER, + elite=EliteSpec.RITUALIST, + is_player=True, + account_name=":Player.1234", + subgroup="1", + instance_id=1111, + team_id=2767, + ), + Agent( + id=1, + name="Second Character", + profession=Profession.WARRIOR, + elite=EliteSpec.SPELLBREAKER, + is_player=True, + account_name=":Player.1234", + subgroup="1", + instance_id=1111, + team_id=2767, + ), + ], + ) + expected: dict[str, Any] = { + "players": [ + { + "account": "Player.1234", + "instanceID": 1111, + "firstAware": 0, + "name": "First Character", + "profession": "Ritualist", + "teamID": 2767, + }, + { + "account": "Player.1234", + "instanceID": 1111, + "firstAware": 1, + "name": "Second Character", + "profession": "Spellbreaker", + "teamID": 2767, + }, + ] + } + + result = compare_elite_insights(fight, expected, []) + + assert result["differences"] == {} + + +def test_compare_elite_insights_keeps_team_on_last_same_character_slice_only() -> None: + fight = Fight( + id="fight", + header=EvtcHeader(build_version="20260424", agent_count=1, duration_ms=10_000), + agents=[ + Agent( + id=1, + name="Same Character", + profession=Profession.GUARDIAN, + elite=EliteSpec.FIREBRAND, + is_player=True, + account_name=":Player.1234", + subgroup="5", + instance_id=1111, + team_id=2767, + ), + ], + ) + expected: dict[str, Any] = { + "players": [ + { + "account": "Player.1234", + "instanceID": 1111, + "firstAware": 0, + "name": "Same Character", + "teamID": 0, + }, + { + "account": "Player.1234", + "instanceID": 1111, + "firstAware": 1, + "name": "Same Character", + "teamID": 2767, + }, + ] + } + + result = compare_elite_insights(fight, expected, []) + + assert result["differences"] == {} + + def test_compare_elite_insights_does_not_merge_shared_instance_buffs() -> None: fight = Fight( id="fight", diff --git a/libs/gw2_analytics/tests/test_rotation.py b/libs/gw2_analytics/tests/test_rotation.py index 0b358786..e7b72fcc 100644 --- a/libs/gw2_analytics/tests/test_rotation.py +++ b/libs/gw2_analytics/tests/test_rotation.py @@ -105,6 +105,26 @@ def test_build_skill_rotation_uses_expected_duration_for_pending_all_in() -> Non ] == [(9265, 200, 2_240)] +def test_build_skill_rotation_uses_expected_duration_for_pending_resurrect() -> None: + origin = 42_000_000 + events = [ + SkillActivationEvent( + time_ms=origin + 200, + source_agent_id=7, + target_agent_id=0, + skill_id=1066, + activation=ActivationType.NORMAL, + duration_ms=700, + expected_duration_ms=800, + ) + ] + + assert [ + (cast.skill_id, cast.time_ms, cast.duration_ms) + for cast in build_skill_rotation(events, duration_ms=3_000, start_time_ms=origin) + ] == [(1066, 200, 800)] + + def test_build_skill_rotation_clamps_endless_night_to_next_activation() -> None: origin = 42_000_000 events = [