Fix hard dependency loading
This commit is contained in:
@@ -1,7 +1,6 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:xml/xml.dart';
|
import 'package:xml/xml.dart';
|
||||||
import 'package:path/path.dart' as path;
|
|
||||||
|
|
||||||
const root = r'C:/Users/Administrator/Seafile/Games-Rimworld';
|
const root = r'C:/Users/Administrator/Seafile/Games-Rimworld';
|
||||||
const modsRoot = '$root/294100';
|
const modsRoot = '$root/294100';
|
||||||
@@ -13,37 +12,36 @@ const logsPath = '$root/ModManager';
|
|||||||
class Logger {
|
class Logger {
|
||||||
static final Logger _instance = Logger._internal();
|
static final Logger _instance = Logger._internal();
|
||||||
static Logger get instance => _instance;
|
static Logger get instance => _instance;
|
||||||
|
|
||||||
File? _logFile;
|
File? _logFile;
|
||||||
IOSink? _logSink;
|
IOSink? _logSink;
|
||||||
|
|
||||||
Logger._internal() {
|
Logger._internal() {
|
||||||
_initLogFile();
|
_initLogFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _initLogFile() {
|
void _initLogFile() {
|
||||||
try {
|
try {
|
||||||
// Use system temp directory
|
// Use system temp directory
|
||||||
final tempDir = Directory.systemTemp;
|
final tempDir = Directory.systemTemp;
|
||||||
final timestamp = DateTime.now().toIso8601String().replaceAll(':', '-').substring(0, 19);
|
final logFileName = 'rimworld_modman.log';
|
||||||
final logFileName = 'rimworld_modman_$timestamp.log';
|
|
||||||
|
|
||||||
_logFile = File('${tempDir.path}${Platform.pathSeparator}$logFileName');
|
_logFile = File('${tempDir.path}${Platform.pathSeparator}$logFileName');
|
||||||
_logSink = _logFile!.openWrite(mode: FileMode.writeOnly);
|
_logSink = _logFile!.openWrite(mode: FileMode.writeOnly);
|
||||||
|
|
||||||
info('Logging initialized. Log file: ${_logFile!.path}');
|
info('Logging initialized. Log file: ${_logFile!.path}');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Failed to initialize log file: $e');
|
print('Failed to initialize log file: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _log(String message, String level) {
|
void _log(String message, String level) {
|
||||||
final timestamp = DateTime.now().toIso8601String();
|
final timestamp = DateTime.now().toIso8601String();
|
||||||
final formattedMessage = '[$timestamp] [$level] $message';
|
final formattedMessage = '[$timestamp] [$level] $message';
|
||||||
|
|
||||||
// Always print to console
|
// Always print to console
|
||||||
print(formattedMessage);
|
print(formattedMessage);
|
||||||
|
|
||||||
// Write to file if initialized
|
// Write to file if initialized
|
||||||
if (_logSink != null) {
|
if (_logSink != null) {
|
||||||
try {
|
try {
|
||||||
@@ -53,19 +51,19 @@ class Logger {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void info(String message) {
|
void info(String message) {
|
||||||
_log(message, 'INFO');
|
_log(message, 'INFO');
|
||||||
}
|
}
|
||||||
|
|
||||||
void warning(String message) {
|
void warning(String message) {
|
||||||
_log(message, 'WARN');
|
_log(message, 'WARN');
|
||||||
}
|
}
|
||||||
|
|
||||||
void error(String message) {
|
void error(String message) {
|
||||||
_log(message, 'ERROR');
|
_log(message, 'ERROR');
|
||||||
}
|
}
|
||||||
|
|
||||||
void close() {
|
void close() {
|
||||||
if (_logSink != null) {
|
if (_logSink != null) {
|
||||||
try {
|
try {
|
||||||
@@ -102,7 +100,8 @@ class Mod {
|
|||||||
final List<String> loadAfter; // ModMetaData.loadAfter
|
final List<String> loadAfter; // ModMetaData.loadAfter
|
||||||
final List<String> loadBefore; // ModMetaData.loadBefore
|
final List<String> loadBefore; // ModMetaData.loadBefore
|
||||||
final List<String> incompatabilities; // ModMetaData.incompatibleWith
|
final List<String> incompatabilities; // ModMetaData.incompatibleWith
|
||||||
final bool enabled; // ConfigFile.mods.firstWhere((mod) => mod.id == id).enabled
|
final bool
|
||||||
|
enabled; // ConfigFile.mods.firstWhere((mod) => mod.id == id).enabled
|
||||||
final int size; // Count of files in the mod directory
|
final int size; // Count of files in the mod directory
|
||||||
final bool isBaseGame; // Is this the base RimWorld game
|
final bool isBaseGame; // Is this the base RimWorld game
|
||||||
final bool isExpansion; // Is this a RimWorld expansion
|
final bool isExpansion; // Is this a RimWorld expansion
|
||||||
@@ -127,18 +126,25 @@ class Mod {
|
|||||||
final logger = Logger.instance;
|
final logger = Logger.instance;
|
||||||
final stopwatch = Stopwatch()..start();
|
final stopwatch = Stopwatch()..start();
|
||||||
|
|
||||||
|
logger.info('Attempting to load mod from directory: $path');
|
||||||
final aboutFile = File('$path/About/About.xml');
|
final aboutFile = File('$path/About/About.xml');
|
||||||
if (!aboutFile.existsSync()) {
|
if (!aboutFile.existsSync()) {
|
||||||
|
logger.error('About.xml file does not exist in $aboutFile');
|
||||||
throw Exception('About.xml file does not exist in $aboutFile');
|
throw Exception('About.xml file does not exist in $aboutFile');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.info('Parsing About.xml file...');
|
||||||
final aboutXml = XmlDocument.parse(aboutFile.readAsStringSync());
|
final aboutXml = XmlDocument.parse(aboutFile.readAsStringSync());
|
||||||
final xmlTime = stopwatch.elapsedMilliseconds;
|
final xmlTime = stopwatch.elapsedMilliseconds;
|
||||||
|
|
||||||
late final XmlElement metadata;
|
late final XmlElement metadata;
|
||||||
try {
|
try {
|
||||||
metadata = findCaseInsensitiveDoc(aboutXml, 'ModMetaData');
|
metadata = findCaseInsensitiveDoc(aboutXml, 'ModMetaData');
|
||||||
|
logger.info('Successfully found ModMetaData in About.xml');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
logger.error(
|
||||||
|
'Error: ModMetaData element is missing in About.xml ($aboutFile). Original error: $e',
|
||||||
|
);
|
||||||
throw Exception(
|
throw Exception(
|
||||||
'Error: ModMetaData element is missing in About.xml ($aboutFile). Original error: $e',
|
'Error: ModMetaData element is missing in About.xml ($aboutFile). Original error: $e',
|
||||||
);
|
);
|
||||||
@@ -147,7 +153,11 @@ class Mod {
|
|||||||
late final String name;
|
late final String name;
|
||||||
try {
|
try {
|
||||||
name = metadata.findElements('name').first.innerText;
|
name = metadata.findElements('name').first.innerText;
|
||||||
|
logger.info('Mod name found: $name');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
logger.error(
|
||||||
|
'Error: name element is missing in ModMetaData ($aboutFile). Original error: $e',
|
||||||
|
);
|
||||||
throw Exception(
|
throw Exception(
|
||||||
'Error: name element is missing in ModMetaData ($aboutFile). Original error: $e',
|
'Error: name element is missing in ModMetaData ($aboutFile). Original error: $e',
|
||||||
);
|
);
|
||||||
@@ -156,7 +166,11 @@ class Mod {
|
|||||||
late final String id;
|
late final String id;
|
||||||
try {
|
try {
|
||||||
id = metadata.findElements('packageId').first.innerText.toLowerCase();
|
id = metadata.findElements('packageId').first.innerText.toLowerCase();
|
||||||
|
logger.info('Mod ID found: $id');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
logger.error(
|
||||||
|
'Error: packageId element is missing in ModMetaData ($aboutFile). Original error: $e',
|
||||||
|
);
|
||||||
throw Exception(
|
throw Exception(
|
||||||
'Error: packageId element is missing in ModMetaData ($aboutFile). Original error: $e',
|
'Error: packageId element is missing in ModMetaData ($aboutFile). Original error: $e',
|
||||||
);
|
);
|
||||||
@@ -171,7 +185,11 @@ class Mod {
|
|||||||
.findElements('li')
|
.findElements('li')
|
||||||
.map((e) => e.innerText)
|
.map((e) => e.innerText)
|
||||||
.toList();
|
.toList();
|
||||||
|
logger.info('Supported versions found: ${versions.join(", ")}');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
logger.error(
|
||||||
|
'Error: supportedVersions or li elements are missing in ModMetaData ($aboutFile). Original error: $e',
|
||||||
|
);
|
||||||
throw Exception(
|
throw Exception(
|
||||||
'Error: supportedVersions or li elements are missing in ModMetaData ($aboutFile). Original error: $e',
|
'Error: supportedVersions or li elements are missing in ModMetaData ($aboutFile). Original error: $e',
|
||||||
);
|
);
|
||||||
@@ -180,8 +198,11 @@ class Mod {
|
|||||||
String description = '';
|
String description = '';
|
||||||
try {
|
try {
|
||||||
description = metadata.findElements('description').first.innerText;
|
description = metadata.findElements('description').first.innerText;
|
||||||
|
logger.info('Mod description found: $description');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Silent error for optional element
|
logger.warning(
|
||||||
|
'Description element is missing in ModMetaData ($aboutFile).',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> hardDependencies = [];
|
List<String> hardDependencies = [];
|
||||||
@@ -189,15 +210,21 @@ class Mod {
|
|||||||
hardDependencies =
|
hardDependencies =
|
||||||
metadata
|
metadata
|
||||||
.findElements('modDependenciesByVersion')
|
.findElements('modDependenciesByVersion')
|
||||||
|
.first
|
||||||
|
.children
|
||||||
|
.whereType<XmlElement>()
|
||||||
.last
|
.last
|
||||||
.findElements('li')
|
.findElements('li')
|
||||||
.map(
|
.map(
|
||||||
(e) =>
|
(e) =>
|
||||||
e.findElements('packageId').first.innerText.toLowerCase(),
|
e.findElements("packageId").first.innerText.toLowerCase(),
|
||||||
)
|
)
|
||||||
.toList();
|
.toList();
|
||||||
|
logger.info('Hard dependencies found: ${hardDependencies.join(", ")}');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Silent error for optional element
|
logger.warning(
|
||||||
|
'Hard dependencies element is missing in ModMetaData ($aboutFile).',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> loadAfter = [];
|
List<String> loadAfter = [];
|
||||||
@@ -209,8 +236,11 @@ class Mod {
|
|||||||
.findElements('li')
|
.findElements('li')
|
||||||
.map((e) => e.innerText.toLowerCase())
|
.map((e) => e.innerText.toLowerCase())
|
||||||
.toList();
|
.toList();
|
||||||
|
logger.info('Load after dependencies found: ${loadAfter.join(", ")}');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Silent error for optional element
|
logger.warning(
|
||||||
|
'Load after element is missing in ModMetaData ($aboutFile).',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> loadBefore = [];
|
List<String> loadBefore = [];
|
||||||
@@ -222,8 +252,11 @@ class Mod {
|
|||||||
.findElements('li')
|
.findElements('li')
|
||||||
.map((e) => e.innerText.toLowerCase())
|
.map((e) => e.innerText.toLowerCase())
|
||||||
.toList();
|
.toList();
|
||||||
|
logger.info('Load before dependencies found: ${loadBefore.join(", ")}');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Silent error for optional element
|
logger.warning(
|
||||||
|
'Load before element is missing in ModMetaData ($aboutFile).',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> incompatabilities = [];
|
List<String> incompatabilities = [];
|
||||||
@@ -235,8 +268,11 @@ class Mod {
|
|||||||
.findElements('li')
|
.findElements('li')
|
||||||
.map((e) => e.innerText.toLowerCase())
|
.map((e) => e.innerText.toLowerCase())
|
||||||
.toList();
|
.toList();
|
||||||
|
logger.info('Incompatibilities found: ${incompatabilities.join(", ")}');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Silent error for optional element
|
logger.warning(
|
||||||
|
'Incompatibilities element is missing in ModMetaData ($aboutFile).',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final metadataTime = stopwatch.elapsedMilliseconds - xmlTime;
|
final metadataTime = stopwatch.elapsedMilliseconds - xmlTime;
|
||||||
@@ -254,6 +290,7 @@ class Mod {
|
|||||||
.startsWith('.'),
|
.startsWith('.'),
|
||||||
)
|
)
|
||||||
.length;
|
.length;
|
||||||
|
logger.info('File count in mod directory: $size');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if this is RimWorld base game or expansion
|
// Check if this is RimWorld base game or expansion
|
||||||
@@ -263,6 +300,9 @@ class Mod {
|
|||||||
// If this is an expansion, ensure it depends on the base game
|
// If this is an expansion, ensure it depends on the base game
|
||||||
if (isExpansion && !loadAfter.contains('ludeon.rimworld')) {
|
if (isExpansion && !loadAfter.contains('ludeon.rimworld')) {
|
||||||
loadAfter.add('ludeon.rimworld');
|
loadAfter.add('ludeon.rimworld');
|
||||||
|
logger.info(
|
||||||
|
'Added base game dependency for expansion mod: ludeon.rimworld',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final fileCountTime =
|
final fileCountTime =
|
||||||
@@ -305,16 +345,17 @@ class ModList {
|
|||||||
// Simplified loading with config file first
|
// Simplified loading with config file first
|
||||||
Future<void> loadWithConfig({bool skipFileCount = false}) async {
|
Future<void> loadWithConfig({bool skipFileCount = false}) async {
|
||||||
final logger = Logger.instance;
|
final logger = Logger.instance;
|
||||||
|
|
||||||
// Clear existing state if reloading
|
// Clear existing state if reloading
|
||||||
if (modsLoaded) {
|
if (modsLoaded) {
|
||||||
|
logger.info('Clearing existing mods state for reload.');
|
||||||
mods.clear();
|
mods.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
modsLoaded = false;
|
modsLoaded = false;
|
||||||
loadedModsCount = 0;
|
loadedModsCount = 0;
|
||||||
loadingStatus = 'Loading active mods from config...';
|
loadingStatus = 'Loading active mods from config...';
|
||||||
|
|
||||||
final stopwatch = Stopwatch()..start();
|
final stopwatch = Stopwatch()..start();
|
||||||
logger.info('Loading configuration from config file: $configPath');
|
logger.info('Loading configuration from config file: $configPath');
|
||||||
|
|
||||||
@@ -322,25 +363,36 @@ class ModList {
|
|||||||
// First, load the config file to get the list of active mods
|
// First, load the config file to get the list of active mods
|
||||||
final configFile = ConfigFile(path: configPath);
|
final configFile = ConfigFile(path: configPath);
|
||||||
await configFile.load();
|
await configFile.load();
|
||||||
|
logger.info('Config file loaded successfully.');
|
||||||
|
|
||||||
// Create a Set of active mod IDs for quick lookups
|
// Create a Set of active mod IDs for quick lookups
|
||||||
final activeModIds = configFile.mods.map((m) => m.id).toSet();
|
final activeModIds = configFile.mods.map((m) => m.id).toSet();
|
||||||
|
logger.info('Active mod IDs created: ${activeModIds.join(', ')}');
|
||||||
|
|
||||||
// Special handling for Ludeon mods that might not exist as directories
|
// Special handling for Ludeon mods that might not exist as directories
|
||||||
for (final configMod in configFile.mods) {
|
for (final configMod in configFile.mods) {
|
||||||
if (configMod.id.startsWith('ludeon.')) {
|
if (configMod.id.startsWith('ludeon.')) {
|
||||||
final isBaseGame = configMod.id == 'ludeon.rimworld';
|
final isBaseGame = configMod.id == 'ludeon.rimworld';
|
||||||
final isExpansion = configMod.id.startsWith('ludeon.rimworld.') && !isBaseGame;
|
final isExpansion =
|
||||||
|
configMod.id.startsWith('ludeon.rimworld.') && !isBaseGame;
|
||||||
|
|
||||||
// Create a placeholder mod for the Ludeon mods that might not have directories
|
// Create a placeholder mod for the Ludeon mods that might not have directories
|
||||||
final mod = Mod(
|
final mod = Mod(
|
||||||
name: isBaseGame ? "RimWorld" :
|
name:
|
||||||
isExpansion ? "RimWorld ${_expansionNameFromId(configMod.id)}" : configMod.id,
|
isBaseGame
|
||||||
|
? "RimWorld"
|
||||||
|
: isExpansion
|
||||||
|
? "RimWorld ${_expansionNameFromId(configMod.id)}"
|
||||||
|
: configMod.id,
|
||||||
id: configMod.id,
|
id: configMod.id,
|
||||||
path: '',
|
path: '',
|
||||||
versions: [],
|
versions: [],
|
||||||
description: isBaseGame ? "RimWorld base game" :
|
description:
|
||||||
isExpansion ? "RimWorld expansion" : "",
|
isBaseGame
|
||||||
|
? "RimWorld base game"
|
||||||
|
: isExpansion
|
||||||
|
? "RimWorld expansion"
|
||||||
|
: "",
|
||||||
hardDependencies: [],
|
hardDependencies: [],
|
||||||
loadAfter: isExpansion ? ['ludeon.rimworld'] : [],
|
loadAfter: isExpansion ? ['ludeon.rimworld'] : [],
|
||||||
loadBefore: [],
|
loadBefore: [],
|
||||||
@@ -350,40 +402,47 @@ class ModList {
|
|||||||
isBaseGame: isBaseGame,
|
isBaseGame: isBaseGame,
|
||||||
isExpansion: isExpansion,
|
isExpansion: isExpansion,
|
||||||
);
|
);
|
||||||
|
|
||||||
mods[configMod.id] = mod;
|
mods[configMod.id] = mod;
|
||||||
loadedModsCount++;
|
loadedModsCount++;
|
||||||
|
logger.info('Added mod from config: ${mod.name} (ID: ${mod.id})');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now scan the directory for mod metadata
|
// Now scan the directory for mod metadata
|
||||||
loadingStatus = 'Scanning mod directories...';
|
loadingStatus = 'Scanning mod directories...';
|
||||||
final directory = Directory(path);
|
final directory = Directory(path);
|
||||||
|
|
||||||
if (!directory.existsSync()) {
|
if (!directory.existsSync()) {
|
||||||
loadingStatus = 'Error: Mods root directory does not exist: $path';
|
loadingStatus = 'Error: Mods root directory does not exist: $path';
|
||||||
logger.error(loadingStatus);
|
logger.error(loadingStatus);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<FileSystemEntity> entities = directory.listSync();
|
final List<FileSystemEntity> entities = directory.listSync();
|
||||||
final List<String> modDirectories =
|
final List<String> modDirectories =
|
||||||
entities.whereType<Directory>().map((dir) => dir.path).toList();
|
entities.whereType<Directory>().map((dir) => dir.path).toList();
|
||||||
|
|
||||||
totalModsFound = modDirectories.length;
|
totalModsFound = modDirectories.length;
|
||||||
loadingStatus = 'Found $totalModsFound mod directories. Loading...';
|
loadingStatus = 'Found $totalModsFound mod directories. Loading...';
|
||||||
logger.info('Found ${modDirectories.length} mod directories (${stopwatch.elapsedMilliseconds}ms)');
|
logger.info(
|
||||||
|
'Found ${modDirectories.length} mod directories (${stopwatch.elapsedMilliseconds}ms)',
|
||||||
|
);
|
||||||
|
|
||||||
for (final modDir in modDirectories) {
|
for (final modDir in modDirectories) {
|
||||||
try {
|
try {
|
||||||
final modStart = stopwatch.elapsedMilliseconds;
|
final modStart = stopwatch.elapsedMilliseconds;
|
||||||
|
|
||||||
// Check if this directory contains a valid mod
|
// Check if this directory contains a valid mod
|
||||||
final aboutFile = File('$modDir/About/About.xml');
|
final aboutFile = File('$modDir/About/About.xml');
|
||||||
if (!aboutFile.existsSync()) continue;
|
if (!aboutFile.existsSync()) {
|
||||||
|
logger.warning('No About.xml found in directory: $modDir');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
final mod = Mod.fromDirectory(modDir, skipFileCount: skipFileCount);
|
final mod = Mod.fromDirectory(modDir, skipFileCount: skipFileCount);
|
||||||
|
logger.info('Loaded mod from directory: ${mod.name} (ID: ${mod.id})');
|
||||||
|
|
||||||
// If we already have this mod from the config (like Ludeon mods), update its data
|
// If we already have this mod from the config (like Ludeon mods), update its data
|
||||||
if (mods.containsKey(mod.id)) {
|
if (mods.containsKey(mod.id)) {
|
||||||
final existingMod = mods[mod.id]!;
|
final existingMod = mods[mod.id]!;
|
||||||
@@ -397,11 +456,14 @@ class ModList {
|
|||||||
loadAfter: mod.loadAfter,
|
loadAfter: mod.loadAfter,
|
||||||
loadBefore: mod.loadBefore,
|
loadBefore: mod.loadBefore,
|
||||||
incompatabilities: mod.incompatabilities,
|
incompatabilities: mod.incompatabilities,
|
||||||
enabled: activeModIds.contains(mod.id), // Set enabled based on config
|
enabled: activeModIds.contains(
|
||||||
|
mod.id,
|
||||||
|
), // Set enabled based on config
|
||||||
size: mod.size,
|
size: mod.size,
|
||||||
isBaseGame: existingMod.isBaseGame,
|
isBaseGame: existingMod.isBaseGame,
|
||||||
isExpansion: existingMod.isExpansion,
|
isExpansion: existingMod.isExpansion,
|
||||||
);
|
);
|
||||||
|
logger.info('Updated existing mod: ${mod.name} (ID: ${mod.id})');
|
||||||
} else {
|
} else {
|
||||||
// Otherwise add as a new mod
|
// Otherwise add as a new mod
|
||||||
mods[mod.id] = Mod(
|
mods[mod.id] = Mod(
|
||||||
@@ -414,43 +476,52 @@ class ModList {
|
|||||||
loadAfter: mod.loadAfter,
|
loadAfter: mod.loadAfter,
|
||||||
loadBefore: mod.loadBefore,
|
loadBefore: mod.loadBefore,
|
||||||
incompatabilities: mod.incompatabilities,
|
incompatabilities: mod.incompatabilities,
|
||||||
enabled: activeModIds.contains(mod.id), // Set enabled based on config
|
enabled: activeModIds.contains(
|
||||||
|
mod.id,
|
||||||
|
), // Set enabled based on config
|
||||||
size: mod.size,
|
size: mod.size,
|
||||||
isBaseGame: mod.isBaseGame,
|
isBaseGame: mod.isBaseGame,
|
||||||
isExpansion: mod.isExpansion,
|
isExpansion: mod.isExpansion,
|
||||||
);
|
);
|
||||||
loadedModsCount++;
|
loadedModsCount++;
|
||||||
|
logger.info('Added new mod: ${mod.name} (ID: ${mod.id})');
|
||||||
}
|
}
|
||||||
|
|
||||||
final modTime = stopwatch.elapsedMilliseconds - modStart;
|
final modTime = stopwatch.elapsedMilliseconds - modStart;
|
||||||
loadingStatus = 'Loaded $loadedModsCount/$totalModsFound mods...';
|
loadingStatus = 'Loaded $loadedModsCount/$totalModsFound mods...';
|
||||||
|
|
||||||
if (loadedModsCount % 50 == 0 || loadedModsCount == totalModsFound) {
|
if (loadedModsCount % 50 == 0 || loadedModsCount == totalModsFound) {
|
||||||
logger.info('Progress: Loaded $loadedModsCount mods (${stopwatch.elapsedMilliseconds}ms)');
|
logger.info(
|
||||||
|
'Progress: Loaded $loadedModsCount mods (${stopwatch.elapsedMilliseconds}ms)',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('Error loading mod from directory: $modDir');
|
logger.error('Error loading mod from directory: $modDir');
|
||||||
logger.error('Error: $e');
|
logger.error('Error: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
modsLoaded = true;
|
modsLoaded = true;
|
||||||
final totalTime = stopwatch.elapsedMilliseconds;
|
final totalTime = stopwatch.elapsedMilliseconds;
|
||||||
loadingStatus = 'Completed! Loaded $loadedModsCount mods in ${totalTime}ms.';
|
loadingStatus =
|
||||||
logger.info('Loading complete! Loaded ${mods.length} mods in ${totalTime}ms');
|
'Completed! Loaded $loadedModsCount mods in ${totalTime}ms.';
|
||||||
|
logger.info(
|
||||||
|
'Loading complete! Loaded ${mods.length} mods in ${totalTime}ms',
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
loadingStatus = 'Error loading mods: $e';
|
loadingStatus = 'Error loading mods: $e';
|
||||||
logger.error(loadingStatus);
|
logger.error(loadingStatus);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to get a nice expansion name from ID
|
// Helper function to get a nice expansion name from ID
|
||||||
String _expansionNameFromId(String id) {
|
String _expansionNameFromId(String id) {
|
||||||
final parts = id.split('.');
|
final parts = id.split('.');
|
||||||
if (parts.length < 3) return id;
|
if (parts.length < 3) return id;
|
||||||
|
|
||||||
final expansionPart = parts[2];
|
final expansionPart = parts[2];
|
||||||
return expansionPart.substring(0, 1).toUpperCase() + expansionPart.substring(1);
|
return expansionPart.substring(0, 1).toUpperCase() +
|
||||||
|
expansionPart.substring(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build a directed graph of mod dependencies
|
// Build a directed graph of mod dependencies
|
||||||
@@ -478,7 +549,8 @@ class ModList {
|
|||||||
// 2. Add expansions as dependencies of mods that load after them
|
// 2. Add expansions as dependencies of mods that load after them
|
||||||
|
|
||||||
// First identify the base game and expansions
|
// First identify the base game and expansions
|
||||||
final baseGameId = mods.values.where((m) => m.isBaseGame).map((m) => m.id).firstOrNull;
|
final baseGameId =
|
||||||
|
mods.values.where((m) => m.isBaseGame).map((m) => m.id).firstOrNull;
|
||||||
if (baseGameId != null) {
|
if (baseGameId != null) {
|
||||||
for (final mod in mods.values) {
|
for (final mod in mods.values) {
|
||||||
// Skip the base game itself and mods that explicitly load before it
|
// Skip the base game itself and mods that explicitly load before it
|
||||||
@@ -590,7 +662,7 @@ class ModList {
|
|||||||
|
|
||||||
// Separate nodes by "layers" (nodes that can be processed at the same time)
|
// Separate nodes by "layers" (nodes that can be processed at the same time)
|
||||||
List<List<String>> layers = [];
|
List<List<String>> layers = [];
|
||||||
|
|
||||||
// Process until all nodes are assigned to layers
|
// Process until all nodes are assigned to layers
|
||||||
while (inDegree.isNotEmpty) {
|
while (inDegree.isNotEmpty) {
|
||||||
// Find all nodes with in-degree 0 in this iteration
|
// Find all nodes with in-degree 0 in this iteration
|
||||||
@@ -600,13 +672,15 @@ class ModList {
|
|||||||
currentLayer.add(node);
|
currentLayer.add(node);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (currentLayer.isEmpty && inDegree.isNotEmpty) {
|
if (currentLayer.isEmpty && inDegree.isNotEmpty) {
|
||||||
// We have a cycle - add all remaining nodes to a final layer
|
// We have a cycle - add all remaining nodes to a final layer
|
||||||
currentLayer = inDegree.keys.toList();
|
currentLayer = inDegree.keys.toList();
|
||||||
print("Warning: Cycle detected in dependency graph. Adding all remaining nodes to final layer.");
|
print(
|
||||||
|
"Warning: Cycle detected in dependency graph. Adding all remaining nodes to final layer.",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort this layer by mod size (descending)
|
// Sort this layer by mod size (descending)
|
||||||
currentLayer.sort((a, b) {
|
currentLayer.sort((a, b) {
|
||||||
final modA = mods[a];
|
final modA = mods[a];
|
||||||
@@ -614,14 +688,14 @@ class ModList {
|
|||||||
if (modA == null || modB == null) return 0;
|
if (modA == null || modB == null) return 0;
|
||||||
return modB.size.compareTo(modA.size); // Larger mods first
|
return modB.size.compareTo(modA.size); // Larger mods first
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add the layer to our layers list
|
// Add the layer to our layers list
|
||||||
layers.add(currentLayer);
|
layers.add(currentLayer);
|
||||||
|
|
||||||
// Remove processed nodes from inDegree
|
// Remove processed nodes from inDegree
|
||||||
for (final node in currentLayer) {
|
for (final node in currentLayer) {
|
||||||
inDegree.remove(node);
|
inDegree.remove(node);
|
||||||
|
|
||||||
// Update in-degrees for remaining nodes
|
// Update in-degrees for remaining nodes
|
||||||
for (final entry in graphCopy.entries) {
|
for (final entry in graphCopy.entries) {
|
||||||
if (entry.value.contains(node)) {
|
if (entry.value.contains(node)) {
|
||||||
@@ -632,13 +706,13 @@ class ModList {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flatten the layers to get the final order (first layer first)
|
// Flatten the layers to get the final order (first layer first)
|
||||||
List<String> result = [];
|
List<String> result = [];
|
||||||
for (final layer in layers) {
|
for (final layer in layers) {
|
||||||
result.addAll(layer);
|
result.addAll(layer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Final sanity check to make sure all nodes are included
|
// Final sanity check to make sure all nodes are included
|
||||||
if (result.length != graph.keys.length) {
|
if (result.length != graph.keys.length) {
|
||||||
// Add any missing nodes
|
// Add any missing nodes
|
||||||
@@ -648,7 +722,7 @@ class ModList {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -728,7 +802,9 @@ class ModList {
|
|||||||
print("Will attempt to break cycle to produce a valid load order");
|
print("Will attempt to break cycle to produce a valid load order");
|
||||||
}
|
}
|
||||||
|
|
||||||
print("Performing topological sort for hard dependencies (prioritizing larger mods)...");
|
print(
|
||||||
|
"Performing topological sort for hard dependencies (prioritizing larger mods)...",
|
||||||
|
);
|
||||||
final hardOrder = topologicalSort(hardGraph);
|
final hardOrder = topologicalSort(hardGraph);
|
||||||
|
|
||||||
print("Adjusting for soft dependencies...");
|
print("Adjusting for soft dependencies...");
|
||||||
@@ -786,33 +862,48 @@ class ConfigFile {
|
|||||||
logger.info('Found ${modElements.length} active mods.');
|
logger.info('Found ${modElements.length} active mods.');
|
||||||
|
|
||||||
// Get the list of known expansions
|
// Get the list of known expansions
|
||||||
final knownExpansionsElement = modConfigData.findElements("knownExpansions").firstOrNull;
|
final knownExpansionsElement =
|
||||||
final knownExpansionIds = knownExpansionsElement != null
|
modConfigData.findElements("knownExpansions").firstOrNull;
|
||||||
? knownExpansionsElement.findElements("li").map((e) => e.innerText.toLowerCase()).toList()
|
final knownExpansionIds =
|
||||||
: <String>[];
|
knownExpansionsElement != null
|
||||||
|
? knownExpansionsElement
|
||||||
|
.findElements("li")
|
||||||
|
.map((e) => e.innerText.toLowerCase())
|
||||||
|
.toList()
|
||||||
|
: <String>[];
|
||||||
|
|
||||||
logger.info('Found ${knownExpansionIds.length} known expansions.');
|
logger.info('Found ${knownExpansionIds.length} known expansions.');
|
||||||
|
|
||||||
// Clear and recreate the mods list
|
// Clear and recreate the mods list
|
||||||
mods = [];
|
mods = [];
|
||||||
for (final modElement in modElements) {
|
for (final modElement in modElements) {
|
||||||
final modId = modElement.innerText.toLowerCase();
|
final modId = modElement.innerText.toLowerCase();
|
||||||
|
|
||||||
// Check if this is a special Ludeon mod
|
// Check if this is a special Ludeon mod
|
||||||
final isBaseGame = modId == 'ludeon.rimworld';
|
final isBaseGame = modId == 'ludeon.rimworld';
|
||||||
final isExpansion = !isBaseGame && modId.startsWith('ludeon.rimworld.') &&
|
final isExpansion =
|
||||||
knownExpansionIds.contains(modId);
|
!isBaseGame &&
|
||||||
|
modId.startsWith('ludeon.rimworld.') &&
|
||||||
|
knownExpansionIds.contains(modId);
|
||||||
|
|
||||||
// We'll populate with dummy mods for now, they'll be replaced later
|
// We'll populate with dummy mods for now, they'll be replaced later
|
||||||
mods.add(
|
mods.add(
|
||||||
Mod(
|
Mod(
|
||||||
name: isBaseGame ? "RimWorld" :
|
name:
|
||||||
isExpansion ? "RimWorld ${_expansionNameFromId(modId)}" : modId,
|
isBaseGame
|
||||||
|
? "RimWorld"
|
||||||
|
: isExpansion
|
||||||
|
? "RimWorld ${_expansionNameFromId(modId)}"
|
||||||
|
: modId,
|
||||||
id: modId,
|
id: modId,
|
||||||
path: '',
|
path: '',
|
||||||
versions: [],
|
versions: [],
|
||||||
description: isBaseGame ? "RimWorld base game" :
|
description:
|
||||||
isExpansion ? "RimWorld expansion" : "",
|
isBaseGame
|
||||||
|
? "RimWorld base game"
|
||||||
|
: isExpansion
|
||||||
|
? "RimWorld expansion"
|
||||||
|
: "",
|
||||||
hardDependencies: [],
|
hardDependencies: [],
|
||||||
loadAfter: isExpansion ? ['ludeon.rimworld'] : [],
|
loadAfter: isExpansion ? ['ludeon.rimworld'] : [],
|
||||||
loadBefore: [],
|
loadBefore: [],
|
||||||
@@ -831,14 +922,15 @@ class ConfigFile {
|
|||||||
throw Exception('Failed to load config file: $e');
|
throw Exception('Failed to load config file: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to get a nice expansion name from ID
|
// Helper function to get a nice expansion name from ID
|
||||||
String _expansionNameFromId(String id) {
|
String _expansionNameFromId(String id) {
|
||||||
final parts = id.split('.');
|
final parts = id.split('.');
|
||||||
if (parts.length < 3) return id;
|
if (parts.length < 3) return id;
|
||||||
|
|
||||||
final expansionPart = parts[2];
|
final expansionPart = parts[2];
|
||||||
return expansionPart.substring(0, 1).toUpperCase() + expansionPart.substring(1);
|
return expansionPart.substring(0, 1).toUpperCase() +
|
||||||
|
expansionPart.substring(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save the current mod order back to the config file
|
// Save the current mod order back to the config file
|
||||||
|
Reference in New Issue
Block a user