From d88d61446fbaea87c2ae0c606404e867e4191b6a Mon Sep 17 00:00:00 2001 From: Dara Adedeji Date: Thu, 15 Oct 2026 18:08:57 -0400 Subject: [PATCH] Match replay guns by the folders replays carry The weapon table guessed eight folders wrong, so the Judge, Guardian, Marshal, Outlaw, Operator and Frenzy showed no weapon, the Shorty showed as the Bandit and the Bandit as nothing. Its folders now come from valorant-api.com's asset paths, checked against the economy weapons and kill sources of three 13.0x replays (every gun in their 478 kills now maps), and match ignoring case since replays carry both DMR and Dmr. Co-Authored-By: Claude Opus 5.5 --- lib/replay/replay_weapons.dart | 49 +++++++++++++++++---------------- test/replay_weapons_test.dart | 50 ++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 23 deletions(-) create mode 100644 test/replay_weapons_test.dart diff --git a/lib/replay/replay_weapons.dart b/lib/replay/replay_weapons.dart index 4b31ddc6..74fc6bd3 100644 --- a/lib/replay/replay_weapons.dart +++ b/lib/replay/replay_weapons.dart @@ -1,35 +1,38 @@ import 'package:icarus/const/weapons.dart'; /// Icarus weapons by the equippable folder Valorant keeps each gun in, e.g. -/// `/Game/Equippables/Guns/Rifles/AK/...` is the Vandal. Checked against the -/// equippable paths replays carry; an unknown path shows no weapon. +/// `/Game/Equippables/Guns/Rifles/AK/...` is the Vandal. The folders are +/// valorant-api.com's asset paths, checked against the economy weapons and +/// kill sources of 13.0x replays; an unknown path shows no weapon. Matched +/// ignoring case: replays carry both `/SniperRifles/DMR/` and `/Dmr/`. const List<(String, WeaponType)> _weaponsByPathPart = [ - ('/Sidearms/BasePistol/', WeaponType.classic), - ('/Sidearms/Shotgun/', WeaponType.shorty), - ('/Sidearms/AutomaticPistol/', WeaponType.frenzy), - ('/Sidearms/Luger/', WeaponType.ghost), - ('/Sidearms/Revolver/', WeaponType.sheriff), - ('/Sidearms/Slim/', WeaponType.bandit), - ('/SubMachineGuns/Vector/', WeaponType.stinger), - ('/SubMachineGuns/MP5/', WeaponType.spectre), - ('/Shotguns/PumpShotgun/', WeaponType.bucky), - ('/Shotguns/AutomaticShotgun/', WeaponType.judge), - ('/Rifles/Burst/', WeaponType.bulldog), - ('/Rifles/DMR/', WeaponType.guardian), - ('/Rifles/Carbine/', WeaponType.phantom), - ('/Rifles/AK/', WeaponType.vandal), - ('/Rifles/BattleRifle/', WeaponType.warden), - ('/Sniper/LeverSniperRifle/', WeaponType.marshal), - ('/Sniper/DoubleSniper/', WeaponType.outlaw), - ('/Sniper/BoltSniper/', WeaponType.operator), - ('/HvyMachineGuns/LMG/', WeaponType.ares), - ('/HvyMachineGuns/HMG/', WeaponType.odin), + ('/sidearms/basepistol/', WeaponType.classic), + ('/sidearms/slim/', WeaponType.shorty), + ('/sidearms/autopistol/', WeaponType.frenzy), + ('/sidearms/luger/', WeaponType.ghost), + ('/sidearms/revolver/', WeaponType.sheriff), + ('/sidearms/compact/', WeaponType.bandit), + ('/submachineguns/vector/', WeaponType.stinger), + ('/submachineguns/mp5/', WeaponType.spectre), + ('/shotguns/pumpshotgun/', WeaponType.bucky), + ('/shotguns/autoshotgun/', WeaponType.judge), + ('/rifles/burst/', WeaponType.bulldog), + ('/rifles/carbine/', WeaponType.phantom), + ('/rifles/ak/', WeaponType.vandal), + ('/rifles/battlerifle/', WeaponType.warden), + ('/sniperrifles/dmr/', WeaponType.guardian), + ('/sniperrifles/leversniper/', WeaponType.marshal), + ('/sniperrifles/doublesniper/', WeaponType.outlaw), + ('/sniperrifles/boltsniper/', WeaponType.operator), + ('/hvymachineguns/lmg/', WeaponType.ares), + ('/hvymachineguns/hmg/', WeaponType.odin), ]; WeaponType replayWeaponType(String? equippablePath) { if (equippablePath == null) return WeaponType.none; + final path = equippablePath.toLowerCase(); for (final (part, weapon) in _weaponsByPathPart) { - if (equippablePath.contains(part)) return weapon; + if (path.contains(part)) return weapon; } return WeaponType.none; } diff --git a/test/replay_weapons_test.dart b/test/replay_weapons_test.dart new file mode 100644 index 00000000..ac3455e6 --- /dev/null +++ b/test/replay_weapons_test.dart @@ -0,0 +1,50 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:icarus/const/weapons.dart'; +import 'package:icarus/replay/replay_weapons.dart'; + +void main() { + test('every gun path seen in replays is its weapon', () { + // Economy weapons and kill sources from three 13.0x replays, plus the + // Frenzy from valorant-api.com's asset paths (no replay had one). + const guns = { + 'Sidearms/BasePistol/BasePistol.BasePistol_C': WeaponType.classic, + 'Sidearms/Slim/SawedOffShotgun.SawedOffShotgun_C': WeaponType.shorty, + 'Sidearms/AutoPistol/AutoPistol.AutoPistol_C': WeaponType.frenzy, + 'Sidearms/Luger/LugerPistol.LugerPistol_C': WeaponType.ghost, + 'Sidearms/Revolver/RevolverPistol.RevolverPistol_C': WeaponType.sheriff, + 'Sidearms/Compact/CompactPistol.CompactPistol_C': WeaponType.bandit, + 'SubMachineGuns/Vector/Vector.Vector_C': WeaponType.stinger, + 'SubMachineGuns/MP5/SubMachineGun_MP5.SubMachineGun_MP5_C': + WeaponType.spectre, + 'Shotguns/PumpShotgun/PumpShotgun.PumpShotgun_C': WeaponType.bucky, + 'Shotguns/AutoShotgun/AutomaticShotgun.AutomaticShotgun_C': + WeaponType.judge, + 'Rifles/Burst/AssaultRifle_Burst.AssaultRifle_Burst_C': + WeaponType.bulldog, + 'SniperRifles/DMR/DMR.DMR_C': WeaponType.guardian, + 'SniperRifles/Dmr/DMR.DMR_C': WeaponType.guardian, + 'Rifles/Carbine/AssaultRifle_ACR.AssaultRifle_ACR_C': WeaponType.phantom, + 'Rifles/AK/AssaultRifle_AK.AssaultRifle_AK_C': WeaponType.vandal, + 'Rifles/BattleRifle/BattleRifle.BattleRifle_C': WeaponType.warden, + 'SniperRifles/Leversniper/LeverSniperRifle.LeverSniperRifle_C': + WeaponType.marshal, + 'SniperRifles/Doublesniper/DS_Gun.DS_Gun_C': WeaponType.outlaw, + 'SniperRifles/Boltsniper/BoltSniper.BoltSniper_C': WeaponType.operator, + 'HvyMachineGuns/LMG/LightMachineGun.LightMachineGun_C': WeaponType.ares, + 'HvyMachineGuns/HMG/HeavyMachineGun.HeavyMachineGun_C': WeaponType.odin, + }; + for (final MapEntry(key: path, value: weapon) in guns.entries) { + expect(replayWeaponType('/Game/Equippables/Guns/$path'), weapon, + reason: path); + } + }); + + test('an ability or unknown path is no weapon', () { + expect( + replayWeaponType('/Game/Characters/Deadeye/S0/Ability_Q/Gun/' + 'Gun_Deadeye_Q_Pistol.Gun_Deadeye_Q_Pistol_C'), + WeaponType.none, + ); + expect(replayWeaponType(null), WeaponType.none); + }); +}