Compare commits
3 Commits
efe74b404e
...
02cfe01ae0
| Author | SHA1 | Date | |
|---|---|---|---|
| 02cfe01ae0 | |||
| 1dabc804b4 | |||
| a6cfd3e16e |
@@ -115,7 +115,6 @@ class _ModManagerPageState extends State<ModManagerPage> {
|
||||
|
||||
bool _isLoading = false;
|
||||
String _statusMessage = '';
|
||||
int _totalModsFound = 0;
|
||||
bool _skipFileCount = false;
|
||||
bool _hasCycles = false;
|
||||
List<String>? _cycleInfo;
|
||||
@@ -156,7 +155,6 @@ class _ModManagerPageState extends State<ModManagerPage> {
|
||||
_activeMods = modManager.mods.values.where((m) => m.enabled).toList();
|
||||
_isLoading = false;
|
||||
_statusMessage = 'Loaded ${_availableMods.length} mods';
|
||||
_totalModsFound = _availableMods.length;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -341,7 +339,12 @@ class _ModManagerPageState extends State<ModManagerPage> {
|
||||
margin: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.red.shade900.withOpacity(0.3),
|
||||
color: Color.fromRGBO(
|
||||
Colors.red.shade900.red,
|
||||
Colors.red.shade900.green,
|
||||
Colors.red.shade900.blue,
|
||||
0.3
|
||||
),
|
||||
borderRadius: BorderRadius.circular(4.0),
|
||||
border: Border.all(color: Colors.red.shade800),
|
||||
),
|
||||
@@ -677,9 +680,7 @@ class _ModManagerPageState extends State<ModManagerPage> {
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
_searchQuery.isNotEmpty
|
||||
? Colors.blue.withOpacity(
|
||||
0.2,
|
||||
)
|
||||
? const Color.fromRGBO(0, 0, 255, 0.2)
|
||||
: null,
|
||||
borderRadius:
|
||||
BorderRadius.circular(4),
|
||||
@@ -823,7 +824,6 @@ class _ModManagerPageState extends State<ModManagerPage> {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_statusMessage = 'Loaded mod: ${mod.name}';
|
||||
_totalModsFound = modManager.mods.length;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1093,14 +1093,16 @@ class _ModManagerPageState extends State<ModManagerPage> {
|
||||
});
|
||||
|
||||
// Show success message
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
'Mod order saved to config. ${_activeMods.length} mods enabled.',
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
'Mod order saved to config. ${_activeMods.length} mods enabled.',
|
||||
),
|
||||
backgroundColor: Colors.green,
|
||||
),
|
||||
backgroundColor: Colors.green,
|
||||
),
|
||||
);
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
final logger = Logger.instance;
|
||||
logger.error('Error saving mod load order: $e');
|
||||
@@ -1111,12 +1113,14 @@ class _ModManagerPageState extends State<ModManagerPage> {
|
||||
});
|
||||
|
||||
// Show error message
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Error saving mod order: $e'),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Error saving mod order: $e'),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
11
lib/mod.dart
11
lib/mod.dart
@@ -47,6 +47,17 @@ class Mod {
|
||||
this.enabled = false,
|
||||
});
|
||||
|
||||
int get tier {
|
||||
if (isBaseGame) return 0;
|
||||
if (isExpansion) return 1;
|
||||
return 2;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Mod{name: $name, id: $id, path: $path, dependencies: $dependencies, loadAfter: $loadAfter, loadBefore: $loadBefore, incompatibilities: $incompatibilities, size: $size, isBaseGame: $isBaseGame, isExpansion: $isExpansion}';
|
||||
}
|
||||
|
||||
static Mod fromDirectory(String path, {bool skipFileCount = false}) {
|
||||
final logger = Logger.instance;
|
||||
final stopwatch = Stopwatch()..start();
|
||||
|
||||
@@ -255,34 +255,145 @@ class ModList {
|
||||
LoadOrder generateLoadOrder([LoadOrder? loadOrder]) {
|
||||
loadOrder ??= LoadOrder();
|
||||
final logger = Logger.instance;
|
||||
logger.info('Generating load order...');
|
||||
|
||||
for (final mod in activeMods.values) {
|
||||
logger.info('Checking mod: ${mod.id}');
|
||||
logger.info('Mod details: ${mod.toString()}');
|
||||
for (final incomp in mod.incompatibilities) {
|
||||
if (activeMods.containsKey(incomp)) {
|
||||
loadOrder.errors.add(
|
||||
'Incompatibility detected: ${mod.id} is incompatible with $incomp',
|
||||
);
|
||||
logger.warning(
|
||||
'Incompatibility detected: ${mod.id} is incompatible with $incomp',
|
||||
);
|
||||
} else {
|
||||
logger.info('No incompatibility found for: $incomp');
|
||||
}
|
||||
}
|
||||
for (final dep in mod.dependencies) {
|
||||
if (!activeMods.containsKey(dep)) {
|
||||
loadOrder.errors.add('Missing dependency: ${mod.id} requires $dep');
|
||||
logger.warning('Missing dependency: ${mod.id} requires $dep');
|
||||
} else {
|
||||
logger.info('Dependency found: ${mod.id} requires $dep');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.info('Adding active mods to load order...');
|
||||
loadOrder.order.addAll(activeMods.values.toList());
|
||||
logger.info(
|
||||
'Active mods added: ${loadOrder.order.map((mod) => mod.id).join(', ')}',
|
||||
);
|
||||
|
||||
loadOrder.order.sort((a, b) {
|
||||
if (a.isBaseGame && !b.isBaseGame) return -1;
|
||||
if (!a.isBaseGame && b.isBaseGame) return 1;
|
||||
if (a.isExpansion && !b.isExpansion) return -1;
|
||||
if (!a.isExpansion && b.isExpansion) return 1;
|
||||
final modMap = {for (final mod in loadOrder.order) mod.id: mod};
|
||||
final graph = <String, Set<String>>{};
|
||||
final inDegree = <String, int>{};
|
||||
|
||||
// Step 1: Initialize graph and inDegree
|
||||
for (final mod in loadOrder.order) {
|
||||
graph[mod.id] = <String>{};
|
||||
inDegree[mod.id] = 0;
|
||||
}
|
||||
|
||||
// Step 2: Build dependency graph
|
||||
void addEdge(String from, String to) {
|
||||
final fromMod = modMap[from];
|
||||
if (fromMod == null) {
|
||||
logger.warning('Missing dependency: $from');
|
||||
return;
|
||||
}
|
||||
final toMod = modMap[to];
|
||||
if (toMod == null) {
|
||||
logger.warning('Missing dependency: $to');
|
||||
return;
|
||||
}
|
||||
if (graph[from]!.add(to)) {
|
||||
inDegree[to] = inDegree[to]! + 1;
|
||||
}
|
||||
}
|
||||
|
||||
for (final mod in loadOrder.order) {
|
||||
for (final target in mod.loadBefore) {
|
||||
addEdge(mod.id, target);
|
||||
}
|
||||
for (final target in mod.loadAfter) {
|
||||
addEdge(target, mod.id);
|
||||
}
|
||||
for (final dep in mod.dependencies) {
|
||||
addEdge(dep, mod.id);
|
||||
}
|
||||
}
|
||||
|
||||
// Step 3: Calculate tiers dynamically with cross-tier dependencies
|
||||
final tiers = <Mod, int>{};
|
||||
for (final mod in loadOrder.order) {
|
||||
int tier = 2; // Default to Tier 2
|
||||
|
||||
// Check if mod loads before any base game mod (Tier 0)
|
||||
final loadsBeforeBase = mod.loadBefore.any(
|
||||
(id) => modMap[id]?.isBaseGame ?? false,
|
||||
);
|
||||
if (mod.isBaseGame || loadsBeforeBase) {
|
||||
tier = 0;
|
||||
} else {
|
||||
// Check if mod loads before any expansion (Tier 1)
|
||||
final loadsBeforeExpansion = mod.loadBefore.any(
|
||||
(id) => modMap[id]?.isExpansion ?? false,
|
||||
);
|
||||
if (mod.isExpansion || loadsBeforeExpansion) {
|
||||
tier = 1;
|
||||
}
|
||||
}
|
||||
|
||||
tiers[mod] = tier;
|
||||
}
|
||||
|
||||
// Step 4: Global priority queue (tier ascending, size descending)
|
||||
final pq = PriorityQueue<Mod>((a, b) {
|
||||
final tierA = tiers[a]!;
|
||||
final tierB = tiers[b]!;
|
||||
if (tierA != tierB) return tierA.compareTo(tierB);
|
||||
return b.size.compareTo(a.size);
|
||||
});
|
||||
|
||||
Map<String, List<Mod>> relations = {};
|
||||
for (int i = loadOrder.order.length - 1; i >= 0; i--) {
|
||||
final mod = loadOrder!.order[i];
|
||||
logger.info('Processing mod: ${mod.id}');
|
||||
loadOrder = shuffleMod(mod, loadOrder, relations);
|
||||
// Initialize queue with mods having inDegree 0
|
||||
for (final mod in loadOrder.order) {
|
||||
if (inDegree[mod.id] == 0) {
|
||||
pq.add(mod);
|
||||
}
|
||||
}
|
||||
|
||||
return loadOrder!;
|
||||
final orderedMods = <Mod>[];
|
||||
while (pq.isNotEmpty) {
|
||||
final current = pq.removeFirst();
|
||||
orderedMods.add(current);
|
||||
|
||||
for (final neighborId in graph[current.id]!) {
|
||||
inDegree[neighborId] = inDegree[neighborId]! - 1;
|
||||
if (inDegree[neighborId] == 0) {
|
||||
final neighbor = modMap[neighborId]!;
|
||||
pq.add(neighbor);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (orderedMods.length != loadOrder.order.length) {
|
||||
loadOrder.errors.add('Cycle detected in dependencies');
|
||||
logger.warning(
|
||||
'Cycle detected in dependencies: expected ${loadOrder.order.length}, got ${orderedMods.length}.',
|
||||
);
|
||||
}
|
||||
|
||||
loadOrder.order = orderedMods;
|
||||
logger.info(
|
||||
'Load order generated successfully with ${loadOrder.order.length} mods.',
|
||||
);
|
||||
for (final mod in loadOrder.order) {
|
||||
logger.info('Mod: ${mod.toString()}');
|
||||
}
|
||||
return loadOrder;
|
||||
}
|
||||
|
||||
// The point of relations and the recursive call is to handle the case where
|
||||
|
||||
@@ -200,18 +200,18 @@ void main() {
|
||||
final expected = [
|
||||
'brrainz.harmony',
|
||||
'ludeon.rimworld',
|
||||
'ludeon.rimworld.royalty',
|
||||
'ludeon.rimworld.ideology',
|
||||
'ludeon.rimworld.biotech',
|
||||
'ludeon.rimworld.anomaly',
|
||||
'ludeon.rimworld.biotech',
|
||||
'ludeon.rimworld.ideology',
|
||||
'ludeon.rimworld.royalty',
|
||||
'dubwise.rimatomics',
|
||||
'jecrell.doorsexpanded',
|
||||
'dubwise.rimefeller',
|
||||
'neronix17.toolbox',
|
||||
'automatic.bionicicons',
|
||||
'lwm.deepstorage',
|
||||
'dubwise.dubsmintmenus',
|
||||
'dubwise.dubsmintminimap',
|
||||
'dubwise.dubsmintmenus',
|
||||
'brrainz.justignoremepassing',
|
||||
];
|
||||
expect(result.errors, isEmpty);
|
||||
@@ -299,6 +299,414 @@ void main() {
|
||||
list.enableAll();
|
||||
final order = list.generateLoadOrder();
|
||||
|
||||
final expected = [
|
||||
'zetrith.prepatcher',
|
||||
'brrainz.harmony',
|
||||
'ludeon.rimworld',
|
||||
'bs.betterlog',
|
||||
'ludeon.rimworld.anomaly',
|
||||
'ludeon.rimworld.royalty',
|
||||
'ludeon.rimworld.ideology',
|
||||
'ludeon.rimworld.biotech',
|
||||
];
|
||||
expect(order.loadOrder, equals(expected));
|
||||
});
|
||||
test('Expansions should load before most mods', () {
|
||||
final list = ModList();
|
||||
list.mods = {
|
||||
'bs.betterlog': makeDummy().copyWith(
|
||||
id: 'bs.betterlog',
|
||||
name: 'Better Log - Fix your errors',
|
||||
enabled: true,
|
||||
size: 69,
|
||||
dependencies: [],
|
||||
loadAfter: [
|
||||
'brrainz.harmony',
|
||||
'me.samboycoding.betterloading',
|
||||
'zetrith.prepatcher',
|
||||
'ludeon.rimworld',
|
||||
],
|
||||
loadBefore: [
|
||||
'ludeon.rimworld.royalty',
|
||||
'ludeon.rimworld.ideology',
|
||||
'ludeon.rimworld.biotech',
|
||||
'ludeon.rimworld.anomaly',
|
||||
'bs.performance',
|
||||
'unlimitedhugs.hugslib',
|
||||
],
|
||||
incompatibilities: [],
|
||||
),
|
||||
'zetrith.prepatcher': makeDummy().copyWith(
|
||||
id: 'zetrith.prepatcher',
|
||||
name: 'Prepatcher',
|
||||
path:
|
||||
'C:/Users/Administrator/Seafile/Games-RimWorld/294100\\2934420800',
|
||||
dependencies: [],
|
||||
loadAfter: [],
|
||||
loadBefore: ['ludeon.rimworld', 'brrainz.harmony'],
|
||||
incompatibilities: [],
|
||||
size: 21,
|
||||
isBaseGame: false,
|
||||
isExpansion: false,
|
||||
),
|
||||
'brrainz.harmony': makeDummy().copyWith(
|
||||
id: 'brrainz.harmony',
|
||||
name: 'Harmony',
|
||||
path:
|
||||
'C:/Users/Administrator/Seafile/Games-RimWorld/294100\\2009463077',
|
||||
dependencies: [],
|
||||
loadAfter: [],
|
||||
loadBefore: ['ludeon.rimworld'],
|
||||
incompatibilities: [],
|
||||
size: 17,
|
||||
isBaseGame: false,
|
||||
isExpansion: false,
|
||||
),
|
||||
'ludeon.rimworld': makeDummy().copyWith(
|
||||
id: 'ludeon.rimworld',
|
||||
name: 'RimWorld',
|
||||
path: '',
|
||||
dependencies: [],
|
||||
loadAfter: [],
|
||||
loadBefore: [],
|
||||
incompatibilities: [],
|
||||
size: 0,
|
||||
isBaseGame: true,
|
||||
isExpansion: false,
|
||||
),
|
||||
'rah.rbse': makeDummy().copyWith(
|
||||
id: 'rah.rbse',
|
||||
name: 'RBSE',
|
||||
path: 'C:/Users/Administrator/Seafile/Games-RimWorld/294100\\850429707',
|
||||
dependencies: [],
|
||||
loadAfter: [],
|
||||
loadBefore: [],
|
||||
incompatibilities: [],
|
||||
size: 1729,
|
||||
isBaseGame: false,
|
||||
isExpansion: false,
|
||||
),
|
||||
'mlie.usethisinstead': makeDummy().copyWith(
|
||||
id: 'mlie.usethisinstead',
|
||||
name: 'Use This Instead',
|
||||
path:
|
||||
'C:/Users/Administrator/Seafile/Games-RimWorld/294100\\3396308787',
|
||||
dependencies: [],
|
||||
loadAfter: ['brrainz.harmony'],
|
||||
loadBefore: [],
|
||||
incompatibilities: [],
|
||||
size: 1651,
|
||||
isBaseGame: false,
|
||||
isExpansion: false,
|
||||
),
|
||||
'dubwise.rimatomics': makeDummy().copyWith(
|
||||
id: 'dubwise.rimatomics',
|
||||
name: 'Dubs Rimatomics',
|
||||
path:
|
||||
'C:/Users/Administrator/Seafile/Games-RimWorld/294100\\1127530465',
|
||||
dependencies: [],
|
||||
loadAfter: [],
|
||||
loadBefore: [],
|
||||
incompatibilities: [],
|
||||
size: 1563,
|
||||
isBaseGame: false,
|
||||
isExpansion: false,
|
||||
),
|
||||
'jecrell.doorsexpanded': makeDummy().copyWith(
|
||||
id: 'jecrell.doorsexpanded',
|
||||
name: 'Doors Expanded',
|
||||
path:
|
||||
'C:/Users/Administrator/Seafile/Games-RimWorld/294100\\1316188771',
|
||||
dependencies: [],
|
||||
loadAfter: [],
|
||||
loadBefore: [],
|
||||
incompatibilities: [],
|
||||
size: 765,
|
||||
isBaseGame: false,
|
||||
isExpansion: false,
|
||||
),
|
||||
'balistafreak.stopdropandroll': makeDummy().copyWith(
|
||||
id: 'balistafreak.stopdropandroll',
|
||||
name: 'Stop, Drop, And Roll! [BAL]',
|
||||
path:
|
||||
'C:/Users/Administrator/Seafile/Games-RimWorld/294100\\2362707956',
|
||||
dependencies: [],
|
||||
loadAfter: [],
|
||||
loadBefore: [],
|
||||
incompatibilities: [],
|
||||
size: 755,
|
||||
isBaseGame: false,
|
||||
isExpansion: false,
|
||||
),
|
||||
'fluffy.animaltab': makeDummy().copyWith(
|
||||
id: 'fluffy.animaltab',
|
||||
name: 'Animal Tab',
|
||||
path: 'C:/Users/Administrator/Seafile/Games-RimWorld/294100\\712141500',
|
||||
dependencies: ['brrainz.harmony'],
|
||||
loadAfter: [],
|
||||
loadBefore: [],
|
||||
incompatibilities: [],
|
||||
size: 752,
|
||||
isBaseGame: false,
|
||||
isExpansion: false,
|
||||
),
|
||||
'gt.sam.glittertech': makeDummy().copyWith(
|
||||
id: 'gt.sam.glittertech',
|
||||
name: 'Glitter Tech Classic',
|
||||
path: 'C:/Users/Administrator/Seafile/Games-RimWorld/294100\\725576127',
|
||||
dependencies: [],
|
||||
loadAfter: [],
|
||||
loadBefore: [],
|
||||
incompatibilities: [],
|
||||
size: 747,
|
||||
isBaseGame: false,
|
||||
isExpansion: false,
|
||||
),
|
||||
'dubwise.rimefeller': makeDummy().copyWith(
|
||||
id: 'dubwise.rimefeller',
|
||||
name: 'Rimefeller',
|
||||
path:
|
||||
'C:/Users/Administrator/Seafile/Games-RimWorld/294100\\1321849735',
|
||||
dependencies: [],
|
||||
loadAfter: [],
|
||||
loadBefore: [],
|
||||
incompatibilities: [],
|
||||
size: 744,
|
||||
isBaseGame: false,
|
||||
isExpansion: false,
|
||||
),
|
||||
'darthcy.misc.morebetterdeepdrill': makeDummy().copyWith(
|
||||
id: 'darthcy.misc.morebetterdeepdrill',
|
||||
name: 'More and Better Deep Drill',
|
||||
path:
|
||||
'C:/Users/Administrator/Seafile/Games-RimWorld/294100\\3378527302',
|
||||
dependencies: [],
|
||||
loadAfter: ['brrainz.harmony', 'spdskatr.projectrimfactory'],
|
||||
loadBefore: [],
|
||||
incompatibilities: [],
|
||||
size: 738,
|
||||
isBaseGame: false,
|
||||
isExpansion: false,
|
||||
),
|
||||
'haplo.miscellaneous.training': makeDummy().copyWith(
|
||||
id: 'haplo.miscellaneous.training',
|
||||
name: 'Misc. Training',
|
||||
path: 'C:/Users/Administrator/Seafile/Games-RimWorld/294100\\717575199',
|
||||
dependencies: [],
|
||||
loadAfter: ['haplo.miscellaneous.core'],
|
||||
loadBefore: [],
|
||||
incompatibilities: ['haplo.miscellaneous.trainingnotask'],
|
||||
size: 733,
|
||||
isBaseGame: false,
|
||||
isExpansion: false,
|
||||
),
|
||||
'linkolas.stabilize': makeDummy().copyWith(
|
||||
id: 'linkolas.stabilize',
|
||||
name: 'Stabilize',
|
||||
path:
|
||||
'C:/Users/Administrator/Seafile/Games-RimWorld/294100\\2023407836',
|
||||
dependencies: [],
|
||||
loadAfter: [],
|
||||
loadBefore: [],
|
||||
incompatibilities: [],
|
||||
size: 627,
|
||||
isBaseGame: false,
|
||||
isExpansion: false,
|
||||
),
|
||||
'dubwise.dubsperformanceanalyzer.steam': makeDummy().copyWith(
|
||||
id: 'dubwise.dubsperformanceanalyzer.steam',
|
||||
name: 'Dubs Performance Analyzer',
|
||||
path:
|
||||
'C:/Users/Administrator/Seafile/Games-RimWorld/294100\\2038874626',
|
||||
dependencies: [],
|
||||
loadAfter: [],
|
||||
loadBefore: [],
|
||||
incompatibilities: [],
|
||||
size: 570,
|
||||
isBaseGame: false,
|
||||
isExpansion: false,
|
||||
),
|
||||
'memegoddess.searchanddestroy': makeDummy().copyWith(
|
||||
id: 'memegoddess.searchanddestroy',
|
||||
name: 'Search and Destroy (Unofficial Update)',
|
||||
path:
|
||||
'C:/Users/Administrator/Seafile/Games-RimWorld/294100\\3232242247',
|
||||
dependencies: [],
|
||||
loadAfter: [],
|
||||
loadBefore: [],
|
||||
incompatibilities: [],
|
||||
size: 495,
|
||||
isBaseGame: false,
|
||||
isExpansion: false,
|
||||
),
|
||||
'gogatio.mechanoidupgrades': makeDummy().copyWith(
|
||||
id: 'gogatio.mechanoidupgrades',
|
||||
name: 'Mechanoid Upgrades',
|
||||
path:
|
||||
'C:/Users/Administrator/Seafile/Games-RimWorld/294100\\3365118555',
|
||||
dependencies: [],
|
||||
loadAfter: [],
|
||||
loadBefore: [],
|
||||
incompatibilities: [],
|
||||
size: 487,
|
||||
isBaseGame: false,
|
||||
isExpansion: false,
|
||||
),
|
||||
'issaczhuang.muzzleflash': makeDummy().copyWith(
|
||||
id: 'issaczhuang.muzzleflash',
|
||||
name: 'Muzzle Flash',
|
||||
path:
|
||||
'C:/Users/Administrator/Seafile/Games-RimWorld/294100\\2917732219',
|
||||
dependencies: [],
|
||||
loadAfter: ['ludeon.rimworld'],
|
||||
loadBefore: [],
|
||||
incompatibilities: [],
|
||||
size: 431,
|
||||
isBaseGame: false,
|
||||
isExpansion: false,
|
||||
),
|
||||
'smashphil.vehicleframework': makeDummy().copyWith(
|
||||
id: 'smashphil.vehicleframework',
|
||||
name: 'Vehicle Framework',
|
||||
path:
|
||||
'C:/Users/Administrator/Seafile/Games-RimWorld/294100\\3014915404',
|
||||
dependencies: [],
|
||||
loadAfter: ['brrainz.harmony'],
|
||||
loadBefore: [],
|
||||
incompatibilities: [],
|
||||
size: 426,
|
||||
isBaseGame: false,
|
||||
isExpansion: false,
|
||||
),
|
||||
'cabbage.rimcities': makeDummy().copyWith(
|
||||
id: 'cabbage.rimcities',
|
||||
name: 'RimCities',
|
||||
path:
|
||||
'C:/Users/Administrator/Seafile/Games-RimWorld/294100\\1775170117',
|
||||
dependencies: [],
|
||||
loadAfter: [],
|
||||
loadBefore: [],
|
||||
incompatibilities: [],
|
||||
size: 421,
|
||||
isBaseGame: false,
|
||||
isExpansion: false,
|
||||
),
|
||||
'vis.staticquality': makeDummy().copyWith(
|
||||
id: 'vis.staticquality',
|
||||
name: 'Static Quality',
|
||||
path:
|
||||
'C:/Users/Administrator/Seafile/Games-RimWorld/294100\\2801204005',
|
||||
dependencies: [],
|
||||
loadAfter: [],
|
||||
loadBefore: [],
|
||||
incompatibilities: [],
|
||||
size: 385,
|
||||
isBaseGame: false,
|
||||
isExpansion: false,
|
||||
),
|
||||
'automatic.bionicicons': makeDummy().copyWith(
|
||||
id: 'automatic.bionicicons',
|
||||
name: 'Bionic icons',
|
||||
path:
|
||||
'C:/Users/Administrator/Seafile/Games-RimWorld/294100\\1677616980',
|
||||
dependencies: [],
|
||||
loadAfter: [],
|
||||
loadBefore: [],
|
||||
incompatibilities: [],
|
||||
size: 365,
|
||||
isBaseGame: false,
|
||||
isExpansion: false,
|
||||
),
|
||||
'vanillaexpanded.vanillatraitsexpanded': makeDummy().copyWith(
|
||||
id: 'vanillaexpanded.vanillatraitsexpanded',
|
||||
name: 'Vanilla Traits Expanded',
|
||||
path:
|
||||
'C:/Users/Administrator/Seafile/Games-RimWorld/294100\\2296404655',
|
||||
dependencies: [],
|
||||
loadAfter: [],
|
||||
loadBefore: [],
|
||||
incompatibilities: [],
|
||||
size: 338,
|
||||
isBaseGame: false,
|
||||
isExpansion: false,
|
||||
),
|
||||
'tk421storm.ragdoll': makeDummy().copyWith(
|
||||
id: 'tk421storm.ragdoll',
|
||||
name: 'Ragdoll Physics',
|
||||
path:
|
||||
'C:/Users/Administrator/Seafile/Games-RimWorld/294100\\3179116177',
|
||||
dependencies: [],
|
||||
loadAfter: [],
|
||||
loadBefore: [],
|
||||
incompatibilities: [],
|
||||
size: 329,
|
||||
isBaseGame: false,
|
||||
isExpansion: false,
|
||||
),
|
||||
'andromeda.nicehealthtab': makeDummy().copyWith(
|
||||
id: 'andromeda.nicehealthtab',
|
||||
name: 'Nice Health Tab',
|
||||
path:
|
||||
'C:/Users/Administrator/Seafile/Games-RimWorld/294100\\3328729902',
|
||||
dependencies: [],
|
||||
loadAfter: [],
|
||||
loadBefore: [],
|
||||
incompatibilities: [],
|
||||
size: 319,
|
||||
isBaseGame: false,
|
||||
isExpansion: false,
|
||||
),
|
||||
'ludeon.rimworld.anomaly': makeDummy().copyWith(
|
||||
id: 'ludeon.rimworld.anomaly',
|
||||
name: 'RimWorld Anomaly',
|
||||
path: '',
|
||||
dependencies: [],
|
||||
loadAfter: ['ludeon.rimworld'],
|
||||
loadBefore: [],
|
||||
incompatibilities: [],
|
||||
size: 0,
|
||||
isBaseGame: false,
|
||||
isExpansion: true,
|
||||
),
|
||||
'ludeon.rimworld.biotech': makeDummy().copyWith(
|
||||
id: 'ludeon.rimworld.biotech',
|
||||
name: 'RimWorld Biotech',
|
||||
path: '',
|
||||
dependencies: [],
|
||||
loadAfter: ['ludeon.rimworld'],
|
||||
loadBefore: [],
|
||||
incompatibilities: [],
|
||||
size: 0,
|
||||
isBaseGame: false,
|
||||
isExpansion: true,
|
||||
),
|
||||
'ludeon.rimworld.ideology': makeDummy().copyWith(
|
||||
id: 'ludeon.rimworld.ideology',
|
||||
name: 'RimWorld Ideology',
|
||||
path: '',
|
||||
dependencies: [],
|
||||
loadAfter: ['ludeon.rimworld'],
|
||||
loadBefore: [],
|
||||
incompatibilities: [],
|
||||
size: 0,
|
||||
isBaseGame: false,
|
||||
isExpansion: true,
|
||||
),
|
||||
'ludeon.rimworld.royalty': makeDummy().copyWith(
|
||||
id: 'ludeon.rimworld.royalty',
|
||||
name: 'RimWorld Royalty',
|
||||
path: '',
|
||||
dependencies: [],
|
||||
loadAfter: ['ludeon.rimworld'],
|
||||
loadBefore: [],
|
||||
incompatibilities: [],
|
||||
size: 0,
|
||||
isBaseGame: false,
|
||||
isExpansion: true,
|
||||
),
|
||||
};
|
||||
list.enableAll();
|
||||
final order = list.generateLoadOrder();
|
||||
final expected = [
|
||||
'zetrith.prepatcher',
|
||||
'brrainz.harmony',
|
||||
@@ -308,6 +716,28 @@ void main() {
|
||||
'ludeon.rimworld.biotech',
|
||||
'ludeon.rimworld.ideology',
|
||||
'ludeon.rimworld.royalty',
|
||||
'rah.rbse',
|
||||
'mlie.usethisinstead',
|
||||
'dubwise.rimatomics',
|
||||
'jecrell.doorsexpanded',
|
||||
'balistafreak.stopdropandroll',
|
||||
'fluffy.animaltab',
|
||||
'gt.sam.glittertech',
|
||||
'dubwise.rimefeller',
|
||||
'darthcy.misc.morebetterdeepdrill',
|
||||
'haplo.miscellaneous.training',
|
||||
'linkolas.stabilize',
|
||||
'dubwise.dubsperformanceanalyzer.steam',
|
||||
'memegoddess.searchanddestroy',
|
||||
'gogatio.mechanoidupgrades',
|
||||
'issaczhuang.muzzleflash',
|
||||
'smashphil.vehicleframework',
|
||||
'cabbage.rimcities',
|
||||
'vis.staticquality',
|
||||
'automatic.bionicicons',
|
||||
'vanillaexpanded.vanillatraitsexpanded',
|
||||
'tk421storm.ragdoll',
|
||||
'andromeda.nicehealthtab',
|
||||
];
|
||||
expect(order.loadOrder, equals(expected));
|
||||
});
|
||||
|
||||
@@ -245,7 +245,7 @@ void main() {
|
||||
final result = list.loadRequired();
|
||||
|
||||
// We say the mods are incompatible but load them anyway, who are we to decide what isn't loaded?
|
||||
final expected = ['incompatible', 'harmony', 'prepatcher'];
|
||||
final expected = ['harmony', 'prepatcher', 'incompatible'];
|
||||
expect(result.errors, isNotEmpty);
|
||||
expect(result.errors.any((e) => e.contains('incompatible')), isTrue);
|
||||
expect(result.errors.any((e) => e.contains('harmony')), isTrue);
|
||||
@@ -302,7 +302,7 @@ void main() {
|
||||
|
||||
// We try to not disable mods...... But cyclic dependencies are just hell
|
||||
// Can not handle it
|
||||
final expected = ['modB', 'modA', 'modC'];
|
||||
final expected = [];
|
||||
expect(result.errors, isNotEmpty);
|
||||
expect(result.errors.any((e) => e.contains('modA')), isTrue);
|
||||
expect(result.errors.any((e) => e.contains('modB')), isTrue);
|
||||
@@ -487,7 +487,7 @@ void main() {
|
||||
list.enableAll();
|
||||
|
||||
final result = list.generateLoadOrder();
|
||||
final expected = ['modA', 'modB'];
|
||||
final expected = ['modB', 'modA'];
|
||||
expect(result.errors, isNotEmpty);
|
||||
expect(result.errors.any((e) => e.contains('missing1')), isTrue);
|
||||
expect(result.errors.any((e) => e.contains('missing2')), isTrue);
|
||||
@@ -548,7 +548,7 @@ void main() {
|
||||
list.enableAll();
|
||||
|
||||
final result = list.generateLoadOrder();
|
||||
final expected = ['modA', 'modB'];
|
||||
final expected = ['modB', 'modA'];
|
||||
expect(result.errors, isNotEmpty);
|
||||
expect(result.errors.any((e) => e.contains('incompatible')), isTrue);
|
||||
expect(result.errors.any((e) => e.contains('modA')), isTrue);
|
||||
@@ -570,7 +570,7 @@ void main() {
|
||||
list.enableAll();
|
||||
|
||||
final result = list.generateLoadOrder();
|
||||
final expected = ['modA', 'modB'];
|
||||
final expected = ['modB', 'modA'];
|
||||
expect(result.errors, isNotEmpty);
|
||||
expect(result.errors.any((e) => e.contains('missingDep')), isTrue);
|
||||
expect(result.errors.any((e) => e.contains('incompatible')), isTrue);
|
||||
@@ -653,7 +653,7 @@ void main() {
|
||||
list.enableAll();
|
||||
|
||||
final result = list.generateLoadOrder();
|
||||
final expected = ['modA', 'modB'];
|
||||
final expected = ['modB', 'modA'];
|
||||
|
||||
expect(result.errors, isEmpty);
|
||||
expect(result.loadOrder, equals(expected));
|
||||
@@ -691,7 +691,7 @@ void main() {
|
||||
list.enableAll();
|
||||
|
||||
final result = list.generateLoadOrder();
|
||||
final expected = ['modA', 'modB'];
|
||||
final expected = ['modB', 'modA'];
|
||||
|
||||
expect(result.errors, isNotEmpty);
|
||||
expect(result.errors.any((e) => e.contains('nonExistentMod')), isTrue);
|
||||
@@ -778,15 +778,11 @@ void main() {
|
||||
final result = list.generateLoadOrder();
|
||||
final expected = ['existingMod', 'modA'];
|
||||
|
||||
|
||||
// Should still generatdeequals(mopected)
|
||||
expect(result.loadOrder.contains('existingMod'), isTrue);
|
||||
|
||||
// The existing loadAfter relationship should be honored
|
||||
expect(
|
||||
result.loadOrder.indexOf('existingMod'),
|
||||
lessThan(result.loadOrder.indexOf('modA')),
|
||||
);
|
||||
expect(result.loadOrder, equals(expected));
|
||||
|
||||
// System should track or report the missing loadAfter targets
|
||||
expect(result.errors, isEmpty); // Soft constraints shouldn't cause errors
|
||||
@@ -809,7 +805,8 @@ void main() {
|
||||
};
|
||||
list.enableAll();
|
||||
|
||||
final expected = ['modA']; final result = list.generateLoadOrder();
|
||||
final expected = ['modA'];
|
||||
final result = list.generateLoadOrder();
|
||||
|
||||
// Should report the missing dependency
|
||||
expect(result.errors, isNotEmpty);
|
||||
@@ -822,8 +819,7 @@ void main() {
|
||||
isFalse,
|
||||
);
|
||||
|
||||
// Mod should still be , equals(expected)pendencies
|
||||
expect(result.loadOrder.contains('modA'), isTrue);
|
||||
expect(result.loadOrder, equals(expected));
|
||||
},
|
||||
);
|
||||
|
||||
@@ -852,7 +848,7 @@ void main() {
|
||||
list.enableAll();
|
||||
|
||||
final result = list.generateLoadOrder();
|
||||
final expected = ['modB', 'modC', 'modA'];
|
||||
final expected = ['modC', 'modB', 'modA'];
|
||||
|
||||
// Should report all missing dependencies
|
||||
expect(result.errors, isNotEmpty);
|
||||
|
||||
@@ -691,52 +691,41 @@ void main() {
|
||||
test('Should not fuck up troubleshooter', () {
|
||||
final troubleshooter = ModListTroubleshooter(modList);
|
||||
final expectedFirst = [
|
||||
'test.mod1',
|
||||
// 0 depends on 1
|
||||
'test.mod0',
|
||||
'test.mod10',
|
||||
'test.mod9',
|
||||
'test.mod8',
|
||||
'test.mod2',
|
||||
// 3 depends on 4
|
||||
'test.mod4',
|
||||
'test.mod3',
|
||||
'test.mod5',
|
||||
// 6 depends on 7
|
||||
'test.mod7',
|
||||
'test.mod6',
|
||||
'test.mod8',
|
||||
// 9 depends on 10
|
||||
'test.mod10',
|
||||
'test.mod9',
|
||||
'test.mod1',
|
||||
'test.mod0',
|
||||
];
|
||||
|
||||
var result = troubleshooter.linearForward(stepSize: 10);
|
||||
var loadOrder = result.loadRequired();
|
||||
expect(loadOrder.loadOrder.length, equals(11));
|
||||
for (int i = 0; i < expectedFirst.length; i++) {
|
||||
expect(loadOrder.loadOrder[i], equals(expectedFirst[i]));
|
||||
}
|
||||
expect(loadOrder.loadOrder, equals(expectedFirst));
|
||||
|
||||
final expectedSecond = [
|
||||
'test.mod10',
|
||||
'test.mod19',
|
||||
'test.mod18',
|
||||
'test.mod17',
|
||||
'test.mod11',
|
||||
// 12 depends on 13
|
||||
'test.mod13',
|
||||
'test.mod12',
|
||||
'test.mod14',
|
||||
// 15 depends on 16
|
||||
'test.mod16',
|
||||
'test.mod15',
|
||||
'test.mod17',
|
||||
// 18 depends on 19
|
||||
'test.mod19',
|
||||
'test.mod18',
|
||||
'test.mod10',
|
||||
];
|
||||
|
||||
result = troubleshooter.linearForward(stepSize: 10);
|
||||
loadOrder = result.loadRequired();
|
||||
expect(loadOrder.loadOrder.length, equals(10));
|
||||
for (int i = 0; i < expectedSecond.length; i++) {
|
||||
expect(loadOrder.loadOrder[i], equals(expectedSecond[i]));
|
||||
}
|
||||
expect(loadOrder.loadOrder, equals(expectedSecond));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user