diff --git a/apps/wolf_3d_cli/bin/main.dart b/apps/wolf_3d_cli/bin/main.dart index 49804a3..f00d59f 100644 --- a/apps/wolf_3d_cli/bin/main.dart +++ b/apps/wolf_3d_cli/bin/main.dart @@ -7,8 +7,6 @@ library; import 'dart:io'; import 'package:wolf_3d_cli/cli_game_loop.dart'; -import 'package:wolf_3d_cli/cli_renderer_settings_persistence.dart'; -import 'package:wolf_3d_cli/cli_save_game_persistence.dart'; import 'package:wolf_3d_dart/wolf_3d_data.dart'; import 'package:wolf_3d_dart/wolf_3d_data_types.dart'; import 'package:wolf_3d_dart/wolf_3d_engine.dart'; @@ -64,12 +62,12 @@ void main() async { input: CliInput(), onGameWon: () => stopAndExit(0), onQuit: () => stopAndExit(0), - saveGamePersistence: CliSaveGamePersistence(), + saveGamePersistence: DefaultSaveGamePersistence(), ); engine.init(); - final persistence = CliRendererSettingsPersistence(); + final persistence = DefaultRendererSettingsPersistence(); final WolfRendererSettings? saved = await persistence.load(); gameLoop = CliGameLoop( diff --git a/apps/wolf_3d_cli/lib/cli_renderer_settings_persistence.dart b/apps/wolf_3d_cli/lib/cli_renderer_settings_persistence.dart index 745e746..cafed69 100644 --- a/apps/wolf_3d_cli/lib/cli_renderer_settings_persistence.dart +++ b/apps/wolf_3d_cli/lib/cli_renderer_settings_persistence.dart @@ -12,9 +12,7 @@ import 'package:wolf_3d_dart/wolf_3d_engine.dart'; class CliRendererSettingsPersistence extends RendererSettingsPersistence with JsonRendererSettingsPersistence { CliRendererSettingsPersistence({String? filePath}) - : _filePath = - filePath ?? - '${Platform.environment['HOME'] ?? '.'}/.wolf3d_cli_settings.json'; + : _filePath = filePath ?? '${_platformConfigDir()}/settings.json'; final String _filePath; @@ -40,3 +38,21 @@ class CliRendererSettingsPersistence extends RendererSettingsPersistence } } } + +String _platformConfigDir() { + if (Platform.isLinux) { + final String xdg = Platform.environment['XDG_CONFIG_HOME'] ?? ''; + final String home = Platform.environment['HOME'] ?? '.'; + return xdg.isNotEmpty ? '$xdg/wolf3d' : '$home/.config/wolf3d'; + } + if (Platform.isMacOS) { + final String home = Platform.environment['HOME'] ?? '.'; + return '$home/Library/Application Support/wolf3d'; + } + if (Platform.isWindows) { + final String appData = Platform.environment['APPDATA'] ?? '.'; + return '$appData/wolf3d'; + } + final String home = Platform.environment['HOME'] ?? '.'; + return '$home/.config/wolf3d'; +} diff --git a/apps/wolf_3d_cli/lib/cli_save_game_persistence.dart b/apps/wolf_3d_cli/lib/cli_save_game_persistence.dart index 94dedd2..0f3f168 100644 --- a/apps/wolf_3d_cli/lib/cli_save_game_persistence.dart +++ b/apps/wolf_3d_cli/lib/cli_save_game_persistence.dart @@ -12,9 +12,7 @@ import 'package:wolf_3d_dart/wolf_3d_engine.dart'; /// `SAVEGAM{slot}.{ext}` where `{ext}` follows the active game version. class CliSaveGamePersistence implements SaveGamePersistence { CliSaveGamePersistence({String? directoryPath}) - : _directoryPath = - directoryPath ?? - '${Platform.environment['HOME'] ?? '.'}/.wolf3d_saves'; + : _directoryPath = directoryPath ?? '${_platformConfigDir()}/saves'; final String _directoryPath; @@ -66,3 +64,21 @@ class CliSaveGamePersistence implements SaveGamePersistence { return '$_directoryPath/SAVEGAM$normalizedSlot.${version.fileExtension}'; } } + +String _platformConfigDir() { + if (Platform.isLinux) { + final String xdg = Platform.environment['XDG_CONFIG_HOME'] ?? ''; + final String home = Platform.environment['HOME'] ?? '.'; + return xdg.isNotEmpty ? '$xdg/wolf3d' : '$home/.config/wolf3d'; + } + if (Platform.isMacOS) { + final String home = Platform.environment['HOME'] ?? '.'; + return '$home/Library/Application Support/wolf3d'; + } + if (Platform.isWindows) { + final String appData = Platform.environment['APPDATA'] ?? '.'; + return '$appData/wolf3d'; + } + final String home = Platform.environment['HOME'] ?? '.'; + return '$home/.config/wolf3d'; +} diff --git a/apps/wolf_3d_gui/lib/screens/game_screen.dart b/apps/wolf_3d_gui/lib/screens/game_screen.dart index 5e41a0f..cc1ef02 100644 --- a/apps/wolf_3d_gui/lib/screens/game_screen.dart +++ b/apps/wolf_3d_gui/lib/screens/game_screen.dart @@ -13,8 +13,6 @@ import 'package:wolf_3d_dart/wolf_3d_renderer.dart'; import 'package:wolf_3d_flutter/renderer/wolf_3d_ascii_renderer.dart'; import 'package:wolf_3d_flutter/renderer/wolf_3d_flutter_renderer.dart'; import 'package:wolf_3d_flutter/renderer/wolf_3d_glsl_renderer.dart'; -import 'package:wolf_3d_flutter/renderer_settings_persistence_flutter.dart'; -import 'package:wolf_3d_flutter/save_game_persistence_flutter.dart'; import 'package:wolf_3d_flutter/wolf_3d_flutter.dart'; import 'package:wolf_3d_flutter/wolf_3d_input_flutter.dart'; import 'package:wolf_3d_gui/screens/debug_tools_screen.dart'; @@ -141,10 +139,10 @@ class GameScreen extends StatefulWidget { class _GameScreenState extends State { late final WolfEngine _engine; - final FlutterRendererSettingsPersistence _persistence = - FlutterRendererSettingsPersistence(); - final FlutterSaveGamePersistence _savePersistence = - FlutterSaveGamePersistence(); + final DefaultRendererSettingsPersistence _persistence = + DefaultRendererSettingsPersistence(); + final DefaultSaveGamePersistence _savePersistence = + DefaultSaveGamePersistence(); /// Mirrors [WolfRendererSettings.mode] into the Flutter renderer enum. RendererMode _rendererMode = RendererMode.hardware; diff --git a/packages/wolf_3d_dart/lib/src/engine/rendering/default_renderer_settings_persistence.dart b/packages/wolf_3d_dart/lib/src/engine/rendering/default_renderer_settings_persistence.dart new file mode 100644 index 0000000..d1f3f33 --- /dev/null +++ b/packages/wolf_3d_dart/lib/src/engine/rendering/default_renderer_settings_persistence.dart @@ -0,0 +1,5 @@ +/// Routes to the native or stub implementation based on platform. +library; + +export 'default_renderer_settings_persistence_stub.dart' + if (dart.library.io) 'default_renderer_settings_persistence_io.dart'; diff --git a/packages/wolf_3d_dart/lib/src/engine/rendering/default_renderer_settings_persistence_io.dart b/packages/wolf_3d_dart/lib/src/engine/rendering/default_renderer_settings_persistence_io.dart new file mode 100644 index 0000000..0698047 --- /dev/null +++ b/packages/wolf_3d_dart/lib/src/engine/rendering/default_renderer_settings_persistence_io.dart @@ -0,0 +1,49 @@ +/// Native (dart:io) renderer-settings persistence. +library; + +import 'dart:io'; + +import 'package:wolf_3d_dart/src/engine/rendering/renderer_settings.dart'; +import 'package:wolf_3d_dart/src/engine/rendering/renderer_settings_persistence.dart'; +import 'package:wolf_3d_dart/src/platform/platform_config_dir.dart'; + +/// Persists [WolfRendererSettings] as JSON to the platform config directory. +/// +/// Pass an explicit [filePath] to override the default location (useful in tests). +class DefaultRendererSettingsPersistence extends RendererSettingsPersistence + with JsonRendererSettingsPersistence { + DefaultRendererSettingsPersistence({String? filePath}) : _filePath = filePath; + + final String? _filePath; + String? _resolvedPath; + + Future _getFilePath() async { + if (_resolvedPath != null) return _resolvedPath!; + _resolvedPath = _filePath ?? '${platformConfigDir()}/settings.json'; + return _resolvedPath!; + } + + @override + Future readRaw() async { + try { + final String path = await _getFilePath(); + final File f = File(path); + if (!f.existsSync()) return null; + return await f.readAsString(); + } catch (_) { + return null; + } + } + + @override + Future writeRaw(String json) async { + try { + final String path = await _getFilePath(); + final Directory dir = File(path).parent; + if (!dir.existsSync()) await dir.create(recursive: true); + await File(path).writeAsString(json, flush: true); + } catch (_) { + // Best-effort. + } + } +} diff --git a/packages/wolf_3d_dart/lib/src/engine/rendering/default_renderer_settings_persistence_stub.dart b/packages/wolf_3d_dart/lib/src/engine/rendering/default_renderer_settings_persistence_stub.dart new file mode 100644 index 0000000..9f1f5f8 --- /dev/null +++ b/packages/wolf_3d_dart/lib/src/engine/rendering/default_renderer_settings_persistence_stub.dart @@ -0,0 +1,17 @@ +/// Web stub for renderer-settings persistence: silently skips all I/O. +library; + +import 'package:wolf_3d_dart/src/engine/rendering/renderer_settings.dart'; +import 'package:wolf_3d_dart/src/engine/rendering/renderer_settings_persistence.dart'; + +/// No-op implementation used on web, where dart:io is unavailable. +class DefaultRendererSettingsPersistence extends RendererSettingsPersistence { + // ignore: avoid_unused_constructor_parameters + DefaultRendererSettingsPersistence({String? filePath}); + + @override + Future load() async => null; + + @override + Future save(WolfRendererSettings settings) async {} +} diff --git a/packages/wolf_3d_dart/lib/src/engine/save/default_save_game_persistence.dart b/packages/wolf_3d_dart/lib/src/engine/save/default_save_game_persistence.dart new file mode 100644 index 0000000..823dfe4 --- /dev/null +++ b/packages/wolf_3d_dart/lib/src/engine/save/default_save_game_persistence.dart @@ -0,0 +1,5 @@ +/// Routes to the native or stub implementation based on platform. +library; + +export 'default_save_game_persistence_stub.dart' + if (dart.library.io) 'default_save_game_persistence_io.dart'; diff --git a/packages/wolf_3d_dart/lib/src/engine/save/default_save_game_persistence_io.dart b/packages/wolf_3d_dart/lib/src/engine/save/default_save_game_persistence_io.dart new file mode 100644 index 0000000..dc86169 --- /dev/null +++ b/packages/wolf_3d_dart/lib/src/engine/save/default_save_game_persistence_io.dart @@ -0,0 +1,65 @@ +/// Native (dart:io) slot-based save-game persistence. +library; + +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:wolf_3d_dart/src/engine/save/save_game_persistence.dart'; +import 'package:wolf_3d_dart/src/platform/platform_config_dir.dart'; +import 'package:wolf_3d_dart/wolf_3d_data_types.dart'; + +/// Persists save-game slots as raw bytes under the platform config directory. +/// +/// Files are stored in `/saves/` and named +/// `SAVEGAM{slot}.{ext}` where `{ext}` follows the active game version. +/// +/// Pass an explicit [directoryPath] to override the default (useful in tests). +class DefaultSaveGamePersistence extends SaveGamePersistence { + DefaultSaveGamePersistence({String? directoryPath}) + : _directoryPath = directoryPath ?? '${platformConfigDir()}/saves'; + + final String _directoryPath; + + @override + Future load({ + required int slot, + required GameVersion version, + }) async { + try { + final File file = File(_slotPath(slot, version)); + if (!file.existsSync()) return null; + return await file.readAsBytes(); + } catch (_) { + return null; + } + } + + @override + Future exists({ + required int slot, + required GameVersion version, + }) async { + try { + final File file = File(_slotPath(slot, version)); + return file.existsSync() && file.lengthSync() > 0; + } catch (_) { + return false; + } + } + + @override + Future save({ + required int slot, + required GameVersion version, + required Uint8List bytes, + }) async { + final Directory dir = Directory(_directoryPath); + if (!dir.existsSync()) await dir.create(recursive: true); + await File(_slotPath(slot, version)).writeAsBytes(bytes, flush: true); + } + + String _slotPath(int slot, GameVersion version) { + final String normalizedSlot = slot.clamp(0, 9).toString(); + return '$_directoryPath/SAVEGAM$normalizedSlot.${version.fileExtension}'; + } +} diff --git a/packages/wolf_3d_dart/lib/src/engine/save/default_save_game_persistence_stub.dart b/packages/wolf_3d_dart/lib/src/engine/save/default_save_game_persistence_stub.dart new file mode 100644 index 0000000..7772f72 --- /dev/null +++ b/packages/wolf_3d_dart/lib/src/engine/save/default_save_game_persistence_stub.dart @@ -0,0 +1,32 @@ +/// Web stub for save-game persistence: silently skips all I/O. +library; + +import 'dart:typed_data'; + +import 'package:wolf_3d_dart/src/engine/save/save_game_persistence.dart'; +import 'package:wolf_3d_dart/wolf_3d_data_types.dart'; + +/// No-op implementation used on web, where dart:io is unavailable. +class DefaultSaveGamePersistence extends SaveGamePersistence { + // ignore: avoid_unused_constructor_parameters + DefaultSaveGamePersistence({String? directoryPath}); + + @override + Future load({ + required int slot, + required GameVersion version, + }) async => null; + + @override + Future exists({ + required int slot, + required GameVersion version, + }) async => false; + + @override + Future save({ + required int slot, + required GameVersion version, + required Uint8List bytes, + }) async {} +} diff --git a/packages/wolf_3d_dart/lib/src/platform/platform_config_dir.dart b/packages/wolf_3d_dart/lib/src/platform/platform_config_dir.dart new file mode 100644 index 0000000..37fe75f --- /dev/null +++ b/packages/wolf_3d_dart/lib/src/platform/platform_config_dir.dart @@ -0,0 +1,31 @@ +/// Returns the platform-appropriate Wolf3D config directory path. +/// +/// This file is only ever imported by native (dart:io) code paths and must +/// never be loaded on web. +library; + +import 'dart:io'; + +/// Returns the Wolf3D config directory for the current platform. +/// +/// - Linux: `$XDG_CONFIG_HOME/wolf3d` (defaults to `~/.config/wolf3d`) +/// - macOS: `~/Library/Application Support/wolf3d` +/// - Windows: `%APPDATA%/wolf3d` +/// - Other: `~/.config/wolf3d` +String platformConfigDir() { + if (Platform.isLinux) { + final String xdg = Platform.environment['XDG_CONFIG_HOME'] ?? ''; + final String home = Platform.environment['HOME'] ?? '.'; + return xdg.isNotEmpty ? '$xdg/wolf3d' : '$home/.config/wolf3d'; + } + if (Platform.isMacOS) { + final String home = Platform.environment['HOME'] ?? '.'; + return '$home/Library/Application Support/wolf3d'; + } + if (Platform.isWindows) { + final String appData = Platform.environment['APPDATA'] ?? '.'; + return '$appData/wolf3d'; + } + final String home = Platform.environment['HOME'] ?? '.'; + return '$home/.config/wolf3d'; +} diff --git a/packages/wolf_3d_dart/lib/wolf_3d_engine.dart b/packages/wolf_3d_dart/lib/wolf_3d_engine.dart index c9c7b30..652720b 100644 --- a/packages/wolf_3d_dart/lib/wolf_3d_engine.dart +++ b/packages/wolf_3d_dart/lib/wolf_3d_engine.dart @@ -12,8 +12,10 @@ export 'src/engine/managers/door_manager.dart'; export 'src/engine/managers/pushwall_manager.dart'; export 'src/engine/player/player.dart'; export 'src/engine/player_locomotion_constants.dart'; +export 'src/engine/rendering/default_renderer_settings_persistence.dart'; export 'src/engine/rendering/renderer_settings.dart'; export 'src/engine/rendering/renderer_settings_persistence.dart'; +export 'src/engine/save/default_save_game_persistence.dart'; export 'src/engine/save/game_session_snapshot.dart'; export 'src/engine/save/save_game_codec.dart'; export 'src/engine/save/save_game_persistence.dart'; diff --git a/packages/wolf_3d_flutter/lib/renderer_settings_persistence_flutter.dart b/packages/wolf_3d_flutter/lib/renderer_settings_persistence_flutter.dart index 67b3089..002094f 100644 --- a/packages/wolf_3d_flutter/lib/renderer_settings_persistence_flutter.dart +++ b/packages/wolf_3d_flutter/lib/renderer_settings_persistence_flutter.dart @@ -21,7 +21,7 @@ class FlutterRendererSettingsPersistence extends RendererSettingsPersistence final String? _filePath; String? _resolvedPath; - Future _getFilePath() async { + String get filePath { if (_resolvedPath != null) { return _resolvedPath!; } @@ -29,10 +29,7 @@ class FlutterRendererSettingsPersistence extends RendererSettingsPersistence _resolvedPath = _filePath; return _resolvedPath!; } - // Resolve platform app-support directory. - final String home = - Platform.environment['HOME'] ?? Platform.environment['APPDATA'] ?? '.'; - _resolvedPath = '$home/.wolf3d_settings.json'; + _resolvedPath = '$platformConfigDir/settings.json'; return _resolvedPath!; } @@ -40,8 +37,7 @@ class FlutterRendererSettingsPersistence extends RendererSettingsPersistence Future readRaw() async { if (kIsWeb) return null; try { - final String path = await _getFilePath(); - final File f = File(path); + final File f = File(filePath); if (!f.existsSync()) return null; return await f.readAsString(); } catch (_) { @@ -53,10 +49,27 @@ class FlutterRendererSettingsPersistence extends RendererSettingsPersistence Future writeRaw(String json) async { if (kIsWeb) return; try { - final String path = await _getFilePath(); - await File(path).writeAsString(json, flush: true); + await File(filePath).writeAsString(json, flush: true); } catch (_) { // Best-effort. } } + + String get platformConfigDir { + if (Platform.isLinux) { + final String xdg = Platform.environment['XDG_CONFIG_HOME'] ?? ''; + final String home = Platform.environment['HOME'] ?? '.'; + return xdg.isNotEmpty ? '$xdg/wolf3d' : '$home/.config/wolf3d'; + } + if (Platform.isMacOS) { + final String home = Platform.environment['HOME'] ?? '.'; + return '$home/Library/Application Support/wolf3d'; + } + if (Platform.isWindows) { + final String appData = Platform.environment['APPDATA'] ?? '.'; + return '$appData/wolf3d'; + } + final String home = Platform.environment['HOME'] ?? '.'; + return '$home/.config/wolf3d'; + } } diff --git a/packages/wolf_3d_flutter/lib/save_game_persistence_flutter.dart b/packages/wolf_3d_flutter/lib/save_game_persistence_flutter.dart index 1100899..7633bf7 100644 --- a/packages/wolf_3d_flutter/lib/save_game_persistence_flutter.dart +++ b/packages/wolf_3d_flutter/lib/save_game_persistence_flutter.dart @@ -24,9 +24,7 @@ class FlutterSaveGamePersistence implements SaveGamePersistence { return _resolvedDirectoryPath!; } - final String home = - Platform.environment['HOME'] ?? Platform.environment['APPDATA'] ?? '.'; - _resolvedDirectoryPath = '$home/.wolf3d_saves'; + _resolvedDirectoryPath = '${_platformConfigDir()}/saves'; return _resolvedDirectoryPath!; } @@ -94,3 +92,21 @@ class FlutterSaveGamePersistence implements SaveGamePersistence { return '$dirPath/SAVEGAM$normalizedSlot.${version.fileExtension}'; } } + +String _platformConfigDir() { + if (Platform.isLinux) { + final String xdg = Platform.environment['XDG_CONFIG_HOME'] ?? ''; + final String home = Platform.environment['HOME'] ?? '.'; + return xdg.isNotEmpty ? '$xdg/wolf3d' : '$home/.config/wolf3d'; + } + if (Platform.isMacOS) { + final String home = Platform.environment['HOME'] ?? '.'; + return '$home/Library/Application Support/wolf3d'; + } + if (Platform.isWindows) { + final String appData = Platform.environment['APPDATA'] ?? '.'; + return '$appData/wolf3d'; + } + final String home = Platform.environment['HOME'] ?? '.'; + return '$home/.config/wolf3d'; +}