From 9931e7bf8961152d8a6851067d4515025df03683 Mon Sep 17 00:00:00 2001 From: PhatPhuckDave Date: Sun, 16 Mar 2025 13:31:15 +0100 Subject: [PATCH] Implement loading dependencies for mods --- lib/mod_list.dart | 32 +++++++++++++++++++++--- test/mod_list_test.dart | 54 +++++++++++++++++++++++++++++++++++------ 2 files changed, 76 insertions(+), 10 deletions(-) diff --git a/lib/mod_list.dart b/lib/mod_list.dart index 40d8a69..227bc89 100644 --- a/lib/mod_list.dart +++ b/lib/mod_list.dart @@ -5,13 +5,13 @@ import 'package:rimworld_modman/mod.dart'; import 'package:xml/xml.dart'; class ModList { - final String configPath; - final String modsPath; + String configPath = ''; + String modsPath = ''; // O(1) lookup Map activeMods = {}; Map mods = {}; - ModList({required this.configPath, required this.modsPath}); + ModList({this.configPath = '', this.modsPath = ''}); Stream loadAvailable() async* { final logger = Logger.instance; @@ -184,6 +184,18 @@ class ModList { } } + void enableAll() { + for (final mod in mods.values) { + setEnabled(mod.id, true); + } + } + + void disableAll() { + for (final mod in mods.values) { + setEnabled(mod.id, false); + } + } + List> checkIncompatibilities() { List> conflicts = []; List activeModIds = activeMods.keys.toList(); @@ -387,6 +399,20 @@ class ModList { return bestOrder; } + + List loadRequired() { + final toEnable = []; + for (final modid in activeMods.keys) { + final mod = mods[modid]!; + for (final dep in mod.dependencies) { + toEnable.add(dep); + } + } + for (final modid in toEnable) { + setEnabled(modid, true); + } + return generateLoadOrder(); + } } String _expansionNameFromId(String id) { diff --git a/test/mod_list_test.dart b/test/mod_list_test.dart index 6fe9acc..ed39ef8 100644 --- a/test/mod_list_test.dart +++ b/test/mod_list_test.dart @@ -8,7 +8,7 @@ const configRoot = '$root/AppData/RimWorld by Ludeon Studios/Config'; const configPath = '$configRoot/ModsConfig.xml'; const logsPath = '$root/ModManager'; -void main() { +Map generateDummyMods() { final dummyMod = Mod( name: 'Dummy Mod', id: 'dummy', @@ -32,6 +32,13 @@ void main() { loadBefore: ["ludeon.rimworld"], size: 47, ), + 'prepatcher': dummyMod.copyWith( + name: 'Prepatcher', + id: 'prepatcher', + loadAfter: ["ludeon.rimworld"], + dependencies: ["harmony"], + size: 47, + ), 'ludeon.rimworld': dummyMod.copyWith( name: 'RimWorld', id: 'ludeon.rimworld', @@ -56,11 +63,14 @@ void main() { ), }; - final dummyList = ModList(configPath: configPath, modsPath: modsRoot); - dummyList.mods.addAll(dummyMods); - for (final mod in dummyMods.keys) { - dummyList.setEnabled(mod, true); - } + return dummyMods; +} + +void main() { + final dummyMods = generateDummyMods(); + final dummyList = ModList(); + dummyList.mods = dummyMods; + dummyList.enableAll(); dummyList.setEnabled('disabledDummy', false); dummyList.setEnabled('incompatible', false); final sortedMods = dummyList.generateLoadOrder(); @@ -71,6 +81,13 @@ void main() { final rimworldIndex = sortedMods.indexOf('ludeon.rimworld'); expect(harmonyIndex, lessThan(rimworldIndex)); }); + test('Prepatcher should load after Harmony and RimWorld', () { + final prepatcherIndex = sortedMods.indexOf('prepatcher'); + final harmonyIndex = sortedMods.indexOf('harmony'); + final rimworldIndex = sortedMods.indexOf('ludeon.rimworld'); + expect(prepatcherIndex, greaterThan(harmonyIndex)); + expect(prepatcherIndex, greaterThan(rimworldIndex)); + }); test('RimWorld should load before Anomaly', () { final rimworldIndex = sortedMods.indexOf('ludeon.rimworld'); final anomalyIndex = sortedMods.indexOf('ludeon.rimworld.anomaly'); @@ -85,9 +102,32 @@ void main() { final yuuugeIndex = sortedMods.indexOf('yuuuge'); expect(yuuugeIndex, lessThan(smolIndex)); }); - dummyList.setEnabled('incompatible', true); test('Error generating load order with incompatible mods', () { + dummyList.setEnabled('incompatible', true); expect(() => dummyList.generateLoadOrder(), throwsException); }); }); + + final dummyMods2 = generateDummyMods(); + final dummyList2 = ModList(); + dummyList2.mods = dummyMods2; + dummyList2.disableAll(); + dummyList2.setEnabled('prepatcher', true); + final sortedMods2 = dummyList2.loadRequired(); + group('Test loadRequired', () { + test( + 'Harmony should get enabled by loadRequired as a dependency of prepatcher', + () { + final harmonyIndex = sortedMods2.indexOf('harmony'); + expect(harmonyIndex, isNot(-1)); + }, + ); + test('No other mods should get enabled', () { + for (final mod in dummyMods2.keys) { + if (mod != 'prepatcher' && mod != 'harmony') { + expect(sortedMods2.indexOf(mod), isNegative); + } + } + }); + }); }