diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 60ccd25..75983d2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -96,6 +96,7 @@ jobs: name: apk-x86_64-build path: app/linwood-setonix-android-x86_64.apk - name: Copy Corepack + working-directory: ./ run: | cp app/assets/pack.stnx core.stnx - name: Archive Corepack diff --git a/api/lib/src/event/process/client.dart b/api/lib/src/event/process/client.dart index d89433a..b86bcf3 100644 --- a/api/lib/src/event/process/client.dart +++ b/api/lib/src/event/process/client.dart @@ -362,9 +362,7 @@ Future processClientEvent( ); case ModeChangeRequest(): final location = event.location; - final mode = location == null - ? null - : assetManager.getPack(location.namespace)?.getMode(location.id); + final mode = location == null ? null : assetManager.getModeItem(location); return UpdateServerResponse.builder( WorldInitialized.fromMode(mode, state), channel, diff --git a/api/lib/src/event/server.dart b/api/lib/src/event/server.dart index 2e1dc80..2bb4e2f 100644 --- a/api/lib/src/event/server.dart +++ b/api/lib/src/event/server.dart @@ -27,16 +27,18 @@ final class WorldInitialized extends ServerWorldEvent this.clearUserInterface = false, }); - factory WorldInitialized.fromMode(GameMode? mode, WorldState state) => - WorldInitialized( - clearUserInterface: true, - info: state.info.copyWith( - teams: mode?.teams ?? {}, - script: mode?.script, - ), - table: mode?.tables[state.tableName] ?? GameTable(), - teamMembers: const {}, - ); + factory WorldInitialized.fromMode( + PackItem? mode, + WorldState state, + ) => WorldInitialized( + clearUserInterface: true, + info: state.info.copyWith( + teams: mode?.item.teams ?? {}, + script: mode?.location, + ), + table: mode?.item.tables[state.tableName] ?? GameTable(), + teamMembers: const {}, + ); } @MappableClass() diff --git a/api/lib/src/models/config.dart b/api/lib/src/models/config.dart index b792f71..47466c6 100644 --- a/api/lib/src/models/config.dart +++ b/api/lib/src/models/config.dart @@ -41,6 +41,9 @@ final class SetonixConfig with SetonixConfigMappable { final String? endpointSecret; static const String defaultEndpointSecret = ''; static const String envEndpointSecret = 'SETONIX_ENDPOINT_SECRET'; + final String? gameMode; + static const String defaultGameMode = ''; + static const String envGameMode = 'SETONIX_GAME_MODE'; const SetonixConfig({ this.host, @@ -55,6 +58,7 @@ final class SetonixConfig with SetonixConfigMappable { this.accountRequired, this.apiEndpoint, this.endpointSecret, + this.gameMode, }); static const defaultConfig = SetonixConfig( @@ -70,6 +74,7 @@ final class SetonixConfig with SetonixConfigMappable { accountRequired: defaultAccountRequired, apiEndpoint: defaultApiEndpoint, endpointSecret: defaultEndpointSecret, + gameMode: defaultGameMode, ); static SetonixConfig fromEnvironment() { @@ -128,6 +133,9 @@ final class SetonixConfig with SetonixConfigMappable { defaultValue: defaultEndpointSecret, ) : null, + gameMode: bool.hasEnvironment(envGameMode) + ? String.fromEnvironment(envGameMode, defaultValue: defaultGameMode) + : null, ); } diff --git a/api/lib/src/models/config.mapper.dart b/api/lib/src/models/config.mapper.dart index f216f0f..fd4c4a3 100644 --- a/api/lib/src/models/config.mapper.dart +++ b/api/lib/src/models/config.mapper.dart @@ -92,6 +92,12 @@ class SetonixConfigMapper extends ClassMapperBase { _$endpointSecret, opt: true, ); + static String? _$gameMode(SetonixConfig v) => v.gameMode; + static const Field _f$gameMode = Field( + 'gameMode', + _$gameMode, + opt: true, + ); @override final MappableFields fields = const { @@ -107,6 +113,7 @@ class SetonixConfigMapper extends ClassMapperBase { #accountRequired: _f$accountRequired, #apiEndpoint: _f$apiEndpoint, #endpointSecret: _f$endpointSecret, + #gameMode: _f$gameMode, }; static SetonixConfig _instantiate(DecodingData data) { @@ -123,6 +130,7 @@ class SetonixConfigMapper extends ClassMapperBase { accountRequired: data.dec(_f$accountRequired), apiEndpoint: data.dec(_f$apiEndpoint), endpointSecret: data.dec(_f$endpointSecret), + gameMode: data.dec(_f$gameMode), ); } @@ -201,6 +209,7 @@ abstract class SetonixConfigCopyWith<$R, $In extends SetonixConfig, $Out> bool? accountRequired, String? apiEndpoint, String? endpointSecret, + String? gameMode, }); SetonixConfigCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); } @@ -227,6 +236,7 @@ class _SetonixConfigCopyWithImpl<$R, $Out> Object? accountRequired = $none, Object? apiEndpoint = $none, Object? endpointSecret = $none, + Object? gameMode = $none, }) => $apply( FieldCopyWithData({ if (host != $none) #host: host, @@ -241,6 +251,7 @@ class _SetonixConfigCopyWithImpl<$R, $Out> if (accountRequired != $none) #accountRequired: accountRequired, if (apiEndpoint != $none) #apiEndpoint: apiEndpoint, if (endpointSecret != $none) #endpointSecret: endpointSecret, + if (gameMode != $none) #gameMode: gameMode, }), ); @override @@ -257,6 +268,7 @@ class _SetonixConfigCopyWithImpl<$R, $Out> accountRequired: data.get(#accountRequired, or: $value.accountRequired), apiEndpoint: data.get(#apiEndpoint, or: $value.apiEndpoint), endpointSecret: data.get(#endpointSecret, or: $value.endpointSecret), + gameMode: data.get(#gameMode, or: $value.gameMode), ); @override diff --git a/api/lib/src/models/data.dart b/api/lib/src/models/data.dart index ca093ab..79d7f47 100644 --- a/api/lib/src/models/data.dart +++ b/api/lib/src/models/data.dart @@ -38,6 +38,22 @@ class SetonixData extends ArchiveData { : identifier = identifier ?? createPackIdentifier(data), super.fromBytes(); + factory SetonixData.fromMode(ItemLocation? location, GameMode? mode) { + var data = SetonixData.empty().setInfo( + GameInfo( + packs: [?location?.namespace], + script: location, + teams: mode?.teams ?? const {}, + ), + ); + for (final entry + in mode?.tables.entries ?? + Iterable>.empty()) { + data = data.setTable(entry.value, entry.key); + } + return data; + } + GameTable? getTable([String name = '']) { final data = getAsset('$kGameTablePath/$name.json'); if (data == null) return null; @@ -305,6 +321,14 @@ class SetonixData extends ArchiveData { } } + PackItem? getModeItem(String id, [String namespace = '']) => + PackItem.wrap( + pack: this, + namespace: namespace, + id: id, + item: getMode(id), + ); + Map getModesData() => Map.fromEntries( getModes().map((e) { final mode = getMode(e); diff --git a/api/lib/src/models/info.dart b/api/lib/src/models/info.dart index c503aa9..ce4f364 100644 --- a/api/lib/src/models/info.dart +++ b/api/lib/src/models/info.dart @@ -8,7 +8,7 @@ part 'info.mapper.dart'; class GameInfo with GameInfoMappable { final Map teams; final List packs; - final String? script; + final ItemLocation? script; const GameInfo({this.teams = const {}, this.packs = const [], this.script}); } diff --git a/api/lib/src/models/info.mapper.dart b/api/lib/src/models/info.mapper.dart index 5120265..31112da 100644 --- a/api/lib/src/models/info.mapper.dart +++ b/api/lib/src/models/info.mapper.dart @@ -96,6 +96,7 @@ class GameInfoMapper extends ClassMapperBase { if (_instance == null) { MapperContainer.globals.use(_instance = GameInfoMapper._()); GameTeamMapper.ensureInitialized(); + ItemLocationMapper.ensureInitialized(); } return _instance!; } @@ -117,8 +118,8 @@ class GameInfoMapper extends ClassMapperBase { opt: true, def: const [], ); - static String? _$script(GameInfo v) => v.script; - static const Field _f$script = Field( + static ItemLocation? _$script(GameInfo v) => v.script; + static const Field _f$script = Field( 'script', _$script, opt: true, @@ -199,7 +200,12 @@ abstract class GameInfoCopyWith<$R, $In extends GameInfo, $Out> MapCopyWith<$R, String, GameTeam, GameTeamCopyWith<$R, GameTeam, GameTeam>> get teams; ListCopyWith<$R, String, ObjectCopyWith<$R, String, String>> get packs; - $R call({Map? teams, List? packs, String? script}); + ItemLocationCopyWith<$R, ItemLocation, ItemLocation>? get script; + $R call({ + Map? teams, + List? packs, + ItemLocation? script, + }); GameInfoCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); } @@ -226,6 +232,9 @@ class _GameInfoCopyWithImpl<$R, $Out> (v) => call(packs: v), ); @override + ItemLocationCopyWith<$R, ItemLocation, ItemLocation>? get script => + $value.script?.copyWith.$chain((v) => call(script: v)); + @override $R call({ Map? teams, List? packs, diff --git a/api/lib/src/models/mode.dart b/api/lib/src/models/mode.dart index fec6178..d84c75d 100644 --- a/api/lib/src/models/mode.dart +++ b/api/lib/src/models/mode.dart @@ -10,13 +10,11 @@ final class GameMode with GameModeMappable { final String? script; final Map tables; - final String tableName; final Map teams; GameMode({ required this.script, this.tables = const {}, - this.tableName = '', this.teams = const {}, }); } diff --git a/api/lib/src/models/mode.mapper.dart b/api/lib/src/models/mode.mapper.dart index 15ff5c2..0def22a 100644 --- a/api/lib/src/models/mode.mapper.dart +++ b/api/lib/src/models/mode.mapper.dart @@ -31,13 +31,6 @@ class GameModeMapper extends ClassMapperBase { opt: true, def: const {}, ); - static String _$tableName(GameMode v) => v.tableName; - static const Field _f$tableName = Field( - 'tableName', - _$tableName, - opt: true, - def: '', - ); static Map _$teams(GameMode v) => v.teams; static const Field> _f$teams = Field( 'teams', @@ -50,7 +43,6 @@ class GameModeMapper extends ClassMapperBase { final MappableFields fields = const { #script: _f$script, #tables: _f$tables, - #tableName: _f$tableName, #teams: _f$teams, }; @@ -58,7 +50,6 @@ class GameModeMapper extends ClassMapperBase { return GameMode( script: data.dec(_f$script), tables: data.dec(_f$tables), - tableName: data.dec(_f$tableName), teams: data.dec(_f$teams), ); } @@ -132,7 +123,6 @@ abstract class GameModeCopyWith<$R, $In extends GameMode, $Out> $R call({ String? script, Map? tables, - String? tableName, Map? teams, }); GameModeCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); @@ -169,13 +159,11 @@ class _GameModeCopyWithImpl<$R, $Out> $R call({ Object? script = $none, Map? tables, - String? tableName, Map? teams, }) => $apply( FieldCopyWithData({ if (script != $none) #script: script, if (tables != null) #tables: tables, - if (tableName != null) #tableName: tableName, if (teams != null) #teams: teams, }), ); @@ -183,7 +171,6 @@ class _GameModeCopyWithImpl<$R, $Out> GameMode $make(CopyWithData data) => GameMode( script: data.get(#script, or: $value.script), tables: data.get(#tables, or: $value.tables), - tableName: data.get(#tableName, or: $value.tableName), teams: data.get(#teams, or: $value.teams), ); diff --git a/api/lib/src/models/table.dart b/api/lib/src/models/table.dart index 0fd71dc..379dfa1 100644 --- a/api/lib/src/models/table.dart +++ b/api/lib/src/models/table.dart @@ -116,7 +116,7 @@ class BoardTile with BoardTileMappable { BoardTile(this.asset, this.tile); } -@MappableClass() +@MappableClass(hook: ItemLocationHook()) class ItemLocation with ItemLocationMappable { final String namespace, id; @@ -130,6 +130,32 @@ class ItemLocation with ItemLocationMappable { return ItemLocation(splitted[0], splitted[1]); } + bool get isEmpty => namespace.isEmpty && id.isEmpty; + @override String toString() => namespace.isEmpty ? id : '$namespace:$id'; } + +class ItemLocationHook extends MappingHook { + final bool nullOnEmpty; + + const ItemLocationHook({this.nullOnEmpty = true}); + + @override + Object? beforeDecode(Object? value) { + if (value is String) { + return ItemLocation.fromString(value).toMap(); + } + return value; + } + + @override + Object? afterEncode(Object? value) { + if (value is ItemLocation) { + if (value.isEmpty && nullOnEmpty) { + return null; + } + } + return value; + } +} diff --git a/api/lib/src/models/table.mapper.dart b/api/lib/src/models/table.mapper.dart index 5992504..335544d 100644 --- a/api/lib/src/models/table.mapper.dart +++ b/api/lib/src/models/table.mapper.dart @@ -609,6 +609,8 @@ class ItemLocationMapper extends ClassMapperBase { #id: _f$id, }; + @override + final MappingHook hook = const ItemLocationHook(); static ItemLocation _instantiate(DecodingData data) { return ItemLocation(data.dec(_f$namespace), data.dec(_f$id)); } diff --git a/api/lib/src/services/asset.dart b/api/lib/src/services/asset.dart index 07bda9a..1332ab1 100644 --- a/api/lib/src/services/asset.dart +++ b/api/lib/src/services/asset.dart @@ -53,4 +53,7 @@ abstract class AssetManager { PackItem? getDeckItem(ItemLocation location) => getPack(location.namespace)?.getDeckItem(location.id, location.namespace); + + PackItem? getModeItem(ItemLocation location) => + getPack(location.namespace)?.getModeItem(location.id, location.namespace); } diff --git a/api/pubspec.yaml b/api/pubspec.yaml index 4381f96..9bc1d5a 100644 --- a/api/pubspec.yaml +++ b/api/pubspec.yaml @@ -1,6 +1,6 @@ name: setonix_api description: The Linwood Setonix API -version: 0.6.0 +version: 0.5.1 publish_to: none # repository: https://github.com/my_org/my_repo diff --git a/app/AppImageBuilder.yml b/app/AppImageBuilder.yml index 1d5a7a7..8b3c8a4 100644 --- a/app/AppImageBuilder.yml +++ b/app/AppImageBuilder.yml @@ -14,7 +14,7 @@ AppDir: id: dev.linwood.setonix name: Linwood Setonix icon: dev.linwood.setonix - version: 0.6.0 + version: 0.5.1 exec: setonix exec_args: $@ apt: diff --git a/app/lib/bloc/world/bloc.dart b/app/lib/bloc/world/bloc.dart index ab453b5..ad19ad6 100644 --- a/app/lib/bloc/world/bloc.dart +++ b/app/lib/bloc/world/bloc.dart @@ -222,10 +222,10 @@ class WorldBloc extends Bloc { } } - Future _loadScript(String? script) async { + Future _loadScript(ItemLocation? location) async { try { - if (script == null) return; - pluginSystem.loadLuaPlugin(state.assetManager, script); + if (location == null) return; + pluginSystem.loadLuaPluginFromLocation(state.assetManager, location); // ignore: empty_catches } catch (e) {} } diff --git a/app/lib/main.dart b/app/lib/main.dart index 195988d..e92d188 100644 --- a/app/lib/main.dart +++ b/app/lib/main.dart @@ -22,7 +22,6 @@ import 'package:setonix/theme.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:flutter_localized_locales/flutter_localized_locales.dart'; import 'package:window_manager/window_manager.dart'; -import 'package:setonix_plugin/setonix_plugin.dart'; import 'bloc/settings.dart'; import 'pages/settings/home.dart'; @@ -49,7 +48,7 @@ Future main(List args) async { await setup(settingsCubit); - await initPluginSystem(); + //await initPluginSystem(); runApp( MultiBlocProvider( providers: [ @@ -191,7 +190,7 @@ class SetonixApp extends StatelessWidget { builder: (context, state) => const AccountsSettingsPage(), ), GoRoute( - path: 'serverlist', + path: 'servers', builder: (context, state) => const ServersSettingsPage(), ), ], @@ -207,4 +206,4 @@ const isNightly = flavor == 'nightly' || flavor == 'dev' || flavor == 'development'; const shortApplicationName = isNightly ? 'Setonix Nightly' : 'Setonix'; const applicationName = 'Linwood $shortApplicationName'; -const applicationMinorVersion = '0.5.0'; +const applicationMinorVersion = '0.5.1'; diff --git a/app/linux/debian/DEBIAN/control b/app/linux/debian/DEBIAN/control index 756997b..70383b2 100644 --- a/app/linux/debian/DEBIAN/control +++ b/app/linux/debian/DEBIAN/control @@ -1,5 +1,5 @@ Package: linwood-setonix -Version: 0.6.0 +Version: 0.5.1 Section: base Priority: optional Homepage: https://github.com/LinwoodDev/Setonix diff --git a/app/pubspec.lock b/app/pubspec.lock index 01c973e..120ccca 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -1107,7 +1107,7 @@ packages: path: "../api" relative: true source: path - version: "0.6.0" + version: "0.5.1" setonix_plugin: dependency: "direct main" description: diff --git a/app/pubspec.yaml b/app/pubspec.yaml index 97a445e..5f6e82a 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -11,7 +11,7 @@ description: Play games without internet # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 0.6.0+8 +version: 0.5.1+8 publish_to: none environment: diff --git a/metadata/en-US/changelogs/8.txt b/metadata/en-US/changelogs/8.txt new file mode 100644 index 0000000..fe18fa4 --- /dev/null +++ b/metadata/en-US/changelogs/8.txt @@ -0,0 +1,3 @@ +* Fix serverlist route + +Read more here: https://linwood.dev/setonix/0.5.1 \ No newline at end of file diff --git a/plugin/lib/setonix_plugin.dart b/plugin/lib/setonix_plugin.dart index 2fa283f..12aacb8 100644 --- a/plugin/lib/setonix_plugin.dart +++ b/plugin/lib/setonix_plugin.dart @@ -5,10 +5,16 @@ export 'src/rust/api/plugin.dart'; export 'src/rust/api/luau.dart'; export 'events.dart'; +bool _isInitialized = false; + +bool get isPluginSystemInitialized => _isInitialized; + Future initPluginSystem() { - return Future.value(); + _isInitialized = true; + return RustLib.init(); } void disposePluginSystem() { + _isInitialized = false; RustLib.dispose(); } diff --git a/plugin/lib/src/plugin.dart b/plugin/lib/src/plugin.dart index cd02ca9..6688001 100644 --- a/plugin/lib/src/plugin.dart +++ b/plugin/lib/src/plugin.dart @@ -60,6 +60,18 @@ final class PluginSystem { _plugins.remove(name); } + void loadLuaPluginFromLocation( + AssetManager assetManager, + ItemLocation location, [ + String name = 'game', + ]) { + final data = assetManager + .getPack(location.namespace) + ?.getScript(location.id); + if (data == null) return; + loadLuaPlugin(assetManager, data, name); + } + void loadLuaPlugin( AssetManager assetManager, String script, [ diff --git a/plugin/pubspec.lock b/plugin/pubspec.lock index bb0c3f4..bdb20cc 100644 --- a/plugin/pubspec.lock +++ b/plugin/pubspec.lock @@ -442,7 +442,7 @@ packages: path: "../api" relative: true source: path - version: "0.6.0" + version: "0.5.1" shelf: dependency: transitive description: diff --git a/server/lib/src/bloc.dart b/server/lib/src/bloc.dart index a71a160..b3e0c47 100644 --- a/server/lib/src/bloc.dart +++ b/server/lib/src/bloc.dart @@ -67,10 +67,10 @@ class WorldBloc extends Bloc }); } - Future _loadScript(String? script) async { + Future _loadScript(ItemLocation? location) async { try { - if (script == null) return; - pluginSystem.loadLuaPlugin(assetManager, script); + if (location == null) return; + pluginSystem.loadLuaPluginFromLocation(assetManager, location); } catch (e) { server.log('Error loading script: $e', level: LogLevel.error); } diff --git a/server/lib/src/config.dart b/server/lib/src/config.dart index 818119f..697d2f6 100644 --- a/server/lib/src/config.dart +++ b/server/lib/src/config.dart @@ -67,4 +67,10 @@ class ConfigManager { String get endpointSecret => _mergedConfig.endpointSecret ?? SetonixConfig.defaultEndpointSecret; + + ItemLocation? get gameMode { + final data = _mergedConfig.gameMode ?? SetonixConfig.defaultGameMode; + if (data.isEmpty) return null; + return ItemLocation.fromString(data); + } } diff --git a/server/lib/src/server.dart b/server/lib/src/server.dart index dcee2ed..37fb667 100644 --- a/server/lib/src/server.dart +++ b/server/lib/src/server.dart @@ -51,9 +51,15 @@ final class SetonixServer { )); SetonixData _buildDefaultWorld() { - final data = SetonixData.empty().setInfo( - GameInfo(packs: assetManager.getPackIds().toList()), - ); + final location = configManager.gameMode; + GameMode? gameMode; + if (location != null) { + gameMode = assetManager.getPack(location.namespace)?.getMode(location.id); + } + final data = SetonixData.fromMode( + location, + gameMode, + ).setInfo(GameInfo(packs: assetManager.getPackIds().toList())); return data; } diff --git a/server/pubspec.lock b/server/pubspec.lock index 2cb417f..4dedbe0 100644 --- a/server/pubspec.lock +++ b/server/pubspec.lock @@ -484,7 +484,7 @@ packages: path: "../api" relative: true source: path - version: "0.6.0" + version: "0.5.1" setonix_plugin: dependency: "direct main" description: diff --git a/server/pubspec.yaml b/server/pubspec.yaml index b54760f..ff33094 100644 --- a/server/pubspec.yaml +++ b/server/pubspec.yaml @@ -1,6 +1,6 @@ name: setonix_server description: The Linwood Setonix game server -version: 0.6.0 +version: 0.5.1 publish_to: none environment: