diff --git a/assets/weapons/README.md b/assets/weapons/README.md index 720372f52..0c73edf49 100644 --- a/assets/weapons/README.md +++ b/assets/weapons/README.md @@ -25,3 +25,4 @@ The PNGs are unmodified and retain transparency. `WeaponIcon` fits their visible | marshal.png | [Marshal](https://media.valorant-api.com/weapons/c4883e50-4494-202c-3ec3-6b8a9284f00b/killstreamicon.png) | | spectre.png | [Spectre](https://media.valorant-api.com/weapons/462080d1-4035-2937-7c09-27aa2a5c27a7/killstreamicon.png) | | stinger.png | [Stinger](https://media.valorant-api.com/weapons/f7e1b454-4ad4-1063-ec0a-159e56b58941/killstreamicon.png) | +| warden.png | [Warden](https://media.valorant-api.com/weapons/8db0a1bf-4a50-832a-4566-faaaa6d250ca/killstreamicon.png), retrieved 2026-10-15 | diff --git a/assets/weapons/warden.png b/assets/weapons/warden.png new file mode 100644 index 000000000..07b0ba4db Binary files /dev/null and b/assets/weapons/warden.png differ diff --git a/lib/const/placed_classes.g.dart b/lib/const/placed_classes.g.dart index e2aebc8d1..a0e2ac811 100644 --- a/lib/const/placed_classes.g.dart +++ b/lib/const/placed_classes.g.dart @@ -118,6 +118,7 @@ const _$WeaponTypeEnumMap = { WeaponType.operator: 'operator', WeaponType.ares: 'ares', WeaponType.odin: 'odin', + WeaponType.warden: 'warden', }; PlacedViewConeAgent _$PlacedViewConeAgentFromJson(Map json) => diff --git a/lib/const/weapons.dart b/lib/const/weapons.dart index ab4183cb5..a6a497035 100644 --- a/lib/const/weapons.dart +++ b/lib/const/weapons.dart @@ -20,7 +20,8 @@ enum WeaponType { outlaw('Outlaw', WeaponCategory.snipers), operator('Operator', WeaponCategory.snipers), ares('Ares', WeaponCategory.heavies), - odin('Odin', WeaponCategory.heavies); + odin('Odin', WeaponCategory.heavies), + warden('Warden', WeaponCategory.rifles); const WeaponType(this.displayName, this.category); diff --git a/lib/hive/hive_adapters.g.dart b/lib/hive/hive_adapters.g.dart index f76e34c02..c6f8537f2 100644 --- a/lib/hive/hive_adapters.g.dart +++ b/lib/hive/hive_adapters.g.dart @@ -1638,6 +1638,8 @@ class WeaponTypeAdapter extends TypeAdapter { return WeaponType.ares; case 19: return WeaponType.odin; + case 20: + return WeaponType.warden; default: return WeaponType.none; } @@ -1686,6 +1688,8 @@ class WeaponTypeAdapter extends TypeAdapter { writer.writeByte(18); case WeaponType.odin: writer.writeByte(19); + case WeaponType.warden: + writer.writeByte(20); } } diff --git a/lib/hive/hive_adapters.g.yaml b/lib/hive/hive_adapters.g.yaml index 0197e0409..1798dc010 100644 --- a/lib/hive/hive_adapters.g.yaml +++ b/lib/hive/hive_adapters.g.yaml @@ -539,7 +539,7 @@ types: index: 6 WeaponType: typeId: 38 - nextIndex: 20 + nextIndex: 21 fields: none: index: 0 @@ -581,6 +581,8 @@ types: index: 18 odin: index: 19 + warden: + index: 20 CloudMediaJobState: typeId: 39 nextIndex: 3 diff --git a/lib/replay/replay_weapons.dart b/lib/replay/replay_weapons.dart index 41f061239..4b31ddc69 100644 --- a/lib/replay/replay_weapons.dart +++ b/lib/replay/replay_weapons.dart @@ -18,6 +18,7 @@ const List<(String, WeaponType)> _weaponsByPathPart = [ ('/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), diff --git a/lib/widgets/draggable_widgets/agents/weapon_icon.dart b/lib/widgets/draggable_widgets/agents/weapon_icon.dart index 5a59e404a..565425261 100644 --- a/lib/widgets/draggable_widgets/agents/weapon_icon.dart +++ b/lib/widgets/draggable_widgets/agents/weapon_icon.dart @@ -96,4 +96,5 @@ const _artworkBounds = { WeaponType.operator: (Size(512, 96), Rect.fromLTRB(18, 0, 494, 95)), WeaponType.ares: (Size(512, 96), Rect.fromLTRB(18, 1, 495, 96)), WeaponType.odin: (Size(384, 96), Rect.fromLTRB(18, 1, 361, 96)), + WeaponType.warden: (Size(512, 140), Rect.fromLTRB(4, 4, 505, 137)), }; diff --git a/test/agent_weapon_widgets_test.dart b/test/agent_weapon_widgets_test.dart index 63a9f46b5..ee347762b 100644 --- a/test/agent_weapon_widgets_test.dart +++ b/test/agent_weapon_widgets_test.dart @@ -470,7 +470,7 @@ void main() { }); await tester.pumpAndSettle(); expect(tester.takeException(), isNull); - expect(find.byType(WeaponIcon), findsNWidgets(19)); + expect(find.byType(WeaponIcon), findsNWidgets(20)); final sizes = tester .elementList(find.byType(AgentWidget)) .map( diff --git a/test/desktop_4_6_3_adapters_test_support.dart b/test/desktop_4_6_3_adapters_test_support.dart index 939eec4c8..27bd1eb03 100644 --- a/test/desktop_4_6_3_adapters_test_support.dart +++ b/test/desktop_4_6_3_adapters_test_support.dart @@ -1,6 +1,7 @@ // Desktop 4.6.3's adapters for the typeIds this build writes by hand, copied // verbatim from the desktop-stable-v4.6.3+103 tag (only the class names -// changed). Every other adapter is unchanged since that release. Rollback +// changed), plus its WeaponTypeAdapter, which this build extended with the +// Warden. Every other adapter is unchanged since that release. Rollback // tests decode with these to prove 4.6.3 opens, and keeps, what this build // saves. Never edit them to match this build. @@ -505,6 +506,119 @@ class Desktop463EllipseDrawingAdapter extends TypeAdapter { } } +class Desktop463WeaponTypeAdapter extends TypeAdapter { + @override + final typeId = 38; + + @override + WeaponType read(BinaryReader reader) { + switch (reader.readByte()) { + case 0: + return WeaponType.none; + case 1: + return WeaponType.classic; + case 2: + return WeaponType.shorty; + case 3: + return WeaponType.frenzy; + case 4: + return WeaponType.ghost; + case 5: + return WeaponType.bandit; + case 6: + return WeaponType.sheriff; + case 7: + return WeaponType.bulldog; + case 8: + return WeaponType.guardian; + case 9: + return WeaponType.phantom; + case 10: + return WeaponType.vandal; + case 11: + return WeaponType.bucky; + case 12: + return WeaponType.judge; + case 13: + return WeaponType.stinger; + case 14: + return WeaponType.spectre; + case 15: + return WeaponType.marshal; + case 16: + return WeaponType.outlaw; + case 17: + return WeaponType.operator; + case 18: + return WeaponType.ares; + case 19: + return WeaponType.odin; + default: + return WeaponType.none; + } + } + + @override + void write(BinaryWriter writer, WeaponType obj) { + switch (obj) { + case WeaponType.none: + writer.writeByte(0); + case WeaponType.classic: + writer.writeByte(1); + case WeaponType.shorty: + writer.writeByte(2); + case WeaponType.frenzy: + writer.writeByte(3); + case WeaponType.ghost: + writer.writeByte(4); + case WeaponType.bandit: + writer.writeByte(5); + case WeaponType.sheriff: + writer.writeByte(6); + case WeaponType.bulldog: + writer.writeByte(7); + case WeaponType.guardian: + writer.writeByte(8); + case WeaponType.phantom: + writer.writeByte(9); + case WeaponType.vandal: + writer.writeByte(10); + case WeaponType.bucky: + writer.writeByte(11); + case WeaponType.judge: + writer.writeByte(12); + case WeaponType.stinger: + writer.writeByte(13); + case WeaponType.spectre: + writer.writeByte(14); + case WeaponType.marshal: + writer.writeByte(15); + case WeaponType.outlaw: + writer.writeByte(16); + case WeaponType.operator: + writer.writeByte(17); + case WeaponType.ares: + writer.writeByte(18); + case WeaponType.odin: + writer.writeByte(19); + // Not in 4.6.3, which can never hold one; only Dart's exhaustive + // switch needs the case. + case WeaponType.warden: + throw StateError('Desktop 4.6.3 has no Warden'); + } + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is Desktop463WeaponTypeAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} + int _readDrawingHiveColorValue( Map fields, { required int colorFieldIndex, diff --git a/test/hive_desktop_rollback_test.dart b/test/hive_desktop_rollback_test.dart index b019d5f2b..da107ae5d 100644 --- a/test/hive_desktop_rollback_test.dart +++ b/test/hive_desktop_rollback_test.dart @@ -30,7 +30,8 @@ HiveImpl _currentRegistry() { } /// What desktop 4.6.3 registers: this build's adapters, which are unchanged -/// since that release, except the eight this build writes by hand. +/// since that release, except the eight this build writes by hand and the +/// weapon list, which gained the Warden. HiveImpl _desktop463Registry() { final registry = _currentRegistry(); // hive_ce warns on every override, which is the point here. @@ -45,7 +46,8 @@ HiveImpl _desktop463Registry() { ..registerAdapter(Desktop463FreeDrawingAdapter(), override: true) ..registerAdapter(Desktop463LineAdapter(), override: true) ..registerAdapter(Desktop463RectangleDrawingAdapter(), override: true) - ..registerAdapter(Desktop463EllipseDrawingAdapter(), override: true); + ..registerAdapter(Desktop463EllipseDrawingAdapter(), override: true) + ..registerAdapter(Desktop463WeaponTypeAdapter(), override: true); } finally { Logger.level = previousLevel; } @@ -191,6 +193,22 @@ void main() { expect(opened.lineUpOrigins.single.agent.weapon, WeaponType.sheriff); }); + test('desktop 4.6.3 opens a Warden this build saved as no weapon', () { + final page = _richPage(); + page.agentData.first.weapon = WeaponType.warden; + page.lineUpOrigins.single.agent.weapon = WeaponType.warden; + final opened = _across(current, desktop463, page); + + expect( + opened.agentData.map((agent) => agent.weapon), + [WeaponType.none, WeaponType.operator, WeaponType.none], + ); + expect(opened.lineUpOrigins.single.agent.weapon, WeaponType.none); + page.agentData.first.weapon = WeaponType.none; + page.lineUpOrigins.single.agent.weapon = WeaponType.none; + _expectSamePage(opened, page); + }); + test('what desktop 4.6.3 saves back, this build opens whole', () { final page = _richPage(); final rolledBack = _across(current, desktop463, page);