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); + }); +}