From a6327bd570f83b24f024e5b302456114ce726682 Mon Sep 17 00:00:00 2001 From: CodeDoctorDE Date: Sun, 22 Jun 2025 15:37:12 +0200 Subject: [PATCH 01/10] Add gamemode initialization --- api/lib/src/event/process/client.dart | 2 +- api/lib/src/event/server.dart | 6 +- api/lib/src/models/config.dart | 9 + api/lib/src/models/config.mapper.dart | 19 +- api/lib/src/models/data.dart | 226 ++++++++-------- api/lib/src/models/info.dart | 2 +- api/lib/src/models/info.mapper.dart | 14 +- api/lib/src/models/mode.dart | 2 - api/lib/src/models/mode.mapper.dart | 9 - api/lib/src/models/table.dart | 30 ++- api/lib/src/models/table.mapper.dart | 2 + api/pubspec.yaml | 2 +- app/lib/bloc/world/bloc.dart | 6 +- app/lib/main.dart | 3 +- app/pubspec.lock | 24 +- docs/package.json | 2 +- docs/pnpm-lock.yaml | 373 +++++++++++++------------- plugin/lib/setonix_plugin.dart | 8 +- plugin/lib/src/plugin.dart | 9 + server/lib/src/bloc.dart | 6 +- server/lib/src/config.dart | 6 + server/lib/src/server.dart | 7 +- 22 files changed, 420 insertions(+), 347 deletions(-) diff --git a/api/lib/src/event/process/client.dart b/api/lib/src/event/process/client.dart index f7cff5aa..8fea3ef4 100644 --- a/api/lib/src/event/process/client.dart +++ b/api/lib/src/event/process/client.dart @@ -295,7 +295,7 @@ Future processClientEvent( ? null : assetManager.getPack(location.namespace)?.getMode(location.id); return ServerResponse.builder( - WorldInitialized.fromMode(mode, state), channel); + WorldInitialized.fromMode(location, mode, state), channel); case AuthenticateRequest(): final challenge = challengeManager?.getChallenge(channel); if (challenge == null) return null; diff --git a/api/lib/src/event/server.dart b/api/lib/src/event/server.dart index 591b0b21..183dec24 100644 --- a/api/lib/src/event/server.dart +++ b/api/lib/src/event/server.dart @@ -27,11 +27,11 @@ final class WorldInitialized extends ServerWorldEvent this.clearUserInterface = false, }); - factory WorldInitialized.fromMode(GameMode? mode, WorldState state) => + factory WorldInitialized.fromMode( + ItemLocation? location, GameMode? mode, WorldState state) => WorldInitialized( clearUserInterface: true, - info: - state.info.copyWith(teams: mode?.teams ?? {}, script: mode?.script), + info: state.info.copyWith(teams: mode?.teams ?? {}, script: location), table: mode?.tables[state.tableName] ?? GameTable(), teamMembers: const {}, ); diff --git a/api/lib/src/models/config.dart b/api/lib/src/models/config.dart index ec35436c..467ed1b7 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() { @@ -116,6 +121,9 @@ final class SetonixConfig with SetonixConfigMappable { ? String.fromEnvironment(envEndpointSecret, defaultValue: defaultEndpointSecret) : null, + gameMode: bool.hasEnvironment(envGameMode) + ? String.fromEnvironment(envGameMode, defaultValue: defaultGameMode) + : null, ); } @@ -132,5 +140,6 @@ final class SetonixConfig with SetonixConfigMappable { whitelistEnabled: other.whitelistEnabled ?? whitelistEnabled, apiEndpoint: other.apiEndpoint ?? apiEndpoint, endpointSecret: other.endpointSecret ?? endpointSecret, + gameMode: other.gameMode ?? gameMode, ); } diff --git a/api/lib/src/models/config.mapper.dart b/api/lib/src/models/config.mapper.dart index 1ca651ea..dd564e04 100644 --- a/api/lib/src/models/config.mapper.dart +++ b/api/lib/src/models/config.mapper.dart @@ -56,6 +56,9 @@ class SetonixConfigMapper extends ClassMapperBase { static String? _$endpointSecret(SetonixConfig v) => v.endpointSecret; static const Field _f$endpointSecret = Field('endpointSecret', _$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 { @@ -71,6 +74,7 @@ class SetonixConfigMapper extends ClassMapperBase { #accountRequired: _f$accountRequired, #apiEndpoint: _f$apiEndpoint, #endpointSecret: _f$endpointSecret, + #gameMode: _f$gameMode, }; static SetonixConfig _instantiate(DecodingData data) { @@ -86,7 +90,8 @@ class SetonixConfigMapper extends ClassMapperBase { whitelistEnabled: data.dec(_f$whitelistEnabled), accountRequired: data.dec(_f$accountRequired), apiEndpoint: data.dec(_f$apiEndpoint), - endpointSecret: data.dec(_f$endpointSecret)); + endpointSecret: data.dec(_f$endpointSecret), + gameMode: data.dec(_f$gameMode)); } @override @@ -154,7 +159,8 @@ abstract class SetonixConfigCopyWith<$R, $In extends SetonixConfig, $Out> bool? whitelistEnabled, bool? accountRequired, String? apiEndpoint, - String? endpointSecret}); + String? endpointSecret, + String? gameMode}); SetonixConfigCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); } @@ -179,7 +185,8 @@ class _SetonixConfigCopyWithImpl<$R, $Out> Object? whitelistEnabled = $none, Object? accountRequired = $none, Object? apiEndpoint = $none, - Object? endpointSecret = $none}) => + Object? endpointSecret = $none, + Object? gameMode = $none}) => $apply(FieldCopyWithData({ if (host != $none) #host: host, if (port != $none) #port: port, @@ -192,7 +199,8 @@ class _SetonixConfigCopyWithImpl<$R, $Out> if (whitelistEnabled != $none) #whitelistEnabled: whitelistEnabled, if (accountRequired != $none) #accountRequired: accountRequired, if (apiEndpoint != $none) #apiEndpoint: apiEndpoint, - if (endpointSecret != $none) #endpointSecret: endpointSecret + if (endpointSecret != $none) #endpointSecret: endpointSecret, + if (gameMode != $none) #gameMode: gameMode })); @override SetonixConfig $make(CopyWithData data) => SetonixConfig( @@ -208,7 +216,8 @@ class _SetonixConfigCopyWithImpl<$R, $Out> data.get(#whitelistEnabled, or: $value.whitelistEnabled), accountRequired: data.get(#accountRequired, or: $value.accountRequired), apiEndpoint: data.get(#apiEndpoint, or: $value.apiEndpoint), - endpointSecret: data.get(#endpointSecret, or: $value.endpointSecret)); + endpointSecret: data.get(#endpointSecret, or: $value.endpointSecret), + gameMode: data.get(#gameMode, or: $value.gameMode)); @override SetonixConfigCopyWith<$R2, SetonixConfig, $Out2> $chain<$R2, $Out2>( diff --git a/api/lib/src/models/data.dart b/api/lib/src/models/data.dart index 03bdaa70..6327cfbf 100644 --- a/api/lib/src/models/data.dart +++ b/api/lib/src/models/data.dart @@ -32,13 +32,27 @@ class SetonixData extends ArchiveData { final String identifier; SetonixData(super.archive, {super.state, this.identifier = ''}); - SetonixData.empty() - : identifier = '', - super.empty(); + SetonixData.empty() : identifier = '', super.empty(); SetonixData.fromData(super.data, [String? identifier]) - : identifier = identifier ?? createPackIdentifier(data), - super.fromBytes(); + : 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'); @@ -50,10 +64,8 @@ class SetonixData extends ArchiveData { GameTable getTableOrDefault([String name = '']) => getTable(name) ?? GameTable(); - SetonixData setTable(GameTable table, [String name = '']) => setAsset( - '$kGameTablePath/$name.json', - utf8.encode(table.toJson()), - ); + SetonixData setTable(GameTable table, [String name = '']) => + setAsset('$kGameTablePath/$name.json', utf8.encode(table.toJson())); SetonixData removeTable(String name) => removeAsset('$kGameTablePath/$name.json'); @@ -66,10 +78,8 @@ class SetonixData extends ArchiveData { return utf8.decode(data); } - SetonixData setNote(String name, String content) => setAsset( - '$kGameNotesPath/$name.md', - utf8.encode(content), - ); + SetonixData setNote(String name, String content) => + setAsset('$kGameNotesPath/$name.md', utf8.encode(content)); SetonixData removeNote(String name) => removeAsset('$kGameNotesPath/$name.md'); @@ -136,18 +146,19 @@ class SetonixData extends ArchiveData { } } - PackItem? getFigureItem(String id, - [String namespace = '']) => - PackItem.wrap( - pack: this, - namespace: namespace, - id: id, - item: getFigure(id), - ); + PackItem? getFigureItem( + String id, [ + String namespace = '', + ]) => PackItem.wrap( + pack: this, + namespace: namespace, + id: id, + item: getFigure(id), + ); - Iterable> getFigureItems( - [String namespace = '']) => - getFigures().map((e) => getFigureItem(e, namespace)).nonNulls; + Iterable> getFigureItems([ + String namespace = '', + ]) => getFigures().map((e) => getFigureItem(e, namespace)).nonNulls; Iterable getBoards() => getAssets(kPackBoardsPath, true); @@ -176,10 +187,8 @@ class SetonixData extends ArchiveData { SetonixData removeBoard(String id) => removeAsset('$kPackBoardsPath/$id.json'); - SetonixData setBoard(String id, BoardDefinition definition) => setAsset( - '$kPackBoardsPath/$id.json', - utf8.encode(definition.toJson()), - ); + SetonixData setBoard(String id, BoardDefinition definition) => + setAsset('$kPackBoardsPath/$id.json', utf8.encode(definition.toJson())); Iterable getBackgrounds() => getAssets('$kPackBackgroundsPath/', true); @@ -195,18 +204,19 @@ class SetonixData extends ArchiveData { } } - PackItem? getBackgroundItem(String id, - [String namespace = '']) => - PackItem.wrap( - pack: this, - namespace: namespace, - id: id, - item: getBackground(id), - ); + PackItem? getBackgroundItem( + String id, [ + String namespace = '', + ]) => PackItem.wrap( + pack: this, + namespace: namespace, + id: id, + item: getBackground(id), + ); - Iterable> getBackgroundItems( - [String namespace = '']) => - getBackgrounds().map((e) => getBackgroundItem(e, namespace)).nonNulls; + Iterable> getBackgroundItems([ + String namespace = '', + ]) => getBackgrounds().map((e) => getBackgroundItem(e, namespace)).nonNulls; Uint8List? getTexture(String path) => getAsset('$kPackTexturesPath/$path'); @@ -231,27 +241,27 @@ class SetonixData extends ArchiveData { PackTranslation getTranslationOrDefault([String id = kFallbackLocale]) => getTranslation(id) ?? PackTranslation(); - SetonixData setMetadata(FileMetadata metadata) => setAsset( - kPackMetadataPath, - utf8.encode(metadata.toJson()), - ); + SetonixData setMetadata(FileMetadata metadata) => + setAsset(kPackMetadataPath, utf8.encode(metadata.toJson())); @override SetonixData updateState(ArchiveState state) => SetonixData(archive, state: state); - TranslationsStore getTranslationsStore( - {String? Function() getLocale = getDefaultLocale}) => - TranslationsStore( - translations: getAllTranslations(), - getLocale: getLocale, - ); + TranslationsStore getTranslationsStore({ + String? Function() getLocale = getDefaultLocale, + }) => TranslationsStore( + translations: getAllTranslations(), + getLocale: getLocale, + ); SetonixData removeFigure(String figure) => removeAsset('$kPackFiguresPath/$figure.json'); SetonixData setFigure(String figure, FigureDefinition definition) => setAsset( - '$kPackFiguresPath/$figure.json', utf8.encode(definition.toJson())); + '$kPackFiguresPath/$figure.json', + utf8.encode(definition.toJson()), + ); SetonixData removeDeck(String id) => removeAsset('$kPackDecksPath/$id.json'); @@ -262,29 +272,32 @@ class SetonixData extends ArchiveData { removeAsset('$kPackBackgroundsPath/$background.json'); SetonixData setBackground( - String background, BackgroundDefinition definition) => - setAsset('$kPackBackgroundsPath/$background.json', - utf8.encode(definition.toJson())); - - SetonixData setTranslation(PackTranslation translation, - [String locale = kFallbackLocale]) => - setAsset( - '$kPackTranslationsPath/$locale.json', - utf8.encode(translation.toJson()), - ); + String background, + BackgroundDefinition definition, + ) => setAsset( + '$kPackBackgroundsPath/$background.json', + utf8.encode(definition.toJson()), + ); + + SetonixData setTranslation( + PackTranslation translation, [ + String locale = kFallbackLocale, + ]) => setAsset( + '$kPackTranslationsPath/$locale.json', + utf8.encode(translation.toJson()), + ); Iterable getTextures() => getAssets(kPackTexturesPath); - Map getTexturesData() => - Map.fromEntries(getTextures().map((e) { - final data = getTexture(e); - if (data == null) return null; - return MapEntry(e, data); - }).nonNulls); - - SetonixData setTexture(String texture, Uint8List data) => setAsset( - '$kPackTexturesPath/$texture', - data, - ); + Map getTexturesData() => Map.fromEntries( + getTextures().map((e) { + final data = getTexture(e); + if (data == null) return null; + return MapEntry(e, data); + }).nonNulls, + ); + + SetonixData setTexture(String texture, Uint8List data) => + setAsset('$kPackTexturesPath/$texture', data); SetonixData removeTexture(String texture) => removeAsset('$kPackTexturesPath/$texture'); @@ -308,24 +321,28 @@ class SetonixData extends ArchiveData { } } - Map getModesData() => Map.fromEntries(getModes().map((e) { - final mode = getMode(e); - if (mode == null) return null; - return MapEntry(e, mode); - }).nonNulls); + Map getModesData() => Map.fromEntries( + getModes().map((e) { + final mode = getMode(e); + if (mode == null) return null; + return MapEntry(e, mode); + }).nonNulls, + ); SetonixData addAccount(SetonixAccount setonixAccount) { final accountId = setonixAccount.name; return setAsset( - '$kPackAccountsPath/$accountId.key', setonixAccount.privateKey) - .setAsset( - '$kPackAccountsPath/$accountId.pub', setonixAccount.publicKey); + '$kPackAccountsPath/$accountId.key', + setonixAccount.privateKey, + ).setAsset('$kPackAccountsPath/$accountId.pub', setonixAccount.publicKey); } Iterable getAccounts() sync* { const kKeySuffix = '.key'; - final privateKeys = getAssets('$kPackAccountsPath/', true) - .where((e) => e.endsWith(kKeySuffix)); + final privateKeys = getAssets( + '$kPackAccountsPath/', + true, + ).where((e) => e.endsWith(kKeySuffix)); for (final path in privateKeys) { final name = path.substring(0, path.length - kKeySuffix.length); final privateKey = getAsset(path); @@ -346,7 +363,7 @@ class SetonixFile { final Uint8List data; SetonixFile(this.data, [String? identifier]) - : identifier = identifier ?? createPackIdentifier(data); + : identifier = identifier ?? createPackIdentifier(data); SetonixData load() => SetonixData.fromData(data, identifier); } @@ -361,28 +378,22 @@ final class PackItem { final ItemLocation location; final T item; - PackItem({ - required this.pack, - required this.location, - required this.item, - }); - - factory PackItem.fromRaw( - {required SetonixData pack, - required String namespace, - required String path, - required T item}) => - PackItem( - item: item, - pack: pack, - location: ItemLocation(namespace, path), - ); - - static PackItem? wrap( - {required SetonixData pack, - required String namespace, - T? item, - String? id}) { + PackItem({required this.pack, required this.location, required this.item}); + + factory PackItem.fromRaw({ + required SetonixData pack, + required String namespace, + required String path, + required T item, + }) => + PackItem(item: item, pack: pack, location: ItemLocation(namespace, path)); + + static PackItem? wrap({ + required SetonixData pack, + required String namespace, + T? item, + String? id, + }) { if (item == null || id == null) return null; return PackItem( pack: pack, @@ -394,9 +405,6 @@ final class PackItem { String get namespace => location.namespace; String get id => location.id; - PackItem withItem(E backgroundTranslation) => PackItem( - pack: pack, - location: location, - item: backgroundTranslation, - ); + PackItem withItem(E backgroundTranslation) => + PackItem(pack: pack, location: location, item: backgroundTranslation); } diff --git a/api/lib/src/models/info.dart b/api/lib/src/models/info.dart index cc7ed024..14472942 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 {}, diff --git a/api/lib/src/models/info.mapper.dart b/api/lib/src/models/info.mapper.dart index 50d3119e..6afd5255 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!; } @@ -109,8 +110,8 @@ class GameInfoMapper extends ClassMapperBase { static List _$packs(GameInfo v) => v.packs; static const Field> _f$packs = Field('packs', _$packs, opt: true, def: const []); - static String? _$script(GameInfo v) => v.script; - static const Field _f$script = + static ItemLocation? _$script(GameInfo v) => v.script; + static const Field _f$script = Field('script', _$script, opt: true); @override @@ -180,7 +181,11 @@ 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); } @@ -201,6 +206,9 @@ class _GameInfoCopyWithImpl<$R, $Out> ListCopyWith($value.packs, (v, t) => ObjectCopyWith(v, $identity, t), (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 fec61785..d84c75d4 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 ec70189c..7a8d3263 100644 --- a/api/lib/src/models/mode.mapper.dart +++ b/api/lib/src/models/mode.mapper.dart @@ -27,9 +27,6 @@ class GameModeMapper extends ClassMapperBase { static Map _$tables(GameMode v) => v.tables; static const Field> _f$tables = Field('tables', _$tables, 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', _$teams, opt: true, def: const {}); @@ -38,7 +35,6 @@ class GameModeMapper extends ClassMapperBase { final MappableFields fields = const { #script: _f$script, #tables: _f$tables, - #tableName: _f$tableName, #teams: _f$teams, }; @@ -46,7 +42,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)); } @@ -107,7 +102,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); } @@ -133,19 +127,16 @@ 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 })); @override 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)); @override diff --git a/api/lib/src/models/table.dart b/api/lib/src/models/table.dart index df57c0aa..ece2e58f 100644 --- a/api/lib/src/models/table.dart +++ b/api/lib/src/models/table.dart @@ -125,7 +125,7 @@ class BoardTile with BoardTileMappable { BoardTile(this.asset, this.tile); } -@MappableClass() +@MappableClass(hook: ItemLocationHook()) class ItemLocation with ItemLocationMappable { final String namespace, id; @@ -139,6 +139,34 @@ 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 4cfcd196..c5a8ac2c 100644 --- a/api/lib/src/models/table.mapper.dart +++ b/api/lib/src/models/table.mapper.dart @@ -518,6 +518,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/pubspec.yaml b/api/pubspec.yaml index 9f58782d..e2cc05dc 100644 --- a/api/pubspec.yaml +++ b/api/pubspec.yaml @@ -5,7 +5,7 @@ publish_to: none # repository: https://github.com/my_org/my_repo environment: - sdk: ">=3.5.1 <4.0.0" + sdk: ">=3.8.0 <4.0.0" # Add regular dependencies here. dependencies: diff --git a/app/lib/bloc/world/bloc.dart b/app/lib/bloc/world/bloc.dart index 9c3fe216..a479a5e6 100644 --- a/app/lib/bloc/world/bloc.dart +++ b/app/lib/bloc/world/bloc.dart @@ -200,10 +200,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 dc9766dc..904b75e8 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: [ diff --git a/app/pubspec.lock b/app/pubspec.lock index 10672fe1..6812bd6e 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -85,10 +85,10 @@ packages: dependency: transitive description: name: build - sha256: "8295b0b6dfe00499b786718f2936a56b5e0d56d169c528472c8c1908f2b1e3ee" + sha256: "74273591bd8b7f82eeb1f191c1b65a6576535bbfd5ca3722778b07d5702d33cc" url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.5.3" build_cli_annotations: dependency: transitive description: @@ -117,26 +117,26 @@ packages: dependency: transitive description: name: build_resolvers - sha256: "57fe2f9149b01d52fcd0ea7de17083739b5cf9e040dedb4e24a17c628c1e9caf" + sha256: badce70566085f2e87434531c4a6bc8e833672f755fc51146d612245947e91c9 url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.5.3" build_runner: dependency: "direct dev" description: name: build_runner - sha256: "9b196d7b629c5317dff3ec83c7e98840b773cee3b9339b646fe487048d2ebc74" + sha256: b9070a4127033777c0e63195f6f117ed16a351ed676f6313b095cf4f328c0b82 url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.5.3" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: dae6a8a5cbef6866cdfa0d96df4b0d85b9086897096270a405a44d946dafffba + sha256: "1cdfece3eeb3f1263f7dbf5bcc0cba697bd0c22d2c866cb4b578c954dbb09bcf" url: "https://pub.dev" source: hosted - version: "9.0.0" + version: "9.1.1" built_collection: dependency: transitive description: @@ -541,10 +541,10 @@ packages: dependency: "direct main" description: name: flutter_svg - sha256: d44bf546b13025ec7353091516f6881f1d4c633993cb109c3916c3a0159dadf1 + sha256: cd57f7969b4679317c17af6fd16ee233c1e60a82ed209d8a475c54fd6fd6f845 url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.2.0" flutter_test: dependency: "direct dev" description: flutter @@ -580,10 +580,10 @@ packages: dependency: "direct main" description: name: go_router - sha256: f222e12c5360b0c90aae5d33e6b351a6789d4aa1c97b32691b4a30946fc91813 + sha256: "02ff498f6279470ff7f60c998a69b872f26696ceec237c8402e63a2133868ddf" url: "https://pub.dev" source: hosted - version: "15.2.0" + version: "15.2.3" graphs: dependency: transitive description: diff --git a/docs/package.json b/docs/package.json index d00c2626..a0d4e3c7 100644 --- a/docs/package.json +++ b/docs/package.json @@ -16,7 +16,7 @@ "@phosphor-icons/react": "^2.1.10", "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", - "astro": "^5.9.3", + "astro": "^5.10.0", "react": "^19.1.0", "react-dom": "^19.1.0", "remark-gemoji": "^8.0.0", diff --git a/docs/pnpm-lock.yaml b/docs/pnpm-lock.yaml index 34b260c1..bd5a5e18 100644 --- a/docs/pnpm-lock.yaml +++ b/docs/pnpm-lock.yaml @@ -13,10 +13,10 @@ importers: version: 0.9.4(typescript@5.8.3) '@astrojs/react': specifier: ^4.3.0 - version: 4.3.0(@types/node@24.0.3)(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(jiti@2.4.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.89.2)(terser@5.42.0)(yaml@2.8.0) + version: 4.3.0(@types/node@24.0.3)(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(jiti@2.4.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0) '@astrojs/starlight': specifier: ^0.34.4 - version: 0.34.4(astro@5.9.3(@types/node@24.0.3)(jiti@2.4.2)(rollup@2.79.2)(sass@1.89.2)(terser@5.42.0)(typescript@5.8.3)(yaml@2.8.0)) + version: 0.34.4(astro@5.10.0(@types/node@24.0.3)(jiti@2.4.2)(rollup@2.79.2)(sass@1.89.2)(terser@5.43.1)(typescript@5.8.3)(yaml@2.8.0)) '@phosphor-icons/react': specifier: ^2.1.10 version: 2.1.10(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -27,8 +27,8 @@ importers: specifier: ^19.1.6 version: 19.1.6(@types/react@19.1.8) astro: - specifier: ^5.9.3 - version: 5.9.3(@types/node@24.0.3)(jiti@2.4.2)(rollup@2.79.2)(sass@1.89.2)(terser@5.42.0)(typescript@5.8.3)(yaml@2.8.0) + specifier: ^5.10.0 + version: 5.10.0(@types/node@24.0.3)(jiti@2.4.2)(rollup@2.79.2)(sass@1.89.2)(terser@5.43.1)(typescript@5.8.3)(yaml@2.8.0) react: specifier: ^19.1.0 version: 19.1.0 @@ -47,7 +47,7 @@ importers: devDependencies: '@vite-pwa/astro': specifier: ^1.1.0 - version: 1.1.0(astro@5.9.3(@types/node@24.0.3)(jiti@2.4.2)(rollup@2.79.2)(sass@1.89.2)(terser@5.42.0)(typescript@5.8.3)(yaml@2.8.0))(vite-plugin-pwa@1.0.0(vite@6.3.5(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(terser@5.42.0)(yaml@2.8.0))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0)) + version: 1.1.0(astro@5.10.0(@types/node@24.0.3)(jiti@2.4.2)(rollup@2.79.2)(sass@1.89.2)(terser@5.43.1)(typescript@5.8.3)(yaml@2.8.0))(vite-plugin-pwa@1.0.0(vite@6.3.5(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0)) sass: specifier: ^1.89.2 version: 1.89.2 @@ -56,7 +56,7 @@ importers: version: 0.34.2 vite-plugin-pwa: specifier: ^1.0.0 - version: 1.0.0(vite@6.3.5(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(terser@5.42.0)(yaml@2.8.0))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0) + version: 1.0.0(vite@6.3.5(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0) workbox-window: specifier: ^7.3.0 version: 7.3.0 @@ -1244,8 +1244,8 @@ packages: peerDependencies: rollup: ^1.20.0||^2.0.0 - '@rollup/pluginutils@5.1.4': - resolution: {integrity: sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==} + '@rollup/pluginutils@5.2.0': + resolution: {integrity: sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw==} engines: {node: '>=14.0.0'} peerDependencies: rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 @@ -1253,123 +1253,123 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.43.0': - resolution: {integrity: sha512-Krjy9awJl6rKbruhQDgivNbD1WuLb8xAclM4IR4cN5pHGAs2oIMMQJEiC3IC/9TZJ+QZkmZhlMO/6MBGxPidpw==} + '@rollup/rollup-android-arm-eabi@4.44.0': + resolution: {integrity: sha512-xEiEE5oDW6tK4jXCAyliuntGR+amEMO7HLtdSshVuhFnKTYoeYMyXQK7pLouAJJj5KHdwdn87bfHAR2nSdNAUA==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.43.0': - resolution: {integrity: sha512-ss4YJwRt5I63454Rpj+mXCXicakdFmKnUNxr1dLK+5rv5FJgAxnN7s31a5VchRYxCFWdmnDWKd0wbAdTr0J5EA==} + '@rollup/rollup-android-arm64@4.44.0': + resolution: {integrity: sha512-uNSk/TgvMbskcHxXYHzqwiyBlJ/lGcv8DaUfcnNwict8ba9GTTNxfn3/FAoFZYgkaXXAdrAA+SLyKplyi349Jw==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.43.0': - resolution: {integrity: sha512-eKoL8ykZ7zz8MjgBenEF2OoTNFAPFz1/lyJ5UmmFSz5jW+7XbH1+MAgCVHy72aG59rbuQLcJeiMrP8qP5d/N0A==} + '@rollup/rollup-darwin-arm64@4.44.0': + resolution: {integrity: sha512-VGF3wy0Eq1gcEIkSCr8Ke03CWT+Pm2yveKLaDvq51pPpZza3JX/ClxXOCmTYYq3us5MvEuNRTaeyFThCKRQhOA==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.43.0': - resolution: {integrity: sha512-SYwXJgaBYW33Wi/q4ubN+ldWC4DzQY62S4Ll2dgfr/dbPoF50dlQwEaEHSKrQdSjC6oIe1WgzosoaNoHCdNuMg==} + '@rollup/rollup-darwin-x64@4.44.0': + resolution: {integrity: sha512-fBkyrDhwquRvrTxSGH/qqt3/T0w5Rg0L7ZIDypvBPc1/gzjJle6acCpZ36blwuwcKD/u6oCE/sRWlUAcxLWQbQ==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.43.0': - resolution: {integrity: sha512-SV+U5sSo0yujrjzBF7/YidieK2iF6E7MdF6EbYxNz94lA+R0wKl3SiixGyG/9Klab6uNBIqsN7j4Y/Fya7wAjQ==} + '@rollup/rollup-freebsd-arm64@4.44.0': + resolution: {integrity: sha512-u5AZzdQJYJXByB8giQ+r4VyfZP+walV+xHWdaFx/1VxsOn6eWJhK2Vl2eElvDJFKQBo/hcYIBg/jaKS8ZmKeNQ==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.43.0': - resolution: {integrity: sha512-J7uCsiV13L/VOeHJBo5SjasKiGxJ0g+nQTrBkAsmQBIdil3KhPnSE9GnRon4ejX1XDdsmK/l30IYLiAaQEO0Cg==} + '@rollup/rollup-freebsd-x64@4.44.0': + resolution: {integrity: sha512-qC0kS48c/s3EtdArkimctY7h3nHicQeEUdjJzYVJYR3ct3kWSafmn6jkNCA8InbUdge6PVx6keqjk5lVGJf99g==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.43.0': - resolution: {integrity: sha512-gTJ/JnnjCMc15uwB10TTATBEhK9meBIY+gXP4s0sHD1zHOaIh4Dmy1X9wup18IiY9tTNk5gJc4yx9ctj/fjrIw==} + '@rollup/rollup-linux-arm-gnueabihf@4.44.0': + resolution: {integrity: sha512-x+e/Z9H0RAWckn4V2OZZl6EmV0L2diuX3QB0uM1r6BvhUIv6xBPL5mrAX2E3e8N8rEHVPwFfz/ETUbV4oW9+lQ==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.43.0': - resolution: {integrity: sha512-ZJ3gZynL1LDSIvRfz0qXtTNs56n5DI2Mq+WACWZ7yGHFUEirHBRt7fyIk0NsCKhmRhn7WAcjgSkSVVxKlPNFFw==} + '@rollup/rollup-linux-arm-musleabihf@4.44.0': + resolution: {integrity: sha512-1exwiBFf4PU/8HvI8s80icyCcnAIB86MCBdst51fwFmH5dyeoWVPVgmQPcKrMtBQ0W5pAs7jBCWuRXgEpRzSCg==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.43.0': - resolution: {integrity: sha512-8FnkipasmOOSSlfucGYEu58U8cxEdhziKjPD2FIa0ONVMxvl/hmONtX/7y4vGjdUhjcTHlKlDhw3H9t98fPvyA==} + '@rollup/rollup-linux-arm64-gnu@4.44.0': + resolution: {integrity: sha512-ZTR2mxBHb4tK4wGf9b8SYg0Y6KQPjGpR4UWwTFdnmjB4qRtoATZ5dWn3KsDwGa5Z2ZBOE7K52L36J9LueKBdOQ==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.43.0': - resolution: {integrity: sha512-KPPyAdlcIZ6S9C3S2cndXDkV0Bb1OSMsX0Eelr2Bay4EsF9yi9u9uzc9RniK3mcUGCLhWY9oLr6er80P5DE6XA==} + '@rollup/rollup-linux-arm64-musl@4.44.0': + resolution: {integrity: sha512-GFWfAhVhWGd4r6UxmnKRTBwP1qmModHtd5gkraeW2G490BpFOZkFtem8yuX2NyafIP/mGpRJgTJ2PwohQkUY/Q==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loongarch64-gnu@4.43.0': - resolution: {integrity: sha512-HPGDIH0/ZzAZjvtlXj6g+KDQ9ZMHfSP553za7o2Odegb/BEfwJcR0Sw0RLNpQ9nC6Gy8s+3mSS9xjZ0n3rhcYg==} + '@rollup/rollup-linux-loongarch64-gnu@4.44.0': + resolution: {integrity: sha512-xw+FTGcov/ejdusVOqKgMGW3c4+AgqrfvzWEVXcNP6zq2ue+lsYUgJ+5Rtn/OTJf7e2CbgTFvzLW2j0YAtj0Gg==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.43.0': - resolution: {integrity: sha512-gEmwbOws4U4GLAJDhhtSPWPXUzDfMRedT3hFMyRAvM9Mrnj+dJIFIeL7otsv2WF3D7GrV0GIewW0y28dOYWkmw==} + '@rollup/rollup-linux-powerpc64le-gnu@4.44.0': + resolution: {integrity: sha512-bKGibTr9IdF0zr21kMvkZT4K6NV+jjRnBoVMt2uNMG0BYWm3qOVmYnXKzx7UhwrviKnmK46IKMByMgvpdQlyJQ==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.43.0': - resolution: {integrity: sha512-XXKvo2e+wFtXZF/9xoWohHg+MuRnvO29TI5Hqe9xwN5uN8NKUYy7tXUG3EZAlfchufNCTHNGjEx7uN78KsBo0g==} + '@rollup/rollup-linux-riscv64-gnu@4.44.0': + resolution: {integrity: sha512-vV3cL48U5kDaKZtXrti12YRa7TyxgKAIDoYdqSIOMOFBXqFj2XbChHAtXquEn2+n78ciFgr4KIqEbydEGPxXgA==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.43.0': - resolution: {integrity: sha512-ruf3hPWhjw6uDFsOAzmbNIvlXFXlBQ4nk57Sec8E8rUxs/AI4HD6xmiiasOOx/3QxS2f5eQMKTAwk7KHwpzr/Q==} + '@rollup/rollup-linux-riscv64-musl@4.44.0': + resolution: {integrity: sha512-TDKO8KlHJuvTEdfw5YYFBjhFts2TR0VpZsnLLSYmB7AaohJhM8ctDSdDnUGq77hUh4m/djRafw+9zQpkOanE2Q==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.43.0': - resolution: {integrity: sha512-QmNIAqDiEMEvFV15rsSnjoSmO0+eJLoKRD9EAa9rrYNwO/XRCtOGM3A5A0X+wmG+XRrw9Fxdsw+LnyYiZWWcVw==} + '@rollup/rollup-linux-s390x-gnu@4.44.0': + resolution: {integrity: sha512-8541GEyktXaw4lvnGp9m84KENcxInhAt6vPWJ9RodsB/iGjHoMB2Pp5MVBCiKIRxrxzJhGCxmNzdu+oDQ7kwRA==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.43.0': - resolution: {integrity: sha512-jAHr/S0iiBtFyzjhOkAics/2SrXE092qyqEg96e90L3t9Op8OTzS6+IX0Fy5wCt2+KqeHAkti+eitV0wvblEoQ==} + '@rollup/rollup-linux-x64-gnu@4.44.0': + resolution: {integrity: sha512-iUVJc3c0o8l9Sa/qlDL2Z9UP92UZZW1+EmQ4xfjTc1akr0iUFZNfxrXJ/R1T90h/ILm9iXEY6+iPrmYB3pXKjw==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.43.0': - resolution: {integrity: sha512-3yATWgdeXyuHtBhrLt98w+5fKurdqvs8B53LaoKD7P7H7FKOONLsBVMNl9ghPQZQuYcceV5CDyPfyfGpMWD9mQ==} + '@rollup/rollup-linux-x64-musl@4.44.0': + resolution: {integrity: sha512-PQUobbhLTQT5yz/SPg116VJBgz+XOtXt8D1ck+sfJJhuEsMj2jSej5yTdp8CvWBSceu+WW+ibVL6dm0ptG5fcA==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.43.0': - resolution: {integrity: sha512-wVzXp2qDSCOpcBCT5WRWLmpJRIzv23valvcTwMHEobkjippNf+C3ys/+wf07poPkeNix0paTNemB2XrHr2TnGw==} + '@rollup/rollup-win32-arm64-msvc@4.44.0': + resolution: {integrity: sha512-M0CpcHf8TWn+4oTxJfh7LQuTuaYeXGbk0eageVjQCKzYLsajWS/lFC94qlRqOlyC2KvRT90ZrfXULYmukeIy7w==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.43.0': - resolution: {integrity: sha512-fYCTEyzf8d+7diCw8b+asvWDCLMjsCEA8alvtAutqJOJp/wL5hs1rWSqJ1vkjgW0L2NB4bsYJrpKkiIPRR9dvw==} + '@rollup/rollup-win32-ia32-msvc@4.44.0': + resolution: {integrity: sha512-3XJ0NQtMAXTWFW8FqZKcw3gOQwBtVWP/u8TpHP3CRPXD7Pd6s8lLdH3sHWh8vqKCyyiI8xW5ltJScQmBU9j7WA==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.43.0': - resolution: {integrity: sha512-SnGhLiE5rlK0ofq8kzuDkM0g7FN1s5VYY+YSMTibP7CqShxCQvqtNxTARS4xX4PFJfHjG0ZQYX9iGzI3FQh5Aw==} + '@rollup/rollup-win32-x64-msvc@4.44.0': + resolution: {integrity: sha512-Q2Mgwt+D8hd5FIPUuPDsvPR7Bguza6yTkJxspDGkZj7tBRn2y4KSWYuIXpftFSjBra76TbKerCV7rgFPQrn+wQ==} cpu: [x64] os: [win32] - '@shikijs/core@3.6.0': - resolution: {integrity: sha512-9By7Xb3olEX0o6UeJyPLI1PE1scC4d3wcVepvtv2xbuN9/IThYN4Wcwh24rcFeASzPam11MCq8yQpwwzCgSBRw==} + '@shikijs/core@3.7.0': + resolution: {integrity: sha512-yilc0S9HvTPyahHpcum8eonYrQtmGTU0lbtwxhA6jHv4Bm1cAdlPFRCJX4AHebkCm75aKTjjRAW+DezqD1b/cg==} - '@shikijs/engine-javascript@3.6.0': - resolution: {integrity: sha512-7YnLhZG/TU05IHMG14QaLvTW/9WiK8SEYafceccHUSXs2Qr5vJibUwsDfXDLmRi0zHdzsxrGKpSX6hnqe0k8nA==} + '@shikijs/engine-javascript@3.7.0': + resolution: {integrity: sha512-0t17s03Cbv+ZcUvv+y33GtX75WBLQELgNdVghnsdhTgU3hVcWcMsoP6Lb0nDTl95ZJfbP1mVMO0p3byVh3uuzA==} - '@shikijs/engine-oniguruma@3.6.0': - resolution: {integrity: sha512-nmOhIZ9yT3Grd+2plmW/d8+vZ2pcQmo/UnVwXMUXAKTXdi+LK0S08Ancrz5tQQPkxvjBalpMW2aKvwXfelauvA==} + '@shikijs/engine-oniguruma@3.7.0': + resolution: {integrity: sha512-5BxcD6LjVWsGu4xyaBC5bu8LdNgPCVBnAkWTtOCs/CZxcB22L8rcoWfv7Hh/3WooVjBZmFtyxhgvkQFedPGnFw==} - '@shikijs/langs@3.6.0': - resolution: {integrity: sha512-IdZkQJaLBu1LCYCwkr30hNuSDfllOT8RWYVZK1tD2J03DkiagYKRxj/pDSl8Didml3xxuyzUjgtioInwEQM/TA==} + '@shikijs/langs@3.7.0': + resolution: {integrity: sha512-1zYtdfXLr9xDKLTGy5kb7O0zDQsxXiIsw1iIBcNOO8Yi5/Y1qDbJ+0VsFoqTlzdmneO8Ij35g7QKF8kcLyznCQ==} - '@shikijs/themes@3.6.0': - resolution: {integrity: sha512-Fq2j4nWr1DF4drvmhqKq8x5vVQ27VncF8XZMBuHuQMZvUSS3NBgpqfwz/FoGe36+W6PvniZ1yDlg2d4kmYDU6w==} + '@shikijs/themes@3.7.0': + resolution: {integrity: sha512-VJx8497iZPy5zLiiCTSIaOChIcKQwR0FebwE9S3rcN0+J/GTWwQ1v/bqhTbpbY3zybPKeO8wdammqkpXc4NVjQ==} - '@shikijs/types@3.6.0': - resolution: {integrity: sha512-cLWFiToxYu0aAzJqhXTQsFiJRTFDAGl93IrMSBNaGSzs7ixkLfdG6pH11HipuWFGW5vyx4X47W8HDQ7eSrmBUg==} + '@shikijs/types@3.7.0': + resolution: {integrity: sha512-MGaLeaRlSWpnP0XSAum3kP3a8vtcTsITqoEPYdt3lQG3YCdQH4DnEhodkYcNMcU0uW0RffhoD1O3e0vG5eSBBg==} '@shikijs/vscode-textmate@10.0.2': resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} @@ -1401,9 +1401,6 @@ packages: '@types/estree@0.0.39': resolution: {integrity: sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==} - '@types/estree@1.0.7': - resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} - '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} @@ -1568,8 +1565,8 @@ packages: peerDependencies: astro: ^4.0.0-beta || ^5.0.0-beta || ^3.3.0 - astro@5.9.3: - resolution: {integrity: sha512-VReZrpUa/3rfeiVvsQ1A2M3ujDPI+pDGIYOMtXPEZwut8tZoEyealXXLjitgCsJ+3dunKGZbg4Eak6i+r0vniw==} + astro@5.10.0: + resolution: {integrity: sha512-g/t54kVzQnFVijs+GbbbX/NBAFTl/3yNAEA/AQYq4FumLLVv7n4BIF+jKhcPGn9iFGyT1Cjvr7KB/qYyNvHEIg==} engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0'} hasBin: true @@ -1672,8 +1669,8 @@ packages: resolution: {integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==} engines: {node: '>=16'} - caniuse-lite@1.0.30001723: - resolution: {integrity: sha512-1R/elMjtehrFejxwmexeXAtae5UO9iSyFn6G/I806CYC/BLyyBk1EPhrKBkWhy6wM6Xnm47dSJQec+tLJ39WHw==} + caniuse-lite@1.0.30001724: + resolution: {integrity: sha512-WqJo7p0TbHDOythNTqYujmaJTvtYRZrjpP8TCvH6Vb9CYJerJNKamKzIWOM4BkQatWj9H2lYulpdAQNBe7QhNA==} ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} @@ -1885,8 +1882,8 @@ packages: engines: {node: '>=0.10.0'} hasBin: true - electron-to-chromium@1.5.167: - resolution: {integrity: sha512-LxcRvnYO5ez2bMOFpbuuVuAI5QNeY1ncVytE/KXaL6ZNfzX1yPlAO0nSOyIHx2fVAuUprMqPs/TdVhUFZy7SIQ==} + electron-to-chromium@1.5.171: + resolution: {integrity: sha512-scWpzXEJEMrGJa4Y6m/tVotb0WuvNmasv3wWVzUAeCgKU0ToFOhUW6Z+xWnRQANMYGxN4ngJXIThgBJOqzVPCQ==} emmet@2.4.11: resolution: {integrity: sha512-23QPJB3moh/U9sT4rQzGgeyyGIrcM+GH5uVYg2C6wZIxAIJq7Ng3QLT79tl8FUwDXhyq9SusfknOrofAKqvgyQ==} @@ -3028,8 +3025,8 @@ packages: engines: {node: '>=10.0.0'} hasBin: true - rollup@4.43.0: - resolution: {integrity: sha512-wdN2Kd3Twh8MAEOEJZsuxuLKCsBEo4PVNLK6tQWAn10VhsVewQLzcucMgLolRlhFybGxfclbPeEYBaP6RvUFGg==} + rollup@4.44.0: + resolution: {integrity: sha512-qHcdEzLCiktQIfwBq420pn2dP+30uzqYxv9ETm91wdt2R9AFcWfjNAmje4NWlnCIQ5RMTzVf0ZyisOKqHR6RwA==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -3094,8 +3091,8 @@ packages: resolution: {integrity: sha512-lszvBmB9QURERtyKT2bNmsgxXK0ShJrL/fvqlonCo7e6xBF8nT8xU6pW+PMIbLsz0RxQk3rgH9kd8UmvOzlMJg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - shiki@3.6.0: - resolution: {integrity: sha512-tKn/Y0MGBTffQoklaATXmTqDU02zx8NYBGQ+F6gy87/YjKbizcLd+Cybh/0ZtOBX9r1NEnAy/GTRDKtOsc1L9w==} + shiki@3.7.0: + resolution: {integrity: sha512-ZcI4UT9n6N2pDuM2n3Jbk0sR4Swzq43nLPgS/4h0E3B/NrFn2HKElrDtceSf8Zx/OWYOo7G1SAtBLypCp+YXqg==} side-channel-list@1.0.0: resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} @@ -3229,8 +3226,8 @@ packages: resolution: {integrity: sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw==} engines: {node: '>=10'} - terser@5.42.0: - resolution: {integrity: sha512-UYCvU9YQW2f/Vwl+P0GfhxJxbUGLwd+5QrrGgLajzWAtC/23AX0vcise32kkP7Eu0Wu9VlzzHAXkLObgjQfFlQ==} + terser@5.43.1: + resolution: {integrity: sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==} engines: {node: '>=10'} hasBin: true @@ -3532,10 +3529,10 @@ packages: yaml: optional: true - vitefu@1.0.6: - resolution: {integrity: sha512-+Rex1GlappUyNN6UfwbVZne/9cYC4+R2XDk9xkNXBKMw6HQagdX9PgZ8V2v1WUSK1wfBLp7qbI1+XSNIlB1xmA==} + vitefu@1.0.7: + resolution: {integrity: sha512-eRWXLBbJjW3X5z5P5IHcSm2yYbYRPb2kQuc+oqsbAl99WB5kVsPbiiox+cymo8twTzifA6itvhr2CmjnaZZp0Q==} peerDependencies: - vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0 peerDependenciesMeta: vite: optional: true @@ -3799,8 +3796,8 @@ packages: typescript: ^4.9.4 || ^5.0.2 zod: ^3 - zod@3.25.64: - resolution: {integrity: sha512-hbP9FpSZf7pkS7hRVUrOjhwKJNyampPgtXKc3AN6DsWtoHsg2Sb4SQaS4Tcay380zSwd2VPo9G9180emBACp5g==} + zod@3.25.67: + resolution: {integrity: sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==} zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} @@ -3873,7 +3870,7 @@ snapshots: remark-parse: 11.0.0 remark-rehype: 11.1.2 remark-smartypants: 3.0.2 - shiki: 3.6.0 + shiki: 3.7.0 smol-toml: 1.3.4 unified: 11.0.5 unist-util-remove-position: 5.0.0 @@ -3883,12 +3880,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@astrojs/mdx@4.3.0(astro@5.9.3(@types/node@24.0.3)(jiti@2.4.2)(rollup@2.79.2)(sass@1.89.2)(terser@5.42.0)(typescript@5.8.3)(yaml@2.8.0))': + '@astrojs/mdx@4.3.0(astro@5.10.0(@types/node@24.0.3)(jiti@2.4.2)(rollup@2.79.2)(sass@1.89.2)(terser@5.43.1)(typescript@5.8.3)(yaml@2.8.0))': dependencies: '@astrojs/markdown-remark': 6.3.2 '@mdx-js/mdx': 3.1.0(acorn@8.15.0) acorn: 8.15.0 - astro: 5.9.3(@types/node@24.0.3)(jiti@2.4.2)(rollup@2.79.2)(sass@1.89.2)(terser@5.42.0)(typescript@5.8.3)(yaml@2.8.0) + astro: 5.10.0(@types/node@24.0.3)(jiti@2.4.2)(rollup@2.79.2)(sass@1.89.2)(terser@5.43.1)(typescript@5.8.3)(yaml@2.8.0) es-module-lexer: 1.7.0 estree-util-visit: 2.0.0 hast-util-to-html: 9.0.5 @@ -3906,15 +3903,15 @@ snapshots: dependencies: prismjs: 1.30.0 - '@astrojs/react@4.3.0(@types/node@24.0.3)(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(jiti@2.4.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.89.2)(terser@5.42.0)(yaml@2.8.0)': + '@astrojs/react@4.3.0(@types/node@24.0.3)(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(jiti@2.4.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0)': dependencies: '@types/react': 19.1.8 '@types/react-dom': 19.1.6(@types/react@19.1.8) - '@vitejs/plugin-react': 4.5.2(vite@6.3.5(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(terser@5.42.0)(yaml@2.8.0)) + '@vitejs/plugin-react': 4.5.2(vite@6.3.5(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0)) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) ultrahtml: 1.6.0 - vite: 6.3.5(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(terser@5.42.0)(yaml@2.8.0) + vite: 6.3.5(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0) transitivePeerDependencies: - '@types/node' - jiti @@ -3933,19 +3930,19 @@ snapshots: dependencies: sitemap: 8.0.0 stream-replace-string: 2.0.0 - zod: 3.25.64 + zod: 3.25.67 - '@astrojs/starlight@0.34.4(astro@5.9.3(@types/node@24.0.3)(jiti@2.4.2)(rollup@2.79.2)(sass@1.89.2)(terser@5.42.0)(typescript@5.8.3)(yaml@2.8.0))': + '@astrojs/starlight@0.34.4(astro@5.10.0(@types/node@24.0.3)(jiti@2.4.2)(rollup@2.79.2)(sass@1.89.2)(terser@5.43.1)(typescript@5.8.3)(yaml@2.8.0))': dependencies: '@astrojs/markdown-remark': 6.3.2 - '@astrojs/mdx': 4.3.0(astro@5.9.3(@types/node@24.0.3)(jiti@2.4.2)(rollup@2.79.2)(sass@1.89.2)(terser@5.42.0)(typescript@5.8.3)(yaml@2.8.0)) + '@astrojs/mdx': 4.3.0(astro@5.10.0(@types/node@24.0.3)(jiti@2.4.2)(rollup@2.79.2)(sass@1.89.2)(terser@5.43.1)(typescript@5.8.3)(yaml@2.8.0)) '@astrojs/sitemap': 3.4.1 '@pagefind/default-ui': 1.3.0 '@types/hast': 3.0.4 '@types/js-yaml': 4.0.9 '@types/mdast': 4.0.4 - astro: 5.9.3(@types/node@24.0.3)(jiti@2.4.2)(rollup@2.79.2)(sass@1.89.2)(terser@5.42.0)(typescript@5.8.3)(yaml@2.8.0) - astro-expressive-code: 0.41.2(astro@5.9.3(@types/node@24.0.3)(jiti@2.4.2)(rollup@2.79.2)(sass@1.89.2)(terser@5.42.0)(typescript@5.8.3)(yaml@2.8.0)) + astro: 5.10.0(@types/node@24.0.3)(jiti@2.4.2)(rollup@2.79.2)(sass@1.89.2)(terser@5.43.1)(typescript@5.8.3)(yaml@2.8.0) + astro-expressive-code: 0.41.2(astro@5.10.0(@types/node@24.0.3)(jiti@2.4.2)(rollup@2.79.2)(sass@1.89.2)(terser@5.43.1)(typescript@5.8.3)(yaml@2.8.0)) bcp-47: 2.1.0 hast-util-from-html: 2.0.3 hast-util-select: 6.0.4 @@ -4763,7 +4760,7 @@ snapshots: '@expressive-code/plugin-shiki@0.41.2': dependencies: '@expressive-code/core': 0.41.2 - shiki: 3.6.0 + shiki: 3.7.0 '@expressive-code/plugin-text-markers@0.41.2': dependencies: @@ -5089,7 +5086,7 @@ snapshots: '@rollup/plugin-node-resolve@15.3.1(rollup@2.79.2)': dependencies: - '@rollup/pluginutils': 5.1.4(rollup@2.79.2) + '@rollup/pluginutils': 5.2.0(rollup@2.79.2) '@types/resolve': 1.20.2 deepmerge: 4.3.1 is-module: 1.0.0 @@ -5107,7 +5104,7 @@ snapshots: dependencies: serialize-javascript: 6.0.2 smob: 1.5.0 - terser: 5.42.0 + terser: 5.43.1 optionalDependencies: rollup: 2.79.2 @@ -5118,7 +5115,7 @@ snapshots: picomatch: 2.3.1 rollup: 2.79.2 - '@rollup/pluginutils@5.1.4(rollup@2.79.2)': + '@rollup/pluginutils@5.2.0(rollup@2.79.2)': dependencies: '@types/estree': 1.0.8 estree-walker: 2.0.2 @@ -5126,93 +5123,93 @@ snapshots: optionalDependencies: rollup: 2.79.2 - '@rollup/rollup-android-arm-eabi@4.43.0': + '@rollup/rollup-android-arm-eabi@4.44.0': optional: true - '@rollup/rollup-android-arm64@4.43.0': + '@rollup/rollup-android-arm64@4.44.0': optional: true - '@rollup/rollup-darwin-arm64@4.43.0': + '@rollup/rollup-darwin-arm64@4.44.0': optional: true - '@rollup/rollup-darwin-x64@4.43.0': + '@rollup/rollup-darwin-x64@4.44.0': optional: true - '@rollup/rollup-freebsd-arm64@4.43.0': + '@rollup/rollup-freebsd-arm64@4.44.0': optional: true - '@rollup/rollup-freebsd-x64@4.43.0': + '@rollup/rollup-freebsd-x64@4.44.0': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.43.0': + '@rollup/rollup-linux-arm-gnueabihf@4.44.0': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.43.0': + '@rollup/rollup-linux-arm-musleabihf@4.44.0': optional: true - '@rollup/rollup-linux-arm64-gnu@4.43.0': + '@rollup/rollup-linux-arm64-gnu@4.44.0': optional: true - '@rollup/rollup-linux-arm64-musl@4.43.0': + '@rollup/rollup-linux-arm64-musl@4.44.0': optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.43.0': + '@rollup/rollup-linux-loongarch64-gnu@4.44.0': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.43.0': + '@rollup/rollup-linux-powerpc64le-gnu@4.44.0': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.43.0': + '@rollup/rollup-linux-riscv64-gnu@4.44.0': optional: true - '@rollup/rollup-linux-riscv64-musl@4.43.0': + '@rollup/rollup-linux-riscv64-musl@4.44.0': optional: true - '@rollup/rollup-linux-s390x-gnu@4.43.0': + '@rollup/rollup-linux-s390x-gnu@4.44.0': optional: true - '@rollup/rollup-linux-x64-gnu@4.43.0': + '@rollup/rollup-linux-x64-gnu@4.44.0': optional: true - '@rollup/rollup-linux-x64-musl@4.43.0': + '@rollup/rollup-linux-x64-musl@4.44.0': optional: true - '@rollup/rollup-win32-arm64-msvc@4.43.0': + '@rollup/rollup-win32-arm64-msvc@4.44.0': optional: true - '@rollup/rollup-win32-ia32-msvc@4.43.0': + '@rollup/rollup-win32-ia32-msvc@4.44.0': optional: true - '@rollup/rollup-win32-x64-msvc@4.43.0': + '@rollup/rollup-win32-x64-msvc@4.44.0': optional: true - '@shikijs/core@3.6.0': + '@shikijs/core@3.7.0': dependencies: - '@shikijs/types': 3.6.0 + '@shikijs/types': 3.7.0 '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 hast-util-to-html: 9.0.5 - '@shikijs/engine-javascript@3.6.0': + '@shikijs/engine-javascript@3.7.0': dependencies: - '@shikijs/types': 3.6.0 + '@shikijs/types': 3.7.0 '@shikijs/vscode-textmate': 10.0.2 oniguruma-to-es: 4.3.3 - '@shikijs/engine-oniguruma@3.6.0': + '@shikijs/engine-oniguruma@3.7.0': dependencies: - '@shikijs/types': 3.6.0 + '@shikijs/types': 3.7.0 '@shikijs/vscode-textmate': 10.0.2 - '@shikijs/langs@3.6.0': + '@shikijs/langs@3.7.0': dependencies: - '@shikijs/types': 3.6.0 + '@shikijs/types': 3.7.0 - '@shikijs/themes@3.6.0': + '@shikijs/themes@3.7.0': dependencies: - '@shikijs/types': 3.6.0 + '@shikijs/types': 3.7.0 - '@shikijs/types@3.6.0': + '@shikijs/types@3.7.0': dependencies: '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 @@ -5261,8 +5258,6 @@ snapshots: '@types/estree@0.0.39': {} - '@types/estree@1.0.7': {} - '@types/estree@1.0.8': {} '@types/fontkit@2.0.8': @@ -5315,12 +5310,12 @@ snapshots: '@ungap/structured-clone@1.3.0': {} - '@vite-pwa/astro@1.1.0(astro@5.9.3(@types/node@24.0.3)(jiti@2.4.2)(rollup@2.79.2)(sass@1.89.2)(terser@5.42.0)(typescript@5.8.3)(yaml@2.8.0))(vite-plugin-pwa@1.0.0(vite@6.3.5(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(terser@5.42.0)(yaml@2.8.0))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0))': + '@vite-pwa/astro@1.1.0(astro@5.10.0(@types/node@24.0.3)(jiti@2.4.2)(rollup@2.79.2)(sass@1.89.2)(terser@5.43.1)(typescript@5.8.3)(yaml@2.8.0))(vite-plugin-pwa@1.0.0(vite@6.3.5(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0))': dependencies: - astro: 5.9.3(@types/node@24.0.3)(jiti@2.4.2)(rollup@2.79.2)(sass@1.89.2)(terser@5.42.0)(typescript@5.8.3)(yaml@2.8.0) - vite-plugin-pwa: 1.0.0(vite@6.3.5(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(terser@5.42.0)(yaml@2.8.0))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0) + astro: 5.10.0(@types/node@24.0.3)(jiti@2.4.2)(rollup@2.79.2)(sass@1.89.2)(terser@5.43.1)(typescript@5.8.3)(yaml@2.8.0) + vite-plugin-pwa: 1.0.0(vite@6.3.5(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0) - '@vitejs/plugin-react@4.5.2(vite@6.3.5(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(terser@5.42.0)(yaml@2.8.0))': + '@vitejs/plugin-react@4.5.2(vite@6.3.5(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))': dependencies: '@babel/core': 7.27.4 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.27.4) @@ -5328,7 +5323,7 @@ snapshots: '@rolldown/pluginutils': 1.0.0-beta.11 '@types/babel__core': 7.20.5 react-refresh: 0.17.0 - vite: 6.3.5(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(terser@5.42.0)(yaml@2.8.0) + vite: 6.3.5(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0) transitivePeerDependencies: - supports-color @@ -5439,12 +5434,12 @@ snapshots: astring@1.9.0: {} - astro-expressive-code@0.41.2(astro@5.9.3(@types/node@24.0.3)(jiti@2.4.2)(rollup@2.79.2)(sass@1.89.2)(terser@5.42.0)(typescript@5.8.3)(yaml@2.8.0)): + astro-expressive-code@0.41.2(astro@5.10.0(@types/node@24.0.3)(jiti@2.4.2)(rollup@2.79.2)(sass@1.89.2)(terser@5.43.1)(typescript@5.8.3)(yaml@2.8.0)): dependencies: - astro: 5.9.3(@types/node@24.0.3)(jiti@2.4.2)(rollup@2.79.2)(sass@1.89.2)(terser@5.42.0)(typescript@5.8.3)(yaml@2.8.0) + astro: 5.10.0(@types/node@24.0.3)(jiti@2.4.2)(rollup@2.79.2)(sass@1.89.2)(terser@5.43.1)(typescript@5.8.3)(yaml@2.8.0) rehype-expressive-code: 0.41.2 - astro@5.9.3(@types/node@24.0.3)(jiti@2.4.2)(rollup@2.79.2)(sass@1.89.2)(terser@5.42.0)(typescript@5.8.3)(yaml@2.8.0): + astro@5.10.0(@types/node@24.0.3)(jiti@2.4.2)(rollup@2.79.2)(sass@1.89.2)(terser@5.43.1)(typescript@5.8.3)(yaml@2.8.0): dependencies: '@astrojs/compiler': 2.12.2 '@astrojs/internal-helpers': 0.6.1 @@ -5452,7 +5447,7 @@ snapshots: '@astrojs/telemetry': 3.3.0 '@capsizecss/unpack': 2.4.0 '@oslojs/encoding': 1.1.0 - '@rollup/pluginutils': 5.1.4(rollup@2.79.2) + '@rollup/pluginutils': 5.2.0(rollup@2.79.2) acorn: 8.15.0 aria-query: 5.3.2 axobject-query: 4.1.0 @@ -5490,7 +5485,7 @@ snapshots: prompts: 2.4.2 rehype: 13.0.2 semver: 7.7.2 - shiki: 3.6.0 + shiki: 3.7.0 tinyexec: 0.3.2 tinyglobby: 0.2.14 tsconfck: 3.1.6(typescript@5.8.3) @@ -5499,14 +5494,14 @@ snapshots: unist-util-visit: 5.0.0 unstorage: 1.16.0 vfile: 6.0.3 - vite: 6.3.5(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(terser@5.42.0)(yaml@2.8.0) - vitefu: 1.0.6(vite@6.3.5(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(terser@5.42.0)(yaml@2.8.0)) + vite: 6.3.5(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0) + vitefu: 1.0.7(vite@6.3.5(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0)) xxhash-wasm: 1.1.0 yargs-parser: 21.1.1 yocto-spinner: 0.2.3 - zod: 3.25.64 - zod-to-json-schema: 3.24.5(zod@3.25.64) - zod-to-ts: 1.2.0(typescript@5.8.3)(zod@3.25.64) + zod: 3.25.67 + zod-to-json-schema: 3.24.5(zod@3.25.67) + zod-to-ts: 1.2.0(typescript@5.8.3)(zod@3.25.67) optionalDependencies: sharp: 0.33.5 transitivePeerDependencies: @@ -5630,8 +5625,8 @@ snapshots: browserslist@4.25.0: dependencies: - caniuse-lite: 1.0.30001723 - electron-to-chromium: 1.5.167 + caniuse-lite: 1.0.30001724 + electron-to-chromium: 1.5.171 node-releases: 2.0.19 update-browserslist-db: 1.1.3(browserslist@4.25.0) @@ -5656,7 +5651,7 @@ snapshots: camelcase@8.0.0: {} - caniuse-lite@1.0.30001723: {} + caniuse-lite@1.0.30001724: {} ccount@2.0.1: {} @@ -5835,7 +5830,7 @@ snapshots: dependencies: jake: 10.9.2 - electron-to-chromium@1.5.167: {} + electron-to-chromium@1.5.171: {} emmet@2.4.11: dependencies: @@ -7543,30 +7538,30 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - rollup@4.43.0: + rollup@4.44.0: dependencies: - '@types/estree': 1.0.7 + '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.43.0 - '@rollup/rollup-android-arm64': 4.43.0 - '@rollup/rollup-darwin-arm64': 4.43.0 - '@rollup/rollup-darwin-x64': 4.43.0 - '@rollup/rollup-freebsd-arm64': 4.43.0 - '@rollup/rollup-freebsd-x64': 4.43.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.43.0 - '@rollup/rollup-linux-arm-musleabihf': 4.43.0 - '@rollup/rollup-linux-arm64-gnu': 4.43.0 - '@rollup/rollup-linux-arm64-musl': 4.43.0 - '@rollup/rollup-linux-loongarch64-gnu': 4.43.0 - '@rollup/rollup-linux-powerpc64le-gnu': 4.43.0 - '@rollup/rollup-linux-riscv64-gnu': 4.43.0 - '@rollup/rollup-linux-riscv64-musl': 4.43.0 - '@rollup/rollup-linux-s390x-gnu': 4.43.0 - '@rollup/rollup-linux-x64-gnu': 4.43.0 - '@rollup/rollup-linux-x64-musl': 4.43.0 - '@rollup/rollup-win32-arm64-msvc': 4.43.0 - '@rollup/rollup-win32-ia32-msvc': 4.43.0 - '@rollup/rollup-win32-x64-msvc': 4.43.0 + '@rollup/rollup-android-arm-eabi': 4.44.0 + '@rollup/rollup-android-arm64': 4.44.0 + '@rollup/rollup-darwin-arm64': 4.44.0 + '@rollup/rollup-darwin-x64': 4.44.0 + '@rollup/rollup-freebsd-arm64': 4.44.0 + '@rollup/rollup-freebsd-x64': 4.44.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.44.0 + '@rollup/rollup-linux-arm-musleabihf': 4.44.0 + '@rollup/rollup-linux-arm64-gnu': 4.44.0 + '@rollup/rollup-linux-arm64-musl': 4.44.0 + '@rollup/rollup-linux-loongarch64-gnu': 4.44.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.44.0 + '@rollup/rollup-linux-riscv64-gnu': 4.44.0 + '@rollup/rollup-linux-riscv64-musl': 4.44.0 + '@rollup/rollup-linux-s390x-gnu': 4.44.0 + '@rollup/rollup-linux-x64-gnu': 4.44.0 + '@rollup/rollup-linux-x64-musl': 4.44.0 + '@rollup/rollup-win32-arm64-msvc': 4.44.0 + '@rollup/rollup-win32-ia32-msvc': 4.44.0 + '@rollup/rollup-win32-x64-msvc': 4.44.0 fsevents: 2.3.3 run-parallel@1.2.0: @@ -7691,14 +7686,14 @@ snapshots: '@img/sharp-win32-ia32': 0.34.2 '@img/sharp-win32-x64': 0.34.2 - shiki@3.6.0: + shiki@3.7.0: dependencies: - '@shikijs/core': 3.6.0 - '@shikijs/engine-javascript': 3.6.0 - '@shikijs/engine-oniguruma': 3.6.0 - '@shikijs/langs': 3.6.0 - '@shikijs/themes': 3.6.0 - '@shikijs/types': 3.6.0 + '@shikijs/core': 3.7.0 + '@shikijs/engine-javascript': 3.7.0 + '@shikijs/engine-oniguruma': 3.7.0 + '@shikijs/langs': 3.7.0 + '@shikijs/themes': 3.7.0 + '@shikijs/types': 3.7.0 '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 @@ -7868,7 +7863,7 @@ snapshots: type-fest: 0.16.0 unique-string: 2.0.0 - terser@5.42.0: + terser@5.43.1: dependencies: '@jridgewell/source-map': 0.3.6 acorn: 8.15.0 @@ -8098,36 +8093,36 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.2 - vite-plugin-pwa@1.0.0(vite@6.3.5(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(terser@5.42.0)(yaml@2.8.0))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0): + vite-plugin-pwa@1.0.0(vite@6.3.5(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0): dependencies: debug: 4.4.1 pretty-bytes: 6.1.1 tinyglobby: 0.2.14 - vite: 6.3.5(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(terser@5.42.0)(yaml@2.8.0) + vite: 6.3.5(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0) workbox-build: 7.3.0(@types/babel__core@7.20.5) workbox-window: 7.3.0 transitivePeerDependencies: - supports-color - vite@6.3.5(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(terser@5.42.0)(yaml@2.8.0): + vite@6.3.5(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0): dependencies: esbuild: 0.25.5 fdir: 6.4.6(picomatch@4.0.2) picomatch: 4.0.2 postcss: 8.5.6 - rollup: 4.43.0 + rollup: 4.44.0 tinyglobby: 0.2.14 optionalDependencies: '@types/node': 24.0.3 fsevents: 2.3.3 jiti: 2.4.2 sass: 1.89.2 - terser: 5.42.0 + terser: 5.43.1 yaml: 2.8.0 - vitefu@1.0.6(vite@6.3.5(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(terser@5.42.0)(yaml@2.8.0)): + vitefu@1.0.7(vite@6.3.5(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0)): optionalDependencies: - vite: 6.3.5(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(terser@5.42.0)(yaml@2.8.0) + vite: 6.3.5(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0) volar-service-css@0.0.62(@volar/language-service@2.4.14): dependencies: @@ -8474,15 +8469,15 @@ snapshots: yoctocolors@2.1.1: {} - zod-to-json-schema@3.24.5(zod@3.25.64): + zod-to-json-schema@3.24.5(zod@3.25.67): dependencies: - zod: 3.25.64 + zod: 3.25.67 - zod-to-ts@1.2.0(typescript@5.8.3)(zod@3.25.64): + zod-to-ts@1.2.0(typescript@5.8.3)(zod@3.25.67): dependencies: typescript: 5.8.3 - zod: 3.25.64 + zod: 3.25.67 - zod@3.25.64: {} + zod@3.25.67: {} zwitch@2.0.4: {} diff --git a/plugin/lib/setonix_plugin.dart b/plugin/lib/setonix_plugin.dart index 2fa283f5..12aacb89 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 ef4ea184..dabb2843 100644 --- a/plugin/lib/src/plugin.dart +++ b/plugin/lib/src/plugin.dart @@ -48,6 +48,15 @@ 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, [String name = 'game']) { unregisterPlugin(name); diff --git a/server/lib/src/bloc.dart b/server/lib/src/bloc.dart index 231a7056..a176e61f 100644 --- a/server/lib/src/bloc.dart +++ b/server/lib/src/bloc.dart @@ -68,10 +68,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 923d3965..3ca3016e 100644 --- a/server/lib/src/config.dart +++ b/server/lib/src/config.dart @@ -68,4 +68,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 9eb143ec..b7147aa8 100644 --- a/server/lib/src/server.dart +++ b/server/lib/src/server.dart @@ -51,7 +51,12 @@ final class SetonixServer { )); SetonixData _buildDefaultWorld() { - final data = SetonixData.empty().setInfo(GameInfo( + 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; From 7a19dcea70d9f652754b7125e0aa1b998f28e802 Mon Sep 17 00:00:00 2001 From: CodeDoctorDE Date: Wed, 2 Jul 2025 12:00:07 +0200 Subject: [PATCH 02/10] Fix mode types --- api/lib/src/event/process/client.dart | 4 +- api/lib/src/event/server.dart | 10 +- api/lib/src/models/config.mapper.dart | 272 +++++++++++++++----------- api/lib/src/models/data.dart | 8 + api/lib/src/models/info.mapper.dart | 190 +++++++++++------- api/lib/src/models/mode.mapper.dart | 117 +++++++---- api/lib/src/models/table.dart | 4 +- api/lib/src/services/asset.dart | 3 + 8 files changed, 376 insertions(+), 232 deletions(-) diff --git a/api/lib/src/event/process/client.dart b/api/lib/src/event/process/client.dart index d89433a5..b86bcf35 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 6e38cbc5..2bb4e2f3 100644 --- a/api/lib/src/event/server.dart +++ b/api/lib/src/event/server.dart @@ -28,13 +28,15 @@ final class WorldInitialized extends ServerWorldEvent }); factory WorldInitialized.fromMode( - ItemLocation location, - GameMode? mode, + PackItem? mode, WorldState state, ) => WorldInitialized( clearUserInterface: true, - info: state.info.copyWith(teams: mode?.teams ?? {}, script: location), - table: mode?.tables[state.tableName] ?? GameTable(), + info: state.info.copyWith( + teams: mode?.item.teams ?? {}, + script: mode?.location, + ), + table: mode?.item.tables[state.tableName] ?? GameTable(), teamMembers: const {}, ); } diff --git a/api/lib/src/models/config.mapper.dart b/api/lib/src/models/config.mapper.dart index dd564e04..fd4c4a36 100644 --- a/api/lib/src/models/config.mapper.dart +++ b/api/lib/src/models/config.mapper.dart @@ -21,44 +21,83 @@ class SetonixConfigMapper extends ClassMapperBase { final String id = 'SetonixConfig'; static String? _$host(SetonixConfig v) => v.host; - static const Field _f$host = - Field('host', _$host, opt: true); + static const Field _f$host = Field( + 'host', + _$host, + opt: true, + ); static int? _$port(SetonixConfig v) => v.port; - static const Field _f$port = - Field('port', _$port, opt: true); + static const Field _f$port = Field( + 'port', + _$port, + opt: true, + ); static String? _$worldFile(SetonixConfig v) => v.worldFile; - static const Field _f$worldFile = - Field('worldFile', _$worldFile, opt: true); + static const Field _f$worldFile = Field( + 'worldFile', + _$worldFile, + opt: true, + ); static bool? _$autosave(SetonixConfig v) => v.autosave; - static const Field _f$autosave = - Field('autosave', _$autosave, opt: true); + static const Field _f$autosave = Field( + 'autosave', + _$autosave, + opt: true, + ); static bool? _$multiWorld(SetonixConfig v) => v.multiWorld; - static const Field _f$multiWorld = - Field('multiWorld', _$multiWorld, opt: true); + static const Field _f$multiWorld = Field( + 'multiWorld', + _$multiWorld, + opt: true, + ); static int? _$maxPlayers(SetonixConfig v) => v.maxPlayers; - static const Field _f$maxPlayers = - Field('maxPlayers', _$maxPlayers, opt: true); + static const Field _f$maxPlayers = Field( + 'maxPlayers', + _$maxPlayers, + opt: true, + ); static String? _$description(SetonixConfig v) => v.description; - static const Field _f$description = - Field('description', _$description, opt: true); + static const Field _f$description = Field( + 'description', + _$description, + opt: true, + ); static String? _$guestPrefix(SetonixConfig v) => v.guestPrefix; - static const Field _f$guestPrefix = - Field('guestPrefix', _$guestPrefix, opt: true); + static const Field _f$guestPrefix = Field( + 'guestPrefix', + _$guestPrefix, + opt: true, + ); static bool? _$whitelistEnabled(SetonixConfig v) => v.whitelistEnabled; - static const Field _f$whitelistEnabled = - Field('whitelistEnabled', _$whitelistEnabled, opt: true); + static const Field _f$whitelistEnabled = Field( + 'whitelistEnabled', + _$whitelistEnabled, + opt: true, + ); static bool? _$accountRequired(SetonixConfig v) => v.accountRequired; - static const Field _f$accountRequired = - Field('accountRequired', _$accountRequired, opt: true); + static const Field _f$accountRequired = Field( + 'accountRequired', + _$accountRequired, + opt: true, + ); static String? _$apiEndpoint(SetonixConfig v) => v.apiEndpoint; - static const Field _f$apiEndpoint = - Field('apiEndpoint', _$apiEndpoint, opt: true); + static const Field _f$apiEndpoint = Field( + 'apiEndpoint', + _$apiEndpoint, + opt: true, + ); static String? _$endpointSecret(SetonixConfig v) => v.endpointSecret; - static const Field _f$endpointSecret = - Field('endpointSecret', _$endpointSecret, opt: true); + static const Field _f$endpointSecret = Field( + 'endpointSecret', + _$endpointSecret, + opt: true, + ); static String? _$gameMode(SetonixConfig v) => v.gameMode; - static const Field _f$gameMode = - Field('gameMode', _$gameMode, opt: true); + static const Field _f$gameMode = Field( + 'gameMode', + _$gameMode, + opt: true, + ); @override final MappableFields fields = const { @@ -79,19 +118,20 @@ class SetonixConfigMapper extends ClassMapperBase { static SetonixConfig _instantiate(DecodingData data) { return SetonixConfig( - host: data.dec(_f$host), - port: data.dec(_f$port), - worldFile: data.dec(_f$worldFile), - autosave: data.dec(_f$autosave), - multiWorld: data.dec(_f$multiWorld), - maxPlayers: data.dec(_f$maxPlayers), - description: data.dec(_f$description), - guestPrefix: data.dec(_f$guestPrefix), - whitelistEnabled: data.dec(_f$whitelistEnabled), - accountRequired: data.dec(_f$accountRequired), - apiEndpoint: data.dec(_f$apiEndpoint), - endpointSecret: data.dec(_f$endpointSecret), - gameMode: data.dec(_f$gameMode)); + host: data.dec(_f$host), + port: data.dec(_f$port), + worldFile: data.dec(_f$worldFile), + autosave: data.dec(_f$autosave), + multiWorld: data.dec(_f$multiWorld), + maxPlayers: data.dec(_f$maxPlayers), + description: data.dec(_f$description), + guestPrefix: data.dec(_f$guestPrefix), + whitelistEnabled: data.dec(_f$whitelistEnabled), + accountRequired: data.dec(_f$accountRequired), + apiEndpoint: data.dec(_f$apiEndpoint), + endpointSecret: data.dec(_f$endpointSecret), + gameMode: data.dec(_f$gameMode), + ); } @override @@ -108,34 +148,43 @@ class SetonixConfigMapper extends ClassMapperBase { mixin SetonixConfigMappable { String toJson() { - return SetonixConfigMapper.ensureInitialized() - .encodeJson(this as SetonixConfig); + return SetonixConfigMapper.ensureInitialized().encodeJson( + this as SetonixConfig, + ); } Map toMap() { - return SetonixConfigMapper.ensureInitialized() - .encodeMap(this as SetonixConfig); + return SetonixConfigMapper.ensureInitialized().encodeMap( + this as SetonixConfig, + ); } SetonixConfigCopyWith - get copyWith => _SetonixConfigCopyWithImpl( - this as SetonixConfig, $identity, $identity); + get copyWith => _SetonixConfigCopyWithImpl( + this as SetonixConfig, + $identity, + $identity, + ); @override String toString() { - return SetonixConfigMapper.ensureInitialized() - .stringifyValue(this as SetonixConfig); + return SetonixConfigMapper.ensureInitialized().stringifyValue( + this as SetonixConfig, + ); } @override bool operator ==(Object other) { - return SetonixConfigMapper.ensureInitialized() - .equalsValue(this as SetonixConfig, other); + return SetonixConfigMapper.ensureInitialized().equalsValue( + this as SetonixConfig, + other, + ); } @override int get hashCode { - return SetonixConfigMapper.ensureInitialized() - .hashValue(this as SetonixConfig); + return SetonixConfigMapper.ensureInitialized().hashValue( + this as SetonixConfig, + ); } } @@ -147,20 +196,21 @@ extension SetonixConfigValueCopy<$R, $Out> abstract class SetonixConfigCopyWith<$R, $In extends SetonixConfig, $Out> implements ClassCopyWith<$R, $In, $Out> { - $R call( - {String? host, - int? port, - String? worldFile, - bool? autosave, - bool? multiWorld, - int? maxPlayers, - String? description, - String? guestPrefix, - bool? whitelistEnabled, - bool? accountRequired, - String? apiEndpoint, - String? endpointSecret, - String? gameMode}); + $R call({ + String? host, + int? port, + String? worldFile, + bool? autosave, + bool? multiWorld, + int? maxPlayers, + String? description, + String? guestPrefix, + bool? whitelistEnabled, + bool? accountRequired, + String? apiEndpoint, + String? endpointSecret, + String? gameMode, + }); SetonixConfigCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); } @@ -173,54 +223,56 @@ class _SetonixConfigCopyWithImpl<$R, $Out> late final ClassMapperBase $mapper = SetonixConfigMapper.ensureInitialized(); @override - $R call( - {Object? host = $none, - Object? port = $none, - Object? worldFile = $none, - Object? autosave = $none, - Object? multiWorld = $none, - Object? maxPlayers = $none, - Object? description = $none, - Object? guestPrefix = $none, - Object? whitelistEnabled = $none, - Object? accountRequired = $none, - Object? apiEndpoint = $none, - Object? endpointSecret = $none, - Object? gameMode = $none}) => - $apply(FieldCopyWithData({ - if (host != $none) #host: host, - if (port != $none) #port: port, - if (worldFile != $none) #worldFile: worldFile, - if (autosave != $none) #autosave: autosave, - if (multiWorld != $none) #multiWorld: multiWorld, - if (maxPlayers != $none) #maxPlayers: maxPlayers, - if (description != $none) #description: description, - if (guestPrefix != $none) #guestPrefix: guestPrefix, - if (whitelistEnabled != $none) #whitelistEnabled: whitelistEnabled, - if (accountRequired != $none) #accountRequired: accountRequired, - if (apiEndpoint != $none) #apiEndpoint: apiEndpoint, - if (endpointSecret != $none) #endpointSecret: endpointSecret, - if (gameMode != $none) #gameMode: gameMode - })); + $R call({ + Object? host = $none, + Object? port = $none, + Object? worldFile = $none, + Object? autosave = $none, + Object? multiWorld = $none, + Object? maxPlayers = $none, + Object? description = $none, + Object? guestPrefix = $none, + Object? whitelistEnabled = $none, + Object? accountRequired = $none, + Object? apiEndpoint = $none, + Object? endpointSecret = $none, + Object? gameMode = $none, + }) => $apply( + FieldCopyWithData({ + if (host != $none) #host: host, + if (port != $none) #port: port, + if (worldFile != $none) #worldFile: worldFile, + if (autosave != $none) #autosave: autosave, + if (multiWorld != $none) #multiWorld: multiWorld, + if (maxPlayers != $none) #maxPlayers: maxPlayers, + if (description != $none) #description: description, + if (guestPrefix != $none) #guestPrefix: guestPrefix, + if (whitelistEnabled != $none) #whitelistEnabled: whitelistEnabled, + if (accountRequired != $none) #accountRequired: accountRequired, + if (apiEndpoint != $none) #apiEndpoint: apiEndpoint, + if (endpointSecret != $none) #endpointSecret: endpointSecret, + if (gameMode != $none) #gameMode: gameMode, + }), + ); @override SetonixConfig $make(CopyWithData data) => SetonixConfig( - host: data.get(#host, or: $value.host), - port: data.get(#port, or: $value.port), - worldFile: data.get(#worldFile, or: $value.worldFile), - autosave: data.get(#autosave, or: $value.autosave), - multiWorld: data.get(#multiWorld, or: $value.multiWorld), - maxPlayers: data.get(#maxPlayers, or: $value.maxPlayers), - description: data.get(#description, or: $value.description), - guestPrefix: data.get(#guestPrefix, or: $value.guestPrefix), - whitelistEnabled: - data.get(#whitelistEnabled, or: $value.whitelistEnabled), - 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)); + host: data.get(#host, or: $value.host), + port: data.get(#port, or: $value.port), + worldFile: data.get(#worldFile, or: $value.worldFile), + autosave: data.get(#autosave, or: $value.autosave), + multiWorld: data.get(#multiWorld, or: $value.multiWorld), + maxPlayers: data.get(#maxPlayers, or: $value.maxPlayers), + description: data.get(#description, or: $value.description), + guestPrefix: data.get(#guestPrefix, or: $value.guestPrefix), + whitelistEnabled: data.get(#whitelistEnabled, or: $value.whitelistEnabled), + 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 SetonixConfigCopyWith<$R2, SetonixConfig, $Out2> $chain<$R2, $Out2>( - Then<$Out2, $R2> t) => - _SetonixConfigCopyWithImpl<$R2, $Out2>($value, $cast, t); + Then<$Out2, $R2> t, + ) => _SetonixConfigCopyWithImpl<$R2, $Out2>($value, $cast, t); } diff --git a/api/lib/src/models/data.dart b/api/lib/src/models/data.dart index 6327cfbf..79d7f476 100644 --- a/api/lib/src/models/data.dart +++ b/api/lib/src/models/data.dart @@ -321,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.mapper.dart b/api/lib/src/models/info.mapper.dart index 6afd5255..31112da5 100644 --- a/api/lib/src/models/info.mapper.dart +++ b/api/lib/src/models/info.mapper.dart @@ -105,14 +105,25 @@ class GameInfoMapper extends ClassMapperBase { final String id = 'GameInfo'; static Map _$teams(GameInfo v) => v.teams; - static const Field> _f$teams = - Field('teams', _$teams, opt: true, def: const {}); + static const Field> _f$teams = Field( + 'teams', + _$teams, + opt: true, + def: const {}, + ); static List _$packs(GameInfo v) => v.packs; - static const Field> _f$packs = - Field('packs', _$packs, opt: true, def: const []); + static const Field> _f$packs = Field( + 'packs', + _$packs, + opt: true, + def: const [], + ); static ItemLocation? _$script(GameInfo v) => v.script; - static const Field _f$script = - Field('script', _$script, opt: true); + static const Field _f$script = Field( + 'script', + _$script, + opt: true, + ); @override final MappableFields fields = const { @@ -123,9 +134,10 @@ class GameInfoMapper extends ClassMapperBase { static GameInfo _instantiate(DecodingData data) { return GameInfo( - teams: data.dec(_f$teams), - packs: data.dec(_f$packs), - script: data.dec(_f$script)); + teams: data.dec(_f$teams), + packs: data.dec(_f$packs), + script: data.dec(_f$script), + ); } @override @@ -142,18 +154,23 @@ class GameInfoMapper extends ClassMapperBase { mixin GameInfoMappable { String toJson() { - return GameInfoMapper.ensureInitialized() - .encodeJson(this as GameInfo); + return GameInfoMapper.ensureInitialized().encodeJson( + this as GameInfo, + ); } Map toMap() { - return GameInfoMapper.ensureInitialized() - .encodeMap(this as GameInfo); + return GameInfoMapper.ensureInitialized().encodeMap( + this as GameInfo, + ); } GameInfoCopyWith get copyWith => _GameInfoCopyWithImpl( - this as GameInfo, $identity, $identity); + this as GameInfo, + $identity, + $identity, + ); @override String toString() { return GameInfoMapper.ensureInitialized().stringifyValue(this as GameInfo); @@ -161,8 +178,10 @@ mixin GameInfoMappable { @override bool operator ==(Object other) { - return GameInfoMapper.ensureInitialized() - .equalsValue(this as GameInfo, other); + return GameInfoMapper.ensureInitialized().equalsValue( + this as GameInfo, + other, + ); } @override @@ -179,13 +198,14 @@ extension GameInfoValueCopy<$R, $Out> on ObjectCopyWith<$R, GameInfo, $Out> { abstract class GameInfoCopyWith<$R, $In extends GameInfo, $Out> implements ClassCopyWith<$R, $In, $Out> { MapCopyWith<$R, String, GameTeam, GameTeamCopyWith<$R, GameTeam, GameTeam>> - get teams; + get teams; ListCopyWith<$R, String, ObjectCopyWith<$R, String, String>> get packs; ItemLocationCopyWith<$R, ItemLocation, ItemLocation>? get script; - $R call( - {Map? teams, - List? packs, - ItemLocation? script}); + $R call({ + Map? teams, + List? packs, + ItemLocation? script, + }); GameInfoCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); } @@ -199,35 +219,44 @@ class _GameInfoCopyWithImpl<$R, $Out> GameInfoMapper.ensureInitialized(); @override MapCopyWith<$R, String, GameTeam, GameTeamCopyWith<$R, GameTeam, GameTeam>> - get teams => MapCopyWith( - $value.teams, (v, t) => v.copyWith.$chain(t), (v) => call(teams: v)); + get teams => MapCopyWith( + $value.teams, + (v, t) => v.copyWith.$chain(t), + (v) => call(teams: v), + ); @override ListCopyWith<$R, String, ObjectCopyWith<$R, String, String>> get packs => - ListCopyWith($value.packs, (v, t) => ObjectCopyWith(v, $identity, t), - (v) => call(packs: v)); + ListCopyWith( + $value.packs, + (v, t) => ObjectCopyWith(v, $identity, t), + (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, - Object? script = $none}) => - $apply(FieldCopyWithData({ - if (teams != null) #teams: teams, - if (packs != null) #packs: packs, - if (script != $none) #script: script - })); + $R call({ + Map? teams, + List? packs, + Object? script = $none, + }) => $apply( + FieldCopyWithData({ + if (teams != null) #teams: teams, + if (packs != null) #packs: packs, + if (script != $none) #script: script, + }), + ); @override GameInfo $make(CopyWithData data) => GameInfo( - teams: data.get(#teams, or: $value.teams), - packs: data.get(#packs, or: $value.packs), - script: data.get(#script, or: $value.script)); + teams: data.get(#teams, or: $value.teams), + packs: data.get(#packs, or: $value.packs), + script: data.get(#script, or: $value.script), + ); @override GameInfoCopyWith<$R2, GameInfo, $Out2> $chain<$R2, $Out2>( - Then<$Out2, $R2> t) => - _GameInfoCopyWithImpl<$R2, $Out2>($value, $cast, t); + Then<$Out2, $R2> t, + ) => _GameInfoCopyWithImpl<$R2, $Out2>($value, $cast, t); } class GameTeamMapper extends ClassMapperBase { @@ -247,11 +276,18 @@ class GameTeamMapper extends ClassMapperBase { final String id = 'GameTeam'; static String _$description(GameTeam v) => v.description; - static const Field _f$description = - Field('description', _$description, opt: true, def: ''); + static const Field _f$description = Field( + 'description', + _$description, + opt: true, + def: '', + ); static TeamColor? _$color(GameTeam v) => v.color; - static const Field _f$color = - Field('color', _$color, opt: true); + static const Field _f$color = Field( + 'color', + _$color, + opt: true, + ); static Set _$claimedCells(GameTeam v) => v.claimedCells; static const Field> _f$claimedCells = @@ -266,9 +302,10 @@ class GameTeamMapper extends ClassMapperBase { static GameTeam _instantiate(DecodingData data) { return GameTeam( - description: data.dec(_f$description), - color: data.dec(_f$color), - claimedCells: data.dec(_f$claimedCells)); + description: data.dec(_f$description), + color: data.dec(_f$color), + claimedCells: data.dec(_f$claimedCells), + ); } @override @@ -285,18 +322,23 @@ class GameTeamMapper extends ClassMapperBase { mixin GameTeamMappable { String toJson() { - return GameTeamMapper.ensureInitialized() - .encodeJson(this as GameTeam); + return GameTeamMapper.ensureInitialized().encodeJson( + this as GameTeam, + ); } Map toMap() { - return GameTeamMapper.ensureInitialized() - .encodeMap(this as GameTeam); + return GameTeamMapper.ensureInitialized().encodeMap( + this as GameTeam, + ); } GameTeamCopyWith get copyWith => _GameTeamCopyWithImpl( - this as GameTeam, $identity, $identity); + this as GameTeam, + $identity, + $identity, + ); @override String toString() { return GameTeamMapper.ensureInitialized().stringifyValue(this as GameTeam); @@ -304,8 +346,10 @@ mixin GameTeamMappable { @override bool operator ==(Object other) { - return GameTeamMapper.ensureInitialized() - .equalsValue(this as GameTeam, other); + return GameTeamMapper.ensureInitialized().equalsValue( + this as GameTeam, + other, + ); } @override @@ -321,10 +365,11 @@ extension GameTeamValueCopy<$R, $Out> on ObjectCopyWith<$R, GameTeam, $Out> { abstract class GameTeamCopyWith<$R, $In extends GameTeam, $Out> implements ClassCopyWith<$R, $In, $Out> { - $R call( - {String? description, - TeamColor? color, - Set? claimedCells}); + $R call({ + String? description, + TeamColor? color, + Set? claimedCells, + }); GameTeamCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); } @@ -337,23 +382,26 @@ class _GameTeamCopyWithImpl<$R, $Out> late final ClassMapperBase $mapper = GameTeamMapper.ensureInitialized(); @override - $R call( - {String? description, - Object? color = $none, - Set? claimedCells}) => - $apply(FieldCopyWithData({ - if (description != null) #description: description, - if (color != $none) #color: color, - if (claimedCells != null) #claimedCells: claimedCells - })); + $R call({ + String? description, + Object? color = $none, + Set? claimedCells, + }) => $apply( + FieldCopyWithData({ + if (description != null) #description: description, + if (color != $none) #color: color, + if (claimedCells != null) #claimedCells: claimedCells, + }), + ); @override GameTeam $make(CopyWithData data) => GameTeam( - description: data.get(#description, or: $value.description), - color: data.get(#color, or: $value.color), - claimedCells: data.get(#claimedCells, or: $value.claimedCells)); + description: data.get(#description, or: $value.description), + color: data.get(#color, or: $value.color), + claimedCells: data.get(#claimedCells, or: $value.claimedCells), + ); @override GameTeamCopyWith<$R2, GameTeam, $Out2> $chain<$R2, $Out2>( - Then<$Out2, $R2> t) => - _GameTeamCopyWithImpl<$R2, $Out2>($value, $cast, t); + Then<$Out2, $R2> t, + ) => _GameTeamCopyWithImpl<$R2, $Out2>($value, $cast, t); } diff --git a/api/lib/src/models/mode.mapper.dart b/api/lib/src/models/mode.mapper.dart index 7a8d3263..0def22a2 100644 --- a/api/lib/src/models/mode.mapper.dart +++ b/api/lib/src/models/mode.mapper.dart @@ -25,11 +25,19 @@ class GameModeMapper extends ClassMapperBase { static String? _$script(GameMode v) => v.script; static const Field _f$script = Field('script', _$script); static Map _$tables(GameMode v) => v.tables; - static const Field> _f$tables = - Field('tables', _$tables, opt: true, def: const {}); + static const Field> _f$tables = Field( + 'tables', + _$tables, + opt: true, + def: const {}, + ); static Map _$teams(GameMode v) => v.teams; - static const Field> _f$teams = - Field('teams', _$teams, opt: true, def: const {}); + static const Field> _f$teams = Field( + 'teams', + _$teams, + opt: true, + def: const {}, + ); @override final MappableFields fields = const { @@ -40,9 +48,10 @@ class GameModeMapper extends ClassMapperBase { static GameMode _instantiate(DecodingData data) { return GameMode( - script: data.dec(_f$script), - tables: data.dec(_f$tables), - teams: data.dec(_f$teams)); + script: data.dec(_f$script), + tables: data.dec(_f$tables), + teams: data.dec(_f$teams), + ); } @override @@ -59,18 +68,23 @@ class GameModeMapper extends ClassMapperBase { mixin GameModeMappable { String toJson() { - return GameModeMapper.ensureInitialized() - .encodeJson(this as GameMode); + return GameModeMapper.ensureInitialized().encodeJson( + this as GameMode, + ); } Map toMap() { - return GameModeMapper.ensureInitialized() - .encodeMap(this as GameMode); + return GameModeMapper.ensureInitialized().encodeMap( + this as GameMode, + ); } GameModeCopyWith get copyWith => _GameModeCopyWithImpl( - this as GameMode, $identity, $identity); + this as GameMode, + $identity, + $identity, + ); @override String toString() { return GameModeMapper.ensureInitialized().stringifyValue(this as GameMode); @@ -78,8 +92,10 @@ mixin GameModeMappable { @override bool operator ==(Object other) { - return GameModeMapper.ensureInitialized() - .equalsValue(this as GameMode, other); + return GameModeMapper.ensureInitialized().equalsValue( + this as GameMode, + other, + ); } @override @@ -95,14 +111,20 @@ extension GameModeValueCopy<$R, $Out> on ObjectCopyWith<$R, GameMode, $Out> { abstract class GameModeCopyWith<$R, $In extends GameMode, $Out> implements ClassCopyWith<$R, $In, $Out> { - MapCopyWith<$R, String, GameTable, - GameTableCopyWith<$R, GameTable, GameTable>> get tables; + MapCopyWith< + $R, + String, + GameTable, + GameTableCopyWith<$R, GameTable, GameTable> + > + get tables; MapCopyWith<$R, String, GameTeam, GameTeamCopyWith<$R, GameTeam, GameTeam>> - get teams; - $R call( - {String? script, - Map? tables, - Map? teams}); + get teams; + $R call({ + String? script, + Map? tables, + Map? teams, + }); GameModeCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); } @@ -115,32 +137,45 @@ class _GameModeCopyWithImpl<$R, $Out> late final ClassMapperBase $mapper = GameModeMapper.ensureInitialized(); @override - MapCopyWith<$R, String, GameTable, - GameTableCopyWith<$R, GameTable, GameTable>> - get tables => MapCopyWith($value.tables, (v, t) => v.copyWith.$chain(t), - (v) => call(tables: v)); + MapCopyWith< + $R, + String, + GameTable, + GameTableCopyWith<$R, GameTable, GameTable> + > + get tables => MapCopyWith( + $value.tables, + (v, t) => v.copyWith.$chain(t), + (v) => call(tables: v), + ); @override MapCopyWith<$R, String, GameTeam, GameTeamCopyWith<$R, GameTeam, GameTeam>> - get teams => MapCopyWith( - $value.teams, (v, t) => v.copyWith.$chain(t), (v) => call(teams: v)); + get teams => MapCopyWith( + $value.teams, + (v, t) => v.copyWith.$chain(t), + (v) => call(teams: v), + ); @override - $R call( - {Object? script = $none, - Map? tables, - Map? teams}) => - $apply(FieldCopyWithData({ - if (script != $none) #script: script, - if (tables != null) #tables: tables, - if (teams != null) #teams: teams - })); + $R call({ + Object? script = $none, + Map? tables, + Map? teams, + }) => $apply( + FieldCopyWithData({ + if (script != $none) #script: script, + if (tables != null) #tables: tables, + if (teams != null) #teams: teams, + }), + ); @override GameMode $make(CopyWithData data) => GameMode( - script: data.get(#script, or: $value.script), - tables: data.get(#tables, or: $value.tables), - teams: data.get(#teams, or: $value.teams)); + script: data.get(#script, or: $value.script), + tables: data.get(#tables, or: $value.tables), + teams: data.get(#teams, or: $value.teams), + ); @override GameModeCopyWith<$R2, GameMode, $Out2> $chain<$R2, $Out2>( - Then<$Out2, $R2> t) => - _GameModeCopyWithImpl<$R2, $Out2>($value, $cast, t); + Then<$Out2, $R2> t, + ) => _GameModeCopyWithImpl<$R2, $Out2>($value, $cast, t); } diff --git a/api/lib/src/models/table.dart b/api/lib/src/models/table.dart index 565e0bcf..379dfa11 100644 --- a/api/lib/src/models/table.dart +++ b/api/lib/src/models/table.dart @@ -139,9 +139,7 @@ class ItemLocation with ItemLocationMappable { class ItemLocationHook extends MappingHook { final bool nullOnEmpty; - const ItemLocationHook({ - this.nullOnEmpty = true, - }); + const ItemLocationHook({this.nullOnEmpty = true}); @override Object? beforeDecode(Object? value) { diff --git a/api/lib/src/services/asset.dart b/api/lib/src/services/asset.dart index 07bda9aa..1332ab1d 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); } From 2de83945d9536bd8a800b185b5ef0ea165040cc5 Mon Sep 17 00:00:00 2001 From: CodeDoctorDE Date: Wed, 2 Jul 2025 12:16:28 +0200 Subject: [PATCH 03/10] Fix corepack ci --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 60ccd25f..75983d2a 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 From 94eba623585035c0815a980b3c284607cf9bd179 Mon Sep 17 00:00:00 2001 From: CodeDoctorDE Date: Wed, 2 Jul 2025 12:24:11 +0200 Subject: [PATCH 04/10] Fix serverlist route --- api/pubspec.yaml | 2 +- app/AppImageBuilder.yml | 2 +- app/lib/main.dart | 4 ++-- app/linux/debian/DEBIAN/control | 2 +- app/pubspec.lock | 2 +- app/pubspec.yaml | 2 +- metadata/en-US/changelogs/8.txt | 3 +++ plugin/pubspec.lock | 2 +- server/pubspec.lock | 2 +- server/pubspec.yaml | 2 +- 10 files changed, 13 insertions(+), 10 deletions(-) create mode 100644 metadata/en-US/changelogs/8.txt diff --git a/api/pubspec.yaml b/api/pubspec.yaml index 4381f96d..9bc1d5aa 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 1d5a7a7a..8b3c8a41 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/main.dart b/app/lib/main.dart index f9f2753c..e92d1886 100644 --- a/app/lib/main.dart +++ b/app/lib/main.dart @@ -190,7 +190,7 @@ class SetonixApp extends StatelessWidget { builder: (context, state) => const AccountsSettingsPage(), ), GoRoute( - path: 'serverlist', + path: 'servers', builder: (context, state) => const ServersSettingsPage(), ), ], @@ -206,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 756997ba..70383b2f 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 01c973ea..120ccca1 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 97a445e3..5f6e82a1 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 00000000..fe18fa4c --- /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/pubspec.lock b/plugin/pubspec.lock index bb0c3f40..bdb20cc8 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/pubspec.lock b/server/pubspec.lock index 2cb417f5..4dedbe07 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 b54760f0..ff330942 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: From 22b161a0c491ce4dbb7d2abd7e96bc54e8295697 Mon Sep 17 00:00:00 2001 From: CodeDoctorDE Date: Sat, 6 Sep 2025 01:11:40 +0200 Subject: [PATCH 05/10] Add first basic working scripts feature in server --- api/lib/src/models/kick.dart | 1 + api/lib/src/models/kick.mapper.dart | 63 ++++++++++++ api/lib/src/models/table.mapper.dart | 7 ++ app/lib/bloc/world/bloc.dart | 5 + plugin/lib/src/events/model.dart | 42 +++++++- plugin/lib/src/events/model.mapper.dart | 3 + plugin/lib/src/plugin.dart | 28 +++--- plugin/lib/src/rust/api/plugin.dart | 6 +- plugin/lib/src/rust/frb_generated.dart | 110 ++++++++++++++++++++- plugin/lib/src/rust/frb_generated.io.dart | 12 +++ plugin/lib/src/rust/frb_generated.web.dart | 12 +++ plugin/rust/src/api/luau/state.rs | 17 ++++ plugin/rust/src/api/plugin.rs | 9 +- plugin/rust/src/frb_generated.rs | 109 ++++++++++++++++++-- server/.gitignore | 1 + server/lib/src/bloc.dart | 51 ++++++++-- server/lib/src/server.dart | 1 + 17 files changed, 444 insertions(+), 33 deletions(-) diff --git a/api/lib/src/models/kick.dart b/api/lib/src/models/kick.dart index a7c49a4b..7ee30ea6 100644 --- a/api/lib/src/models/kick.dart +++ b/api/lib/src/models/kick.dart @@ -2,6 +2,7 @@ import 'package:dart_mappable/dart_mappable.dart'; part 'kick.mapper.dart'; +@MappableEnum() enum KickReason { kick, ban, diff --git a/api/lib/src/models/kick.mapper.dart b/api/lib/src/models/kick.mapper.dart index fdba3663..c25d4f1c 100644 --- a/api/lib/src/models/kick.mapper.dart +++ b/api/lib/src/models/kick.mapper.dart @@ -7,6 +7,68 @@ part of 'kick.dart'; +class KickReasonMapper extends EnumMapper { + KickReasonMapper._(); + + static KickReasonMapper? _instance; + static KickReasonMapper ensureInitialized() { + if (_instance == null) { + MapperContainer.globals.use(_instance = KickReasonMapper._()); + } + return _instance!; + } + + static KickReason fromValue(dynamic value) { + ensureInitialized(); + return MapperContainer.globals.fromValue(value); + } + + @override + KickReason decode(dynamic value) { + switch (value) { + case r'kick': + return KickReason.kick; + case r'ban': + return KickReason.ban; + case r'notWhitelisted': + return KickReason.notWhitelisted; + case r'notRegistered': + return KickReason.notRegistered; + case r'challengeFailed': + return KickReason.challengeFailed; + case r'pleaseLink': + return KickReason.pleaseLink; + default: + throw MapperException.unknownEnumValue(value); + } + } + + @override + dynamic encode(KickReason self) { + switch (self) { + case KickReason.kick: + return r'kick'; + case KickReason.ban: + return r'ban'; + case KickReason.notWhitelisted: + return r'notWhitelisted'; + case KickReason.notRegistered: + return r'notRegistered'; + case KickReason.challengeFailed: + return r'challengeFailed'; + case KickReason.pleaseLink: + return r'pleaseLink'; + } + } +} + +extension KickReasonMapperExtension on KickReason { + String toValue() { + KickReasonMapper.ensureInitialized(); + return MapperContainer.globals.toValue(this) as String; + } +} + class KickMessageMapper extends ClassMapperBase { KickMessageMapper._(); @@ -14,6 +76,7 @@ class KickMessageMapper extends ClassMapperBase { static KickMessageMapper ensureInitialized() { if (_instance == null) { MapperContainer.globals.use(_instance = KickMessageMapper._()); + KickReasonMapper.ensureInitialized(); } return _instance!; } diff --git a/api/lib/src/models/table.mapper.dart b/api/lib/src/models/table.mapper.dart index c80f0d68..4e093df1 100644 --- a/api/lib/src/models/table.mapper.dart +++ b/api/lib/src/models/table.mapper.dart @@ -614,11 +614,18 @@ class ItemLocationMapper extends ClassMapperBase { ); static String _$id(ItemLocation v) => v.id; static const Field _f$id = Field('id', _$id); + static bool _$isEmpty(ItemLocation v) => v.isEmpty; + static const Field _f$isEmpty = Field( + 'isEmpty', + _$isEmpty, + mode: FieldMode.member, + ); @override final MappableFields fields = const { #namespace: _f$namespace, #id: _f$id, + #isEmpty: _f$isEmpty, }; @override diff --git a/app/lib/bloc/world/bloc.dart b/app/lib/bloc/world/bloc.dart index ad19ad63..41072c93 100644 --- a/app/lib/bloc/world/bloc.dart +++ b/app/lib/bloc/world/bloc.dart @@ -36,6 +36,11 @@ class _WorldServerInterfaceImpl implements ServerInterface { bloc._processEvent(NetworkerPacket(event, target)); } + @override + void print(String message, [String? plugin]) { + // TODO: implement better logging + } + @override WorldState get state => bloc.state.world; diff --git a/plugin/lib/src/events/model.dart b/plugin/lib/src/events/model.dart index df5009e6..5a79190c 100644 --- a/plugin/lib/src/events/model.dart +++ b/plugin/lib/src/events/model.dart @@ -1,4 +1,6 @@ +import 'dart:async'; import 'dart:io'; +import 'dart:typed_data'; import 'package:dart_mappable/dart_mappable.dart'; import 'package:networker/networker.dart'; @@ -93,7 +95,45 @@ final class UserLeaveCallback { UserLeaveCallback({required this.channel, required this.info}); } -@MappableClass() +class _ConvertedConnectionInfo extends ConnectionInfo { + final Uri address; + + _ConvertedConnectionInfo(this.address); + + @override + FutureOr close() {} + + @override + bool get isClosed => true; + + @override + FutureOr sendMessage(Uint8List data) {} +} + +class ConnectionInfoMapper extends SimpleMapper { + const ConnectionInfoMapper(); + + @override + ConnectionInfo decode(Object value) { + if (value is Map) { + return _ConvertedConnectionInfo( + Uri.parse(value['address'] as String? ?? 'http://localhost'), + ); + } + return _ConvertedConnectionInfo(Uri.parse('http://localhost')); + } + + @override + Object? encode(ConnectionInfo self) { + return { + 'address': self is _ConvertedConnectionInfo + ? self.address.toString() + : '', + }; + } +} + +@MappableClass(includeCustomMappers: [ConnectionInfoMapper()]) final class UserJoined extends LocalWorldEvent with UserJoinedMappable { final Channel channel; final ConnectionInfo info; diff --git a/plugin/lib/src/events/model.mapper.dart b/plugin/lib/src/events/model.mapper.dart index 8c3bc4d2..5d3e45b4 100644 --- a/plugin/lib/src/events/model.mapper.dart +++ b/plugin/lib/src/events/model.mapper.dart @@ -1,5 +1,6 @@ // coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND +// dart format off // ignore_for_file: type=lint // ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member // ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter @@ -14,6 +15,7 @@ class UserJoinedMapper extends SubClassMapperBase { if (_instance == null) { MapperContainer.globals.use(_instance = UserJoinedMapper._()); LocalWorldEventMapper.ensureInitialized().addSubMapper(_instance!); + MapperContainer.globals.useAll([ConnectionInfoMapper()]); } return _instance!; } @@ -138,3 +140,4 @@ class _UserJoinedCopyWithImpl<$R, $Out> Then<$Out2, $R2> t, ) => _UserJoinedCopyWithImpl<$R2, $Out2>($value, $cast, t); } + diff --git a/plugin/lib/src/plugin.dart b/plugin/lib/src/plugin.dart index 66880016..cc74b90f 100644 --- a/plugin/lib/src/plugin.dart +++ b/plugin/lib/src/plugin.dart @@ -22,6 +22,8 @@ mixin ServerInterface { required String plugin, }); + void print(String message, [String? plugin]); + WorldState get state; List get players; } @@ -32,16 +34,16 @@ final class PluginSystem { PluginSystem({required this.server}); - SetonixPlugin registerPlugin( + Future registerPlugin( String name, - SetonixPlugin Function(PluginServerInterface) pluginBuilder, - ) { + FutureOr Function(PluginServerInterface) pluginBuilder, + ) async { final pluginServer = _PluginServerInterfaceImpl(server, name); - final plugin = pluginBuilder(pluginServer); + final plugin = await pluginBuilder(pluginServer); return _plugins[name] = plugin; } - SetonixPlugin registerLuauPlugin( + Future registerLuauPlugin( String name, String code, { void Function(String)? onPrint, @@ -52,6 +54,7 @@ final class PluginSystem { (pluginServer) => RustSetonixPlugin.build( (c) => LuauPlugin(code: code, callback: c), pluginServer, + onPrint: (e) => server.print(e, name), ), ); } @@ -83,7 +86,7 @@ final class PluginSystem { .getPack(location.namespace) ?.getScript(location.id); if (data == null) return; - registerLuauPlugin(name, data); + registerLuauPlugin(name, data, onPrint: (e) => server.print(e, name)); } bool get _nativeEnabled => RustLib.instance.initialized; @@ -167,14 +170,12 @@ final class RustSetonixPlugin extends SetonixPlugin { RustSetonixPlugin._(super.server, this.plugin); - factory RustSetonixPlugin.build( + static Future build( RustPlugin Function(PluginCallback) builder, PluginServerInterface server, { void Function(String)? onPrint, - }) { + }) async { final callback = PluginCallback.default_(); - final plugin = builder(callback); - final instance = RustSetonixPlugin._(server, plugin); if (onPrint != null) { callback.changeOnPrint(onPrint: onPrint); } @@ -195,13 +196,17 @@ final class RustSetonixPlugin extends SetonixPlugin { final state = server.state; return switch (field) { StateFieldAccess.info => state.info.toJson(), - StateFieldAccess.table => state.table.toJson(), + StateFieldAccess.tables => jsonEncode( + state.data.getTables().toList(), + ), StateFieldAccess.tableName => jsonEncode(state.tableName), StateFieldAccess.players => jsonEncode(server.players), StateFieldAccess.teamMembers => jsonEncode(state.teamMembers), }; }, ); + final plugin = builder(callback); + final instance = RustSetonixPlugin._(server, plugin); instance.eventSystem.on((e) { instance.plugin.runEvent( eventType: e.clientEvent.runtimeType.toString(), @@ -211,6 +216,7 @@ final class RustSetonixPlugin extends SetonixPlugin { source: e.source, ); }); + await instance.plugin.run(); return instance; } diff --git a/plugin/lib/src/rust/api/plugin.dart b/plugin/lib/src/rust/api/plugin.dart index 324ca3bd..0321f48e 100644 --- a/plugin/lib/src/rust/api/plugin.dart +++ b/plugin/lib/src/rust/api/plugin.dart @@ -27,6 +27,10 @@ abstract class PluginCallback implements RustOpaqueInterface { required FutureOr Function(StateFieldAccess) stateFieldAccess, }); + void changeTableAccess({ + required FutureOr Function(String?) tableAccess, + }); + static PluginCallback default_() => RustLib.instance.api.crateApiPluginPluginCallbackDefault(); } @@ -64,4 +68,4 @@ class EventResult { needsUpdate == other.needsUpdate; } -enum StateFieldAccess { table, tableName, info, players, teamMembers } +enum StateFieldAccess { tableName, tables, info, players, teamMembers } diff --git a/plugin/lib/src/rust/frb_generated.dart b/plugin/lib/src/rust/frb_generated.dart index 7f16b3bb..0d00d8a4 100644 --- a/plugin/lib/src/rust/frb_generated.dart +++ b/plugin/lib/src/rust/frb_generated.dart @@ -66,7 +66,7 @@ class RustLib extends BaseEntrypoint { String get codegenVersion => '2.11.1'; @override - int get rustContentHash => 2139481266; + int get rustContentHash => -1139025024; static const kDefaultExternalLibraryLoaderConfig = ExternalLibraryLoaderConfig( @@ -113,6 +113,11 @@ abstract class RustLibApi extends BaseApi { required FutureOr Function(StateFieldAccess) stateFieldAccess, }); + void crateApiPluginPluginCallbackChangeTableAccess({ + required PluginCallback that, + required FutureOr Function(String?) tableAccess, + }); + PluginCallback crateApiPluginPluginCallbackDefault(); Future crateApiSimpleSimpleAdderTwinNormal({ @@ -409,13 +414,49 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { ); @override - PluginCallback crateApiPluginPluginCallbackDefault() { + void crateApiPluginPluginCallbackChangeTableAccess({ + required PluginCallback that, + required FutureOr Function(String?) tableAccess, + }) { return handler.executeSync( SyncTask( callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPluginCallback( + that, + serializer, + ); + sse_encode_DartFn_Inputs_opt_String_Output_String_AnyhowException( + tableAccess, + serializer, + ); return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 8)!; }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: null, + ), + constMeta: kCrateApiPluginPluginCallbackChangeTableAccessConstMeta, + argValues: [that, tableAccess], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiPluginPluginCallbackChangeTableAccessConstMeta => + const TaskConstMeta( + debugName: "PluginCallback_change_table_access", + argNames: ["that", "tableAccess"], + ); + + @override + PluginCallback crateApiPluginPluginCallbackDefault() { + return handler.executeSync( + SyncTask( + callFfi: () { + final serializer = SseSerializer(generalizedFrbRustBinding); + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 9)!; + }, codec: SseCodec( decodeSuccessData: sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPluginCallback, @@ -445,7 +486,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 11, + funcId: 12, port: port_, ); }, @@ -573,6 +614,41 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { }; } + Future Function(int, dynamic) + encode_DartFn_Inputs_opt_String_Output_String_AnyhowException( + FutureOr Function(String?) raw, + ) { + return (callId, rawArg0) async { + final arg0 = dco_decode_opt_String(rawArg0); + + Box? rawOutput; + Box? rawError; + try { + rawOutput = Box(await raw(arg0)); + } catch (e, s) { + rawError = Box(AnyhowException("$e\n\n$s")); + } + + final serializer = SseSerializer(generalizedFrbRustBinding); + assert((rawOutput != null) ^ (rawError != null)); + if (rawOutput != null) { + serializer.buffer.putUint8(0); + sse_encode_String(rawOutput.value, serializer); + } else { + serializer.buffer.putUint8(1); + sse_encode_AnyhowException(rawError!.value, serializer); + } + final output = serializer.intoRaw(); + + generalizedFrbRustBinding.dartFnDeliverOutput( + callId: callId, + ptr: output.ptr, + rustVecLen: output.rustVecLen, + dataLen: output.dataLen, + ); + }; + } + Future Function(int, dynamic) encode_DartFn_Inputs_state_field_access_Output_String_AnyhowException( FutureOr Function(StateFieldAccess) raw, @@ -691,6 +767,15 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { throw UnimplementedError(''); } + @protected + FutureOr Function(String?) + dco_decode_DartFn_Inputs_opt_String_Output_String_AnyhowException( + dynamic raw, + ) { + // Codec=Dco (DartCObject based), see doc to use other codecs + throw UnimplementedError(''); + } + @protected FutureOr Function(StateFieldAccess) dco_decode_DartFn_Inputs_state_field_access_Output_String_AnyhowException( @@ -1185,6 +1270,18 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { ); } + @protected + void sse_encode_DartFn_Inputs_opt_String_Output_String_AnyhowException( + FutureOr Function(String?) self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_DartOpaque( + encode_DartFn_Inputs_opt_String_Output_String_AnyhowException(self), + serializer, + ); + } + @protected void sse_encode_DartFn_Inputs_state_field_access_Output_String_AnyhowException( @@ -1470,4 +1567,11 @@ class PluginCallbackImpl extends RustOpaque implements PluginCallback { that: this, stateFieldAccess: stateFieldAccess, ); + + void changeTableAccess({ + required FutureOr Function(String?) tableAccess, + }) => RustLib.instance.api.crateApiPluginPluginCallbackChangeTableAccess( + that: this, + tableAccess: tableAccess, + ); } diff --git a/plugin/lib/src/rust/frb_generated.io.dart b/plugin/lib/src/rust/frb_generated.io.dart index 368100d8..25946210 100644 --- a/plugin/lib/src/rust/frb_generated.io.dart +++ b/plugin/lib/src/rust/frb_generated.io.dart @@ -71,6 +71,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { dynamic raw, ); + @protected + FutureOr Function(String?) + dco_decode_DartFn_Inputs_opt_String_Output_String_AnyhowException( + dynamic raw, + ); + @protected FutureOr Function(StateFieldAccess) dco_decode_DartFn_Inputs_state_field_access_Output_String_AnyhowException( @@ -305,6 +311,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseSerializer serializer, ); + @protected + void sse_encode_DartFn_Inputs_opt_String_Output_String_AnyhowException( + FutureOr Function(String?) self, + SseSerializer serializer, + ); + @protected void sse_encode_DartFn_Inputs_state_field_access_Output_String_AnyhowException( diff --git a/plugin/lib/src/rust/frb_generated.web.dart b/plugin/lib/src/rust/frb_generated.web.dart index 34fedc43..d4f352e2 100644 --- a/plugin/lib/src/rust/frb_generated.web.dart +++ b/plugin/lib/src/rust/frb_generated.web.dart @@ -73,6 +73,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { dynamic raw, ); + @protected + FutureOr Function(String?) + dco_decode_DartFn_Inputs_opt_String_Output_String_AnyhowException( + dynamic raw, + ); + @protected FutureOr Function(StateFieldAccess) dco_decode_DartFn_Inputs_state_field_access_Output_String_AnyhowException( @@ -307,6 +313,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseSerializer serializer, ); + @protected + void sse_encode_DartFn_Inputs_opt_String_Output_String_AnyhowException( + FutureOr Function(String?) self, + SseSerializer serializer, + ); + @protected void sse_encode_DartFn_Inputs_state_field_access_Output_String_AnyhowException( diff --git a/plugin/rust/src/api/luau/state.rs b/plugin/rust/src/api/luau/state.rs index 69dd52c0..cc600f9e 100644 --- a/plugin/rust/src/api/luau/state.rs +++ b/plugin/rust/src/api/luau/state.rs @@ -19,5 +19,22 @@ impl LuaUserData for LuauStateUserData { Ok(serialized) }); } + fields.add_field_method_get("Table", move |lua, this: &LuauStateUserData| { + let callback = this.0.table_access.clone(); + let result = block_on(callback(None)); + let result = serde_json::from_str::(&result).unwrap(); + let serialized = lua.to_value(&result).unwrap(); + Ok(serialized) + }); + } + + fn add_methods>(methods: &mut M) { + methods.add_method("GetTable", |lua, this: &LuauStateUserData, table_name: Option| { + let callback = this.0.table_access.clone(); + let result = block_on(callback(table_name)); + let result = serde_json::from_str::(&result).unwrap(); + let serialized = lua.to_value(&result).unwrap(); + Ok(serialized) + }); } } diff --git a/plugin/rust/src/api/plugin.rs b/plugin/rust/src/api/plugin.rs index e320f628..a8c2b4dc 100644 --- a/plugin/rust/src/api/plugin.rs +++ b/plugin/rust/src/api/plugin.rs @@ -13,8 +13,8 @@ pub type DartCallback2 = Arc DartFnFuture<()> + Send + Syn #[derive(strum::Display, strum::EnumIter, Clone, Copy)] pub enum StateFieldAccess { - Table, TableName, + Tables, Info, Players, TeamMembers, @@ -26,6 +26,7 @@ pub struct PluginCallback { pub(crate) process_event: DartCallback2>, pub(crate) send_event: DartCallback2>, pub(crate) state_field_access: Arc DartFnFuture + Send + Sync>, + pub(crate) table_access: Arc) -> DartFnFuture + Send + Sync>, } impl Default for PluginCallback { @@ -40,6 +41,7 @@ impl Default for PluginCallback { process_event: Arc::new(|_, _| Box::pin(async {})), send_event: Arc::new(|_, _| Box::pin(async {})), state_field_access: Arc::new(|_| Box::pin(async { "".to_string() })), + table_access: Arc::new(|_| Box::pin(async { "".to_string() })), } } } @@ -63,6 +65,11 @@ impl PluginCallback { pub fn change_state_field_access(&mut self, state_field_access: impl Fn(StateFieldAccess) -> DartFnFuture + 'static + Send + Sync) { self.state_field_access = Arc::new(Box::new(state_field_access)); // or sth like that } + + #[frb(sync)] + pub fn change_table_access(&mut self, table_access: impl Fn(Option) -> DartFnFuture + 'static + Send + Sync) { + self.table_access = Arc::new(Box::new(table_access)); // or sth like that + } } pub type Channel = i16; diff --git a/plugin/rust/src/frb_generated.rs b/plugin/rust/src/frb_generated.rs index 7da3bb05..6e1c2881 100644 --- a/plugin/rust/src/frb_generated.rs +++ b/plugin/rust/src/frb_generated.rs @@ -40,7 +40,7 @@ flutter_rust_bridge::frb_generated_boilerplate!( default_rust_auto_opaque = RustAutoOpaqueMoi, ); pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.11.1"; -pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = 2139481266; +pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = -1139025024; // Section: executor @@ -411,6 +411,60 @@ fn wire__crate__api__plugin__PluginCallback_change_state_field_access_impl( }, ) } +fn wire__crate__api__plugin__PluginCallback_change_table_access_impl( + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) -> flutter_rust_bridge::for_generated::WireSyncRust2DartSse { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_sync::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "PluginCallback_change_table_access", + port: None, + mode: flutter_rust_bridge::for_generated::FfiCallMode::Sync, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_that = , + >>::sse_decode(&mut deserializer); + let api_table_access = decode_DartFn_Inputs_opt_String_Output_String_AnyhowException( + ::sse_decode(&mut deserializer), + ); + deserializer.end(); + transform_result_sse::<_, ()>((move || { + let mut api_that_guard = None; + let decode_indices_ = + flutter_rust_bridge::for_generated::lockable_compute_decode_order(vec![ + flutter_rust_bridge::for_generated::LockableOrderInfo::new( + &api_that, 0, true, + ), + ]); + for i in decode_indices_ { + match i { + 0 => api_that_guard = Some(api_that.lockable_decode_sync_ref_mut()), + _ => unreachable!(), + } + } + let mut api_that_guard = api_that_guard.unwrap(); + let output_ok = Result::<_, ()>::Ok({ + crate::api::plugin::PluginCallback::change_table_access( + &mut *api_that_guard, + api_table_access, + ); + })?; + Ok(output_ok) + })()) + }, + ) +} fn wire__crate__api__plugin__PluginCallback_default_impl( ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, rust_vec_len_: i32, @@ -591,6 +645,38 @@ fn decode_DartFn_Inputs_String_opt_box_autoadd_i_16_Output_unit_AnyhowException( )) } } +fn decode_DartFn_Inputs_opt_String_Output_String_AnyhowException( + dart_opaque: flutter_rust_bridge::DartOpaque, +) -> impl Fn(Option) -> flutter_rust_bridge::DartFnFuture { + use flutter_rust_bridge::IntoDart; + + async fn body(dart_opaque: flutter_rust_bridge::DartOpaque, arg0: Option) -> String { + let args = vec![arg0.into_into_dart().into_dart()]; + let message = FLUTTER_RUST_BRIDGE_HANDLER + .dart_fn_invoke(dart_opaque, args) + .await; + + let mut deserializer = flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let action = deserializer.cursor.read_u8().unwrap(); + let ans = match action { + 0 => std::result::Result::Ok(::sse_decode(&mut deserializer)), + 1 => std::result::Result::Err( + ::sse_decode(&mut deserializer), + ), + _ => unreachable!(), + }; + deserializer.end(); + let ans = ans.expect("Dart throws exception but Rust side assume it is not failable"); + ans + } + + move |arg0: Option| { + flutter_rust_bridge::for_generated::convert_into_dart_fn_future(body( + dart_opaque.clone(), + arg0, + )) + } +} fn decode_DartFn_Inputs_state_field_access_Output_String_AnyhowException( dart_opaque: flutter_rust_bridge::DartOpaque, ) -> impl Fn(crate::api::plugin::StateFieldAccess) -> flutter_rust_bridge::DartFnFuture { @@ -823,8 +909,8 @@ impl SseDecode for crate::api::plugin::StateFieldAccess { fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { let mut inner = ::sse_decode(deserializer); return match inner { - 0 => crate::api::plugin::StateFieldAccess::Table, - 1 => crate::api::plugin::StateFieldAccess::TableName, + 0 => crate::api::plugin::StateFieldAccess::TableName, + 1 => crate::api::plugin::StateFieldAccess::Tables, 2 => crate::api::plugin::StateFieldAccess::Info, 3 => crate::api::plugin::StateFieldAccess::Players, 4 => crate::api::plugin::StateFieldAccess::TeamMembers, @@ -863,7 +949,7 @@ fn pde_ffi_dispatcher_primary_impl( match func_id { 2 => wire__crate__api__luau__LuauPlugin_run_impl(port, ptr, rust_vec_len, data_len), 3 => wire__crate__api__luau__LuauPlugin_run_event_impl(port, ptr, rust_vec_len, data_len), - 11 => wire__crate__api__simple__simple_adder_twin_normal_impl( + 12 => wire__crate__api__simple__simple_adder_twin_normal_impl( port, ptr, rust_vec_len, @@ -902,7 +988,12 @@ fn pde_ffi_dispatcher_sync_impl( rust_vec_len, data_len, ), - 8 => wire__crate__api__plugin__PluginCallback_default_impl(ptr, rust_vec_len, data_len), + 8 => wire__crate__api__plugin__PluginCallback_change_table_access_impl( + ptr, + rust_vec_len, + data_len, + ), + 9 => wire__crate__api__plugin__PluginCallback_default_impl(ptr, rust_vec_len, data_len), _ => unreachable!(), } } @@ -965,8 +1056,8 @@ impl flutter_rust_bridge::IntoIntoDart impl flutter_rust_bridge::IntoDart for crate::api::plugin::StateFieldAccess { fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { match self { - Self::Table => 0.into_dart(), - Self::TableName => 1.into_dart(), + Self::TableName => 0.into_dart(), + Self::Tables => 1.into_dart(), Self::Info => 2.into_dart(), Self::Players => 3.into_dart(), Self::TeamMembers => 4.into_dart(), @@ -1155,8 +1246,8 @@ impl SseEncode for crate::api::plugin::StateFieldAccess { fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { ::sse_encode( match self { - crate::api::plugin::StateFieldAccess::Table => 0, - crate::api::plugin::StateFieldAccess::TableName => 1, + crate::api::plugin::StateFieldAccess::TableName => 0, + crate::api::plugin::StateFieldAccess::Tables => 1, crate::api::plugin::StateFieldAccess::Info => 2, crate::api::plugin::StateFieldAccess::Players => 3, crate::api::plugin::StateFieldAccess::TeamMembers => 4, diff --git a/server/.gitignore b/server/.gitignore index 16cd4faf..7e280e4f 100644 --- a/server/.gitignore +++ b/server/.gitignore @@ -6,6 +6,7 @@ doc/ # Runtime files world.stnx packs/ +scripts/ setonix.db config.json diff --git a/server/lib/src/bloc.dart b/server/lib/src/bloc.dart index b3e0c47a..7b7b4fe6 100644 --- a/server/lib/src/bloc.dart +++ b/server/lib/src/bloc.dart @@ -41,7 +41,6 @@ class WorldBloc extends Bloc ), ) { _pluginSystem = PluginSystem(server: this); - _serverPlugin = _pluginSystem.registerPlugin('', SetonixPlugin.new); on((event, emit) async { final signature = assetManager.createSignature(); final processed = await _computeEvent( @@ -56,7 +55,11 @@ class WorldBloc extends Bloc "World initialized${(event.info?.script != null) ? " with script ${event.info?.script}" : ""}", level: LogLevel.info, ); - await _loadScript((newState ?? state).info.script); + _serverPlugin = await _pluginSystem.registerPlugin( + '', + SetonixPlugin.new, + ); + await _loadScripts((newState ?? state).info.script); } if (newState == null) return; emit(newState); @@ -67,17 +70,51 @@ class WorldBloc extends Bloc }); } - Future _loadScript(ItemLocation? location) async { + @override + void print(String message, [String? plugin]) { + if (plugin != null && plugin.isNotEmpty) { + server.log("[$plugin] $message", level: LogLevel.info); + } else { + server.log(message, level: LogLevel.info); + } + } + + Future _loadScripts(ItemLocation? location) async { try { - if (location == null) return; - pluginSystem.loadLuaPluginFromLocation(assetManager, location); + if (location != null) { + pluginSystem.loadLuaPluginFromLocation(assetManager, location); + } } catch (e) { server.log('Error loading script: $e', level: LogLevel.error); } + + final scriptsFolder = Directory('scripts'); + if (!await scriptsFolder.exists()) { + await scriptsFolder.create(recursive: true); + } + final scriptFiles = (await scriptsFolder.list().toList()) + .whereType() + .where((file) => file.path.endsWith('.lua')); + server.log( + "Found ${scriptFiles.length} script file(s)", + level: LogLevel.info, + ); + for (final file in scriptFiles) { + try { + final code = await file.readAsString(); + final relativePath = file.path.substring(scriptsFolder.path.length + 1); + pluginSystem.registerLuauPlugin(relativePath, code); + } catch (e) { + server.log( + 'Error loading script from ${file.path}: $e', + level: LogLevel.warning, + ); + } + } } Future init() async { - await _loadScript(state.info.script); + await _loadScripts(state.info.script); } Future resetWorld([ItemLocation? mode]) async { @@ -128,7 +165,7 @@ class WorldBloc extends Bloc worldName: worldName, ); if (!force) { - server.defaultEventSystem.fire(event); + server.defaultWorld.pluginSystem.fire(event); if (event.cancelled) return; server.log( 'Processing event by ${event.source}: ${limitOutput(event.clientEvent)}, answered with ${limitOutput(event.serverEvent)}', diff --git a/server/lib/src/server.dart b/server/lib/src/server.dart index 37fb6679..624847bb 100644 --- a/server/lib/src/server.dart +++ b/server/lib/src/server.dart @@ -217,6 +217,7 @@ final class SetonixServer { 'whitelist': WhitelistProgram(this), null: UnknownProgram(), }); + await defaultWorld.init(); } void _onClientEvent( From 83f18bc562a0434986086a298496fc59c935c8b4 Mon Sep 17 00:00:00 2001 From: CodeDoctorDE Date: Mon, 22 Sep 2025 14:37:15 +0200 Subject: [PATCH 06/10] Finish basic game mode support --- api/lib/src/models/config.dart | 1 + api/lib/src/models/data.dart | 20 +++++---- api/lib/src/models/info.dart | 4 +- api/lib/src/models/info.mapper.dart | 8 ++-- app/lib/bloc/world/bloc.dart | 17 +++++-- app/pack/modes/hello.json | 3 ++ app/pack/scripts/hello.lua | 5 +++ plugin/lib/src/plugin.dart | 31 +++++-------- server/lib/src/bloc.dart | 35 ++++++++++----- server/lib/src/config.dart | 4 +- server/lib/src/main.dart | 10 +++++ server/lib/src/programs/scripts.dart | 28 ++++++++++++ server/lib/src/server.dart | 67 +++++++++++++++++++++++++--- tools/build_server.dart | 1 - 14 files changed, 175 insertions(+), 59 deletions(-) create mode 100644 app/pack/modes/hello.json create mode 100644 app/pack/scripts/hello.lua create mode 100644 server/lib/src/programs/scripts.dart delete mode 100644 tools/build_server.dart diff --git a/api/lib/src/models/config.dart b/api/lib/src/models/config.dart index 47466c6a..bc5c5fea 100644 --- a/api/lib/src/models/config.dart +++ b/api/lib/src/models/config.dart @@ -152,5 +152,6 @@ final class SetonixConfig with SetonixConfigMappable { whitelistEnabled: other.whitelistEnabled ?? whitelistEnabled, apiEndpoint: other.apiEndpoint ?? apiEndpoint, endpointSecret: other.endpointSecret ?? endpointSecret, + gameMode: other.gameMode ?? gameMode, ); } diff --git a/api/lib/src/models/data.dart b/api/lib/src/models/data.dart index 79d7f476..07a3067e 100644 --- a/api/lib/src/models/data.dart +++ b/api/lib/src/models/data.dart @@ -38,17 +38,19 @@ class SetonixData extends ArchiveData { : identifier = identifier ?? createPackIdentifier(data), super.fromBytes(); - factory SetonixData.fromMode(ItemLocation? location, GameMode? mode) { + factory SetonixData.fromMode( + PackItem? mode, { + Set packs = const {}, + }) { + if (mode == null) return SetonixData.empty(); var data = SetonixData.empty().setInfo( GameInfo( - packs: [?location?.namespace], - script: location, - teams: mode?.teams ?? const {}, + packs: {...packs, mode.namespace}.toList(), + gameMode: mode.location, + teams: mode.item.teams, ), ); - for (final entry - in mode?.tables.entries ?? - Iterable>.empty()) { + for (final entry in mode.item.tables.entries) { data = data.setTable(entry.value, entry.key); } return data; @@ -413,6 +415,6 @@ final class PackItem { String get namespace => location.namespace; String get id => location.id; - PackItem withItem(E backgroundTranslation) => - PackItem(pack: pack, location: location, item: backgroundTranslation); + PackItem withItem(E item) => + PackItem(pack: pack, location: location, item: item); } diff --git a/api/lib/src/models/info.dart b/api/lib/src/models/info.dart index ce4f3649..6c31566c 100644 --- a/api/lib/src/models/info.dart +++ b/api/lib/src/models/info.dart @@ -8,9 +8,9 @@ part 'info.mapper.dart'; class GameInfo with GameInfoMappable { final Map teams; final List packs; - final ItemLocation? script; + final ItemLocation? gameMode; - const GameInfo({this.teams = const {}, this.packs = const [], this.script}); + const GameInfo({this.teams = const {}, this.packs = const [], this.gameMode}); } @MappableEnum() diff --git a/api/lib/src/models/info.mapper.dart b/api/lib/src/models/info.mapper.dart index 744c163f..7fc717fc 100644 --- a/api/lib/src/models/info.mapper.dart +++ b/api/lib/src/models/info.mapper.dart @@ -119,7 +119,7 @@ class GameInfoMapper extends ClassMapperBase { opt: true, def: const [], ); - static ItemLocation? _$script(GameInfo v) => v.script; + static ItemLocation? _$script(GameInfo v) => v.gameMode; static const Field _f$script = Field( 'script', _$script, @@ -137,7 +137,7 @@ class GameInfoMapper extends ClassMapperBase { return GameInfo( teams: data.dec(_f$teams), packs: data.dec(_f$packs), - script: data.dec(_f$script), + gameMode: data.dec(_f$script), ); } @@ -234,7 +234,7 @@ class _GameInfoCopyWithImpl<$R, $Out> ); @override ItemLocationCopyWith<$R, ItemLocation, ItemLocation>? get script => - $value.script?.copyWith.$chain((v) => call(script: v)); + $value.gameMode?.copyWith.$chain((v) => call(script: v)); @override $R call({ Map? teams, @@ -251,7 +251,7 @@ class _GameInfoCopyWithImpl<$R, $Out> GameInfo $make(CopyWithData data) => GameInfo( teams: data.get(#teams, or: $value.teams), packs: data.get(#packs, or: $value.packs), - script: data.get(#script, or: $value.script), + gameMode: data.get(#script, or: $value.gameMode), ); @override diff --git a/app/lib/bloc/world/bloc.dart b/app/lib/bloc/world/bloc.dart index 41072c93..88e6903e 100644 --- a/app/lib/bloc/world/bloc.dart +++ b/app/lib/bloc/world/bloc.dart @@ -159,7 +159,8 @@ class WorldBloc extends Bloc { ); }); if (!state.multiplayer.isClient) { - _loadScript(state.world.info.script); + final mode = state.world.info.gameMode; + if (mode != null) _loadGameMode(mode); } } @@ -227,10 +228,20 @@ class WorldBloc extends Bloc { } } - Future _loadScript(ItemLocation? location) async { + Future _loadGameMode(ItemLocation? location) async { try { if (location == null) return; - pluginSystem.loadLuaPluginFromLocation(state.assetManager, location); + final gameMode = state.assetManager + .getPack(location.namespace) + ?.getMode(location.id); + if (gameMode == null) return; + final script = gameMode.script; + if (script != null && script.isNotEmpty) { + pluginSystem.loadLuaPluginFromLocation( + state.assetManager, + ItemLocation(location.namespace, script), + ); + } // ignore: empty_catches } catch (e) {} } diff --git a/app/pack/modes/hello.json b/app/pack/modes/hello.json new file mode 100644 index 00000000..45cc5bae --- /dev/null +++ b/app/pack/modes/hello.json @@ -0,0 +1,3 @@ +{ + "script": "hello.lua" +} \ No newline at end of file diff --git a/app/pack/scripts/hello.lua b/app/pack/scripts/hello.lua new file mode 100644 index 00000000..4d51f376 --- /dev/null +++ b/app/pack/scripts/hello.lua @@ -0,0 +1,5 @@ +print("Hello from Lua script!") + +Events.CellHideChanged.Connect(function() + print("cell hide changed") +end) diff --git a/plugin/lib/src/plugin.dart b/plugin/lib/src/plugin.dart index cc74b90f..55953734 100644 --- a/plugin/lib/src/plugin.dart +++ b/plugin/lib/src/plugin.dart @@ -38,16 +38,13 @@ final class PluginSystem { String name, FutureOr Function(PluginServerInterface) pluginBuilder, ) async { + unregisterPlugin(name); final pluginServer = _PluginServerInterfaceImpl(server, name); final plugin = await pluginBuilder(pluginServer); return _plugins[name] = plugin; } - Future registerLuauPlugin( - String name, - String code, { - void Function(String)? onPrint, - }) { + Future registerLuauPlugin(String name, String code) { if (!_nativeEnabled) throw Exception('Native not enabled'); return registerPlugin( name, @@ -72,30 +69,22 @@ final class PluginSystem { .getPack(location.namespace) ?.getScript(location.id); if (data == null) return; - loadLuaPlugin(assetManager, data, name); - } - - void loadLuaPlugin( - AssetManager assetManager, - String script, [ - String name = 'game', - ]) { - unregisterPlugin(name); - final location = ItemLocation.fromString(script); - final data = assetManager - .getPack(location.namespace) - ?.getScript(location.id); - if (data == null) return; - registerLuauPlugin(name, data, onPrint: (e) => server.print(e, name)); + registerLuauPlugin(name, data); } bool get _nativeEnabled => RustLib.instance.initialized; + Iterable get plugins => _plugins.keys; + void dispose([bool disposeNative = true]) { - List.from(_plugins.keys).forEach(unregisterPlugin); + unregisterAll(); if (disposeNative) disposePluginSystem(); } + void unregisterAll() { + List.from(_plugins.keys).forEach(unregisterPlugin); + } + void fire(Event event) { for (final plugin in _plugins.values) { plugin.eventSystem.fire(event); diff --git a/server/lib/src/bloc.dart b/server/lib/src/bloc.dart index 7b7b4fe6..e490b193 100644 --- a/server/lib/src/bloc.dart +++ b/server/lib/src/bloc.dart @@ -16,6 +16,8 @@ Future _computeEvent( ); } +const scriptSuffix = '.lua'; + class WorldBloc extends Bloc with ServerInterface { final SetonixServer server; @@ -52,14 +54,14 @@ class WorldBloc extends Bloc processed.responses.forEach(process); if (event is WorldInitialized) { server.log( - "World initialized${(event.info?.script != null) ? " with script ${event.info?.script}" : ""}", + "World initialized${(event.info?.gameMode != null) ? " with script ${event.info?.gameMode}" : ""}", level: LogLevel.info, ); _serverPlugin = await _pluginSystem.registerPlugin( '', SetonixPlugin.new, ); - await _loadScripts((newState ?? state).info.script); + await _loadScripts((newState ?? state).info.gameMode); } if (newState == null) return; emit(newState); @@ -79,11 +81,19 @@ class WorldBloc extends Bloc } } - Future _loadScripts(ItemLocation? location) async { + Future _loadGameMode(ItemLocation location) async { + final mode = assetManager.getPack(location.namespace)?.getMode(location.id); + if (mode == null) return; + final script = mode.script; + if (script == null) return; + final scriptLocation = ItemLocation.fromString(script, location.namespace); + pluginSystem.loadLuaPluginFromLocation(assetManager, scriptLocation); + } + + Future _loadScripts(ItemLocation? mode) async { + pluginSystem.unregisterAll(); try { - if (location != null) { - pluginSystem.loadLuaPluginFromLocation(assetManager, location); - } + if (mode != null) await _loadGameMode(mode); } catch (e) { server.log('Error loading script: $e', level: LogLevel.error); } @@ -94,7 +104,7 @@ class WorldBloc extends Bloc } final scriptFiles = (await scriptsFolder.list().toList()) .whereType() - .where((file) => file.path.endsWith('.lua')); + .where((file) => file.path.endsWith(scriptSuffix)); server.log( "Found ${scriptFiles.length} script file(s)", level: LogLevel.info, @@ -102,8 +112,11 @@ class WorldBloc extends Bloc for (final file in scriptFiles) { try { final code = await file.readAsString(); - final relativePath = file.path.substring(scriptsFolder.path.length + 1); - pluginSystem.registerLuauPlugin(relativePath, code); + final relativePath = file.path.substring( + scriptsFolder.path.length + 1, + file.path.length - scriptSuffix.length, + ); + await pluginSystem.registerLuauPlugin(relativePath, code); } catch (e) { server.log( 'Error loading script from ${file.path}: $e', @@ -114,7 +127,7 @@ class WorldBloc extends Bloc } Future init() async { - await _loadScripts(state.info.script); + await _loadScripts(state.info.gameMode); } Future resetWorld([ItemLocation? mode]) async { @@ -124,7 +137,7 @@ class WorldBloc extends Bloc Future save({bool force = false}) async { var file = File( - worldName == defaultWorldName ? 'world.stnx' : 'worlds/$worldName.stnx', + '${worldName == defaultWorldName ? SetonixServer.defaultWorldName : '${SetonixServer.worldDirectory}/$worldName'}${SetonixServer.worldSuffix}', ); if (!await file.exists()) { await file.create(recursive: true); diff --git a/server/lib/src/config.dart b/server/lib/src/config.dart index 697d2f61..ac3f9350 100644 --- a/server/lib/src/config.dart +++ b/server/lib/src/config.dart @@ -10,7 +10,9 @@ class ConfigManager { SetonixConfig _argsConfig = SetonixConfig(); ConfigManager({SetonixConfig? argsConfig, SetonixConfig? envConfig}) - : _envConfig = envConfig ?? SetonixConfig.fromEnvironment(); + : _envConfig = (envConfig ?? SetonixConfig.fromEnvironment()).merge( + argsConfig ?? SetonixConfig(), + ); SetonixConfig _mergeConfig() { return _config.merge(_envConfig).merge(_argsConfig); diff --git a/server/lib/src/main.dart b/server/lib/src/main.dart index 2c6d1bb8..a22b01d9 100644 --- a/server/lib/src/main.dart +++ b/server/lib/src/main.dart @@ -60,6 +60,11 @@ ArgParser buildParser() { negatable: false, help: "Enable multi-world support", defaultsTo: false, + ) + ..addOption( + 'game-mode', + abbr: 'g', + help: 'The game mode to load. Otherwise it is a sandbox.', ); } @@ -81,6 +86,7 @@ Future runServer(List arguments, [ServerLoader? onLoad]) async { try { final ArgResults results = argParser.parse(arguments); bool verbose = false, autosave = false, multiWorld = false; + String? gameMode; int maxPlayers = 10; // Process the parsed arguments. @@ -112,6 +118,9 @@ Future runServer(List arguments, [ServerLoader? onLoad]) async { if (results.wasParsed('host')) { host = results['host']; } + if (results.wasParsed('game-mode')) { + gameMode = results['game-mode']; + } final server = await SetonixServer.load( argsConfig: SetonixConfig( host: host, @@ -120,6 +129,7 @@ Future runServer(List arguments, [ServerLoader? onLoad]) async { description: description, maxPlayers: maxPlayers, multiWorld: multiWorld, + gameMode: gameMode, ), ); await server.init(verbose: verbose); diff --git a/server/lib/src/programs/scripts.dart b/server/lib/src/programs/scripts.dart new file mode 100644 index 00000000..4a577ea2 --- /dev/null +++ b/server/lib/src/programs/scripts.dart @@ -0,0 +1,28 @@ +import 'package:consoler/consoler.dart'; +import 'package:setonix_server/setonix_server.dart'; + +class ScriptsProgram extends ConsoleProgram { + final SetonixServer server; + + ScriptsProgram(this.server); + + @override + String getDescription() => "Show all loaded scripts"; + + @override + String getUsage() => "[world]"; + + @override + void run(String label, List args) { + String world = defaultWorldName; + if (args.length > 1) return print("Usage: $label [world]"); + if (args.length == 1) world = args[0]; + print("-----"); + final scripts = server.getWorld(world)?.pluginSystem.plugins.toList() ?? []; + print("Loaded ${scripts.length} script(s)."); + for (final script in scripts) { + print("> $script"); + } + print("-----"); + } +} diff --git a/server/lib/src/server.dart b/server/lib/src/server.dart index 624847bb..3289861d 100644 --- a/server/lib/src/server.dart +++ b/server/lib/src/server.dart @@ -14,6 +14,7 @@ import 'package:setonix_server/src/programs/players.dart'; import 'package:setonix_server/src/programs/reset.dart'; import 'package:setonix_server/src/programs/save.dart'; import 'package:setonix_server/src/programs/say.dart'; +import 'package:setonix_server/src/programs/scripts.dart'; import 'package:setonix_server/src/programs/stop.dart'; import 'package:setonix_plugin/setonix_plugin.dart'; import 'package:setonix_server/src/programs/whitelist.dart'; @@ -29,6 +30,10 @@ String limitOutput(Object? value, [int limit = 500]) { } final class SetonixServer { + static const String defaultWorldName = 'world'; + static const String worldDirectory = 'worlds'; + static const String worldSuffix = '.stnx'; + final Consoler consoler; final ConfigManager configManager; final ServerAssetManager assetManager; @@ -52,14 +57,16 @@ final class SetonixServer { SetonixData _buildDefaultWorld() { final location = configManager.gameMode; - GameMode? gameMode; + PackItem? gameMode; if (location != null) { - gameMode = assetManager.getPack(location.namespace)?.getMode(location.id); + gameMode = assetManager + .getPack(location.namespace) + ?.getModeItem(location.id, location.namespace); } final data = SetonixData.fromMode( - location, gameMode, - ).setInfo(GameInfo(packs: assetManager.getPackIds().toList())); + packs: assetManager.getPackIds().toSet(), + ); return data; } @@ -131,8 +138,6 @@ final class SetonixServer { void log(Object? message, {LogLevel? level}) => consoler.print(message, level: level); - static final String defaultWorldFile = 'world.stnx'; - Map get players => Map.fromEntries( (_server?.clientConnections ?? {}).map( (e) => MapEntry(e, _server!.getConnectionInfo(e)!), @@ -215,9 +220,10 @@ final class SetonixServer { 'reset': ResetProgram(this), 'kick': KickProgram(this), 'whitelist': WhitelistProgram(this), + 'scripts': ScriptsProgram(this), null: UnknownProgram(), }); - await defaultWorld.init(); + await loadWorlds(); } void _onClientEvent( @@ -282,6 +288,53 @@ final class SetonixServer { challengeManager?.removeChallenge(user); } + Future loadWorlds() async { + Map worlds = {}; + final defaultFile = File('$worldDirectory/$defaultWorldName$worldSuffix'); + if (await defaultFile.exists()) { + try { + final bytes = await defaultFile.readAsBytes(); + worlds[defaultWorldName] = SetonixData.fromData(bytes); + } catch (e) { + log( + 'Error loading default world from ${defaultFile.path}: $e', + level: LogLevel.warning, + ); + } + } else { + worlds[defaultWorldName] = _buildDefaultWorld(); + log('No default world found, creating new one', level: LogLevel.info); + } + final dir = Directory(worldDirectory); + if (await dir.exists()) { + await for (final file in dir.list(recursive: false)) { + if (file is! File || !file.path.endsWith(worldSuffix)) continue; + final name = file.path.substring( + dir.path.length + 1, + file.path.length - worldSuffix.length, + ); + if (name == defaultWorldName) continue; + try { + final bytes = await file.readAsBytes(); + worlds[name] = SetonixData.fromData(bytes); + } catch (e) { + log( + 'Error loading world $name from ${file.path}: $e', + level: LogLevel.warning, + ); + } + } + } + for (final entry in worlds.entries) { + final name = entry.key; + final data = entry.value; + final bloc = WorldBloc(data, this, name); + _worlds[name] = bloc; + await bloc.init(); + log('Loaded world $name', level: LogLevel.info); + } + } + Future saveAll({bool force = false}) async { await Future.wait(_worlds.values.map((e) => e.save(force: force))); } diff --git a/tools/build_server.dart b/tools/build_server.dart deleted file mode 100644 index 8b137891..00000000 --- a/tools/build_server.dart +++ /dev/null @@ -1 +0,0 @@ - From 1333bd16ae283651ebaa37ad81ea975efcf9c3b9 Mon Sep 17 00:00:00 2001 From: CodeDoctorDE Date: Sat, 27 Sep 2025 13:14:11 +0200 Subject: [PATCH 07/10] Add waypoints --- api/lib/models.dart | 1 + api/lib/src/event/event.mapper.dart | 300 ++++++++++++++++++++++++ api/lib/src/event/hybrid.dart | 19 ++ api/lib/src/event/process/server.dart | 60 +++++ api/lib/src/models/info.dart | 17 +- api/lib/src/models/info.mapper.dart | 72 +++++- api/lib/src/models/table.dart | 7 - api/lib/src/models/table.mapper.dart | 110 --------- api/lib/src/models/waypoint.dart | 13 + api/lib/src/models/waypoint.mapper.dart | 143 +++++++++++ app/lib/l10n/app_en.arb | 7 +- app/lib/pages/game/drawer.dart | 53 +++++ app/lib/pages/game/waypoint.dart | 75 ++++++ 13 files changed, 744 insertions(+), 133 deletions(-) create mode 100644 api/lib/src/models/waypoint.dart create mode 100644 api/lib/src/models/waypoint.mapper.dart create mode 100644 app/lib/pages/game/waypoint.dart diff --git a/api/lib/models.dart b/api/lib/models.dart index d2510fda..409b27ba 100644 --- a/api/lib/models.dart +++ b/api/lib/models.dart @@ -14,3 +14,4 @@ export 'src/models/table.dart'; export 'src/models/translation.dart'; export 'src/models/vector.dart'; export 'src/models/visual.dart'; +export 'src/models/waypoint.dart'; diff --git a/api/lib/src/event/event.mapper.dart b/api/lib/src/event/event.mapper.dart index 25448de3..b37aeca1 100644 --- a/api/lib/src/event/event.mapper.dart +++ b/api/lib/src/event/event.mapper.dart @@ -4211,6 +4211,8 @@ class HybridWorldEventMapper extends SubClassMapperBase { TableRemovedMapper.ensureInitialized(); NoteChangedMapper.ensureInitialized(); NoteRemovedMapper.ensureInitialized(); + WaypointChangedMapper.ensureInitialized(); + WaypointRemovedMapper.ensureInitialized(); } return _instance!; } @@ -6163,6 +6165,304 @@ class _NoteRemovedCopyWithImpl<$R, $Out> ) => _NoteRemovedCopyWithImpl<$R2, $Out2>($value, $cast, t); } +class WaypointChangedMapper extends SubClassMapperBase { + WaypointChangedMapper._(); + + static WaypointChangedMapper? _instance; + static WaypointChangedMapper ensureInitialized() { + if (_instance == null) { + MapperContainer.globals.use(_instance = WaypointChangedMapper._()); + HybridWorldEventMapper.ensureInitialized().addSubMapper(_instance!); + WaypointMapper.ensureInitialized(); + } + return _instance!; + } + + @override + final String id = 'WaypointChanged'; + + static String? _$team(WaypointChanged v) => v.team; + static const Field _f$team = Field( + 'team', + _$team, + opt: true, + ); + static Waypoint _$waypoint(WaypointChanged v) => v.waypoint; + static const Field _f$waypoint = Field( + 'waypoint', + _$waypoint, + ); + static String? _$name(WaypointChanged v) => v.name; + static const Field _f$name = Field( + 'name', + _$name, + opt: true, + ); + + @override + final MappableFields fields = const { + #team: _f$team, + #waypoint: _f$waypoint, + #name: _f$name, + }; + + @override + final String discriminatorKey = 'type'; + @override + final dynamic discriminatorValue = 'WaypointChanged'; + @override + late final ClassMapperBase superMapper = + HybridWorldEventMapper.ensureInitialized(); + + static WaypointChanged _instantiate(DecodingData data) { + return WaypointChanged( + team: data.dec(_f$team), + waypoint: data.dec(_f$waypoint), + name: data.dec(_f$name), + ); + } + + @override + final Function instantiate = _instantiate; + + static WaypointChanged fromMap(Map map) { + return ensureInitialized().decodeMap(map); + } + + static WaypointChanged fromJson(String json) { + return ensureInitialized().decodeJson(json); + } +} + +mixin WaypointChangedMappable { + String toJson() { + return WaypointChangedMapper.ensureInitialized() + .encodeJson(this as WaypointChanged); + } + + Map toMap() { + return WaypointChangedMapper.ensureInitialized().encodeMap( + this as WaypointChanged, + ); + } + + WaypointChangedCopyWith + get copyWith => + _WaypointChangedCopyWithImpl( + this as WaypointChanged, + $identity, + $identity, + ); + @override + String toString() { + return WaypointChangedMapper.ensureInitialized().stringifyValue( + this as WaypointChanged, + ); + } + + @override + bool operator ==(Object other) { + return WaypointChangedMapper.ensureInitialized().equalsValue( + this as WaypointChanged, + other, + ); + } + + @override + int get hashCode { + return WaypointChangedMapper.ensureInitialized().hashValue( + this as WaypointChanged, + ); + } +} + +extension WaypointChangedValueCopy<$R, $Out> + on ObjectCopyWith<$R, WaypointChanged, $Out> { + WaypointChangedCopyWith<$R, WaypointChanged, $Out> get $asWaypointChanged => + $base.as((v, t, t2) => _WaypointChangedCopyWithImpl<$R, $Out>(v, t, t2)); +} + +abstract class WaypointChangedCopyWith<$R, $In extends WaypointChanged, $Out> + implements HybridWorldEventCopyWith<$R, $In, $Out> { + WaypointCopyWith<$R, Waypoint, Waypoint> get waypoint; + @override + $R call({String? team, Waypoint? waypoint, String? name}); + WaypointChangedCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t, + ); +} + +class _WaypointChangedCopyWithImpl<$R, $Out> + extends ClassCopyWithBase<$R, WaypointChanged, $Out> + implements WaypointChangedCopyWith<$R, WaypointChanged, $Out> { + _WaypointChangedCopyWithImpl(super.value, super.then, super.then2); + + @override + late final ClassMapperBase $mapper = + WaypointChangedMapper.ensureInitialized(); + @override + WaypointCopyWith<$R, Waypoint, Waypoint> get waypoint => + $value.waypoint.copyWith.$chain((v) => call(waypoint: v)); + @override + $R call({Object? team = $none, Waypoint? waypoint, Object? name = $none}) => + $apply( + FieldCopyWithData({ + if (team != $none) #team: team, + if (waypoint != null) #waypoint: waypoint, + if (name != $none) #name: name, + }), + ); + @override + WaypointChanged $make(CopyWithData data) => WaypointChanged( + team: data.get(#team, or: $value.team), + waypoint: data.get(#waypoint, or: $value.waypoint), + name: data.get(#name, or: $value.name), + ); + + @override + WaypointChangedCopyWith<$R2, WaypointChanged, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t, + ) => _WaypointChangedCopyWithImpl<$R2, $Out2>($value, $cast, t); +} + +class WaypointRemovedMapper extends SubClassMapperBase { + WaypointRemovedMapper._(); + + static WaypointRemovedMapper? _instance; + static WaypointRemovedMapper ensureInitialized() { + if (_instance == null) { + MapperContainer.globals.use(_instance = WaypointRemovedMapper._()); + HybridWorldEventMapper.ensureInitialized().addSubMapper(_instance!); + } + return _instance!; + } + + @override + final String id = 'WaypointRemoved'; + + static String? _$team(WaypointRemoved v) => v.team; + static const Field _f$team = Field( + 'team', + _$team, + opt: true, + ); + static String _$name(WaypointRemoved v) => v.name; + static const Field _f$name = Field('name', _$name); + + @override + final MappableFields fields = const { + #team: _f$team, + #name: _f$name, + }; + + @override + final String discriminatorKey = 'type'; + @override + final dynamic discriminatorValue = 'WaypointRemoved'; + @override + late final ClassMapperBase superMapper = + HybridWorldEventMapper.ensureInitialized(); + + static WaypointRemoved _instantiate(DecodingData data) { + return WaypointRemoved(team: data.dec(_f$team), name: data.dec(_f$name)); + } + + @override + final Function instantiate = _instantiate; + + static WaypointRemoved fromMap(Map map) { + return ensureInitialized().decodeMap(map); + } + + static WaypointRemoved fromJson(String json) { + return ensureInitialized().decodeJson(json); + } +} + +mixin WaypointRemovedMappable { + String toJson() { + return WaypointRemovedMapper.ensureInitialized() + .encodeJson(this as WaypointRemoved); + } + + Map toMap() { + return WaypointRemovedMapper.ensureInitialized().encodeMap( + this as WaypointRemoved, + ); + } + + WaypointRemovedCopyWith + get copyWith => + _WaypointRemovedCopyWithImpl( + this as WaypointRemoved, + $identity, + $identity, + ); + @override + String toString() { + return WaypointRemovedMapper.ensureInitialized().stringifyValue( + this as WaypointRemoved, + ); + } + + @override + bool operator ==(Object other) { + return WaypointRemovedMapper.ensureInitialized().equalsValue( + this as WaypointRemoved, + other, + ); + } + + @override + int get hashCode { + return WaypointRemovedMapper.ensureInitialized().hashValue( + this as WaypointRemoved, + ); + } +} + +extension WaypointRemovedValueCopy<$R, $Out> + on ObjectCopyWith<$R, WaypointRemoved, $Out> { + WaypointRemovedCopyWith<$R, WaypointRemoved, $Out> get $asWaypointRemoved => + $base.as((v, t, t2) => _WaypointRemovedCopyWithImpl<$R, $Out>(v, t, t2)); +} + +abstract class WaypointRemovedCopyWith<$R, $In extends WaypointRemoved, $Out> + implements HybridWorldEventCopyWith<$R, $In, $Out> { + @override + $R call({String? team, String? name}); + WaypointRemovedCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t, + ); +} + +class _WaypointRemovedCopyWithImpl<$R, $Out> + extends ClassCopyWithBase<$R, WaypointRemoved, $Out> + implements WaypointRemovedCopyWith<$R, WaypointRemoved, $Out> { + _WaypointRemovedCopyWithImpl(super.value, super.then, super.then2); + + @override + late final ClassMapperBase $mapper = + WaypointRemovedMapper.ensureInitialized(); + @override + $R call({Object? team = $none, String? name}) => $apply( + FieldCopyWithData({ + if (team != $none) #team: team, + if (name != null) #name: name, + }), + ); + @override + WaypointRemoved $make(CopyWithData data) => WaypointRemoved( + team: data.get(#team, or: $value.team), + name: data.get(#name, or: $value.name), + ); + + @override + WaypointRemovedCopyWith<$R2, WaypointRemoved, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t, + ) => _WaypointRemovedCopyWithImpl<$R2, $Out2>($value, $cast, t); +} + class LocalWorldEventMapper extends SubClassMapperBase { LocalWorldEventMapper._(); diff --git a/api/lib/src/event/hybrid.dart b/api/lib/src/event/hybrid.dart index 883c7948..fc197e57 100644 --- a/api/lib/src/event/hybrid.dart +++ b/api/lib/src/event/hybrid.dart @@ -224,3 +224,22 @@ final class NoteRemoved extends HybridWorldEvent with NoteRemovedMappable { NoteRemoved(this.name); } + +@MappableClass() +final class WaypointChanged extends HybridWorldEvent + with WaypointChangedMappable { + final String? team; + final Waypoint waypoint; + final String? name; + + WaypointChanged({this.team, required this.waypoint, this.name}); +} + +@MappableClass() +final class WaypointRemoved extends HybridWorldEvent + with WaypointRemovedMappable { + final String? team; + final String name; + + WaypointRemoved({this.team, required this.name}); +} diff --git a/api/lib/src/event/process/server.dart b/api/lib/src/event/process/server.dart index d6bc8ab8..c0d058e4 100644 --- a/api/lib/src/event/process/server.dart +++ b/api/lib/src/event/process/server.dart @@ -346,6 +346,21 @@ ServerProcessed processServerEvent( state.copyWith( tableName: state.tableName == event.name ? '' : state.tableName, data: state.data.removeTable(event.name), + info: state.info.copyWith( + teams: state.info.teams.map( + (k, v) => MapEntry( + k, + v.copyWith( + claimedCells: v.claimedCells + .where((e) => e.table != event.name) + .toSet(), + waypoints: v.waypoints + .where((e) => e.position.table != event.name) + .toList(), + ), + ), + ), + ), ), ); case NoteChanged(): @@ -418,5 +433,50 @@ ServerProcessed processServerEvent( return ServerProcessed(state.copyWith(serverState: event.state)); case AuthenticatedRequested(): return ServerProcessed(state.copyWith(authRequest: event)); + case WaypointChanged(): + var info = state.info; + final team = event.team; + final waypoints = List.from( + team == null ? info.waypoints : (info.teams[team]?.waypoints ?? []), + ); + final index = waypoints.indexWhere( + (e) => e.name == (event.name ?? event.waypoint.name), + ); + if (index != -1) { + waypoints[index] = event.waypoint; + } else { + waypoints.add(event.waypoint); + } + if (team == null) { + info = info.copyWith(waypoints: waypoints); + } else { + final gameTeam = info.teams[event.team]; + if (gameTeam != null) { + info = info.copyWith.teams.put( + team, + gameTeam.copyWith(waypoints: waypoints), + ); + } + } + return ServerProcessed(state.copyWith(info: info)); + case WaypointRemoved(): + var info = state.info; + final team = event.team; + final waypoints = team == null + ? List.from(info.waypoints) + : List.from(info.teams[team]?.waypoints ?? []); + waypoints.removeWhere((e) => e.name == event.name); + if (team == null) { + info = info.copyWith(waypoints: waypoints); + } else { + final gameTeam = info.teams[event.team]; + if (gameTeam != null) { + info = info.copyWith.teams.put( + team, + gameTeam.copyWith(waypoints: waypoints), + ); + } + } + return ServerProcessed(state.copyWith(info: info)); } } diff --git a/api/lib/src/models/info.dart b/api/lib/src/models/info.dart index 6c31566c..da318e67 100644 --- a/api/lib/src/models/info.dart +++ b/api/lib/src/models/info.dart @@ -1,6 +1,7 @@ import 'package:dart_mappable/dart_mappable.dart'; import 'table.dart'; +import 'waypoint.dart'; part 'info.mapper.dart'; @@ -9,8 +10,14 @@ class GameInfo with GameInfoMappable { final Map teams; final List packs; final ItemLocation? gameMode; + final List waypoints; - const GameInfo({this.teams = const {}, this.packs = const [], this.gameMode}); + const GameInfo({ + this.teams = const {}, + this.packs = const [], + this.gameMode, + this.waypoints = const [], + }); } @MappableEnum() @@ -33,6 +40,12 @@ class GameTeam with GameTeamMappable { final String description; final TeamColor? color; final Set claimedCells; + final List waypoints; - GameTeam({this.description = '', this.color, this.claimedCells = const {}}); + GameTeam({ + this.description = '', + this.color, + this.claimedCells = const {}, + this.waypoints = const [], + }); } diff --git a/api/lib/src/models/info.mapper.dart b/api/lib/src/models/info.mapper.dart index 7fc717fc..12f56c75 100644 --- a/api/lib/src/models/info.mapper.dart +++ b/api/lib/src/models/info.mapper.dart @@ -98,6 +98,7 @@ class GameInfoMapper extends ClassMapperBase { MapperContainer.globals.use(_instance = GameInfoMapper._()); GameTeamMapper.ensureInitialized(); ItemLocationMapper.ensureInitialized(); + WaypointMapper.ensureInitialized(); } return _instance!; } @@ -119,25 +120,34 @@ class GameInfoMapper extends ClassMapperBase { opt: true, def: const [], ); - static ItemLocation? _$script(GameInfo v) => v.gameMode; - static const Field _f$script = Field( - 'script', - _$script, + static ItemLocation? _$gameMode(GameInfo v) => v.gameMode; + static const Field _f$gameMode = Field( + 'gameMode', + _$gameMode, opt: true, ); + static List _$waypoints(GameInfo v) => v.waypoints; + static const Field> _f$waypoints = Field( + 'waypoints', + _$waypoints, + opt: true, + def: const [], + ); @override final MappableFields fields = const { #teams: _f$teams, #packs: _f$packs, - #script: _f$script, + #gameMode: _f$gameMode, + #waypoints: _f$waypoints, }; static GameInfo _instantiate(DecodingData data) { return GameInfo( teams: data.dec(_f$teams), packs: data.dec(_f$packs), - gameMode: data.dec(_f$script), + gameMode: data.dec(_f$gameMode), + waypoints: data.dec(_f$waypoints), ); } @@ -201,11 +211,14 @@ 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; - ItemLocationCopyWith<$R, ItemLocation, ItemLocation>? get script; + ItemLocationCopyWith<$R, ItemLocation, ItemLocation>? get gameMode; + ListCopyWith<$R, Waypoint, WaypointCopyWith<$R, Waypoint, Waypoint>> + get waypoints; $R call({ Map? teams, List? packs, - ItemLocation? script, + ItemLocation? gameMode, + List? waypoints, }); GameInfoCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); } @@ -233,25 +246,35 @@ class _GameInfoCopyWithImpl<$R, $Out> (v) => call(packs: v), ); @override - ItemLocationCopyWith<$R, ItemLocation, ItemLocation>? get script => - $value.gameMode?.copyWith.$chain((v) => call(script: v)); + ItemLocationCopyWith<$R, ItemLocation, ItemLocation>? get gameMode => + $value.gameMode?.copyWith.$chain((v) => call(gameMode: v)); + @override + ListCopyWith<$R, Waypoint, WaypointCopyWith<$R, Waypoint, Waypoint>> + get waypoints => ListCopyWith( + $value.waypoints, + (v, t) => v.copyWith.$chain(t), + (v) => call(waypoints: v), + ); @override $R call({ Map? teams, List? packs, - Object? script = $none, + Object? gameMode = $none, + List? waypoints, }) => $apply( FieldCopyWithData({ if (teams != null) #teams: teams, if (packs != null) #packs: packs, - if (script != $none) #script: script, + if (gameMode != $none) #gameMode: gameMode, + if (waypoints != null) #waypoints: waypoints, }), ); @override GameInfo $make(CopyWithData data) => GameInfo( teams: data.get(#teams, or: $value.teams), packs: data.get(#packs, or: $value.packs), - gameMode: data.get(#script, or: $value.gameMode), + gameMode: data.get(#gameMode, or: $value.gameMode), + waypoints: data.get(#waypoints, or: $value.waypoints), ); @override @@ -269,6 +292,7 @@ class GameTeamMapper extends ClassMapperBase { MapperContainer.globals.use(_instance = GameTeamMapper._()); TeamColorMapper.ensureInitialized(); GlobalVectorDefinitionMapper.ensureInitialized(); + WaypointMapper.ensureInitialized(); } return _instance!; } @@ -293,12 +317,20 @@ class GameTeamMapper extends ClassMapperBase { v.claimedCells; static const Field> _f$claimedCells = Field('claimedCells', _$claimedCells, opt: true, def: const {}); + static List _$waypoints(GameTeam v) => v.waypoints; + static const Field> _f$waypoints = Field( + 'waypoints', + _$waypoints, + opt: true, + def: const [], + ); @override final MappableFields fields = const { #description: _f$description, #color: _f$color, #claimedCells: _f$claimedCells, + #waypoints: _f$waypoints, }; static GameTeam _instantiate(DecodingData data) { @@ -306,6 +338,7 @@ class GameTeamMapper extends ClassMapperBase { description: data.dec(_f$description), color: data.dec(_f$color), claimedCells: data.dec(_f$claimedCells), + waypoints: data.dec(_f$waypoints), ); } @@ -366,10 +399,13 @@ extension GameTeamValueCopy<$R, $Out> on ObjectCopyWith<$R, GameTeam, $Out> { abstract class GameTeamCopyWith<$R, $In extends GameTeam, $Out> implements ClassCopyWith<$R, $In, $Out> { + ListCopyWith<$R, Waypoint, WaypointCopyWith<$R, Waypoint, Waypoint>> + get waypoints; $R call({ String? description, TeamColor? color, Set? claimedCells, + List? waypoints, }); GameTeamCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); } @@ -383,15 +419,24 @@ class _GameTeamCopyWithImpl<$R, $Out> late final ClassMapperBase $mapper = GameTeamMapper.ensureInitialized(); @override + ListCopyWith<$R, Waypoint, WaypointCopyWith<$R, Waypoint, Waypoint>> + get waypoints => ListCopyWith( + $value.waypoints, + (v, t) => v.copyWith.$chain(t), + (v) => call(waypoints: v), + ); + @override $R call({ String? description, Object? color = $none, Set? claimedCells, + List? waypoints, }) => $apply( FieldCopyWithData({ if (description != null) #description: description, if (color != $none) #color: color, if (claimedCells != null) #claimedCells: claimedCells, + if (waypoints != null) #waypoints: waypoints, }), ); @override @@ -399,6 +444,7 @@ class _GameTeamCopyWithImpl<$R, $Out> description: data.get(#description, or: $value.description), color: data.get(#color, or: $value.color), claimedCells: data.get(#claimedCells, or: $value.claimedCells), + waypoints: data.get(#waypoints, or: $value.waypoints), ); @override diff --git a/api/lib/src/models/table.dart b/api/lib/src/models/table.dart index 379dfa11..827ca9b2 100644 --- a/api/lib/src/models/table.dart +++ b/api/lib/src/models/table.dart @@ -82,13 +82,6 @@ class GlobalVectorDefinition with GlobalVectorDefinitionMappable { int get y => position.y; } -@MappableClass() -class GameSeat with GameSeatMappable { - final int? color; - - GameSeat({this.color}); -} - @MappableClass() class TableCell with TableCellMappable { final List objects; diff --git a/api/lib/src/models/table.mapper.dart b/api/lib/src/models/table.mapper.dart index 4e093df1..e296f5bb 100644 --- a/api/lib/src/models/table.mapper.dart +++ b/api/lib/src/models/table.mapper.dart @@ -1012,113 +1012,3 @@ class _GlobalVectorDefinitionCopyWithImpl<$R, $Out> _GlobalVectorDefinitionCopyWithImpl<$R2, $Out2>($value, $cast, t); } -class GameSeatMapper extends ClassMapperBase { - GameSeatMapper._(); - - static GameSeatMapper? _instance; - static GameSeatMapper ensureInitialized() { - if (_instance == null) { - MapperContainer.globals.use(_instance = GameSeatMapper._()); - } - return _instance!; - } - - @override - final String id = 'GameSeat'; - - static int? _$color(GameSeat v) => v.color; - static const Field _f$color = Field( - 'color', - _$color, - opt: true, - ); - - @override - final MappableFields fields = const {#color: _f$color}; - - static GameSeat _instantiate(DecodingData data) { - return GameSeat(color: data.dec(_f$color)); - } - - @override - final Function instantiate = _instantiate; - - static GameSeat fromMap(Map map) { - return ensureInitialized().decodeMap(map); - } - - static GameSeat fromJson(String json) { - return ensureInitialized().decodeJson(json); - } -} - -mixin GameSeatMappable { - String toJson() { - return GameSeatMapper.ensureInitialized().encodeJson( - this as GameSeat, - ); - } - - Map toMap() { - return GameSeatMapper.ensureInitialized().encodeMap( - this as GameSeat, - ); - } - - GameSeatCopyWith get copyWith => - _GameSeatCopyWithImpl( - this as GameSeat, - $identity, - $identity, - ); - @override - String toString() { - return GameSeatMapper.ensureInitialized().stringifyValue(this as GameSeat); - } - - @override - bool operator ==(Object other) { - return GameSeatMapper.ensureInitialized().equalsValue( - this as GameSeat, - other, - ); - } - - @override - int get hashCode { - return GameSeatMapper.ensureInitialized().hashValue(this as GameSeat); - } -} - -extension GameSeatValueCopy<$R, $Out> on ObjectCopyWith<$R, GameSeat, $Out> { - GameSeatCopyWith<$R, GameSeat, $Out> get $asGameSeat => - $base.as((v, t, t2) => _GameSeatCopyWithImpl<$R, $Out>(v, t, t2)); -} - -abstract class GameSeatCopyWith<$R, $In extends GameSeat, $Out> - implements ClassCopyWith<$R, $In, $Out> { - $R call({int? color}); - GameSeatCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); -} - -class _GameSeatCopyWithImpl<$R, $Out> - extends ClassCopyWithBase<$R, GameSeat, $Out> - implements GameSeatCopyWith<$R, GameSeat, $Out> { - _GameSeatCopyWithImpl(super.value, super.then, super.then2); - - @override - late final ClassMapperBase $mapper = - GameSeatMapper.ensureInitialized(); - @override - $R call({Object? color = $none}) => - $apply(FieldCopyWithData({if (color != $none) #color: color})); - @override - GameSeat $make(CopyWithData data) => - GameSeat(color: data.get(#color, or: $value.color)); - - @override - GameSeatCopyWith<$R2, GameSeat, $Out2> $chain<$R2, $Out2>( - Then<$Out2, $R2> t, - ) => _GameSeatCopyWithImpl<$R2, $Out2>($value, $cast, t); -} - diff --git a/api/lib/src/models/waypoint.dart b/api/lib/src/models/waypoint.dart new file mode 100644 index 00000000..b19920e8 --- /dev/null +++ b/api/lib/src/models/waypoint.dart @@ -0,0 +1,13 @@ +import 'package:dart_mappable/dart_mappable.dart'; + +import 'table.dart'; + +part 'waypoint.mapper.dart'; + +@MappableClass() +final class Waypoint with WaypointMappable { + final String name; + final GlobalVectorDefinition position; + + Waypoint({required this.name, required this.position}); +} diff --git a/api/lib/src/models/waypoint.mapper.dart b/api/lib/src/models/waypoint.mapper.dart new file mode 100644 index 00000000..4de1b350 --- /dev/null +++ b/api/lib/src/models/waypoint.mapper.dart @@ -0,0 +1,143 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// dart format off +// ignore_for_file: type=lint +// ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member +// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter + +part of 'waypoint.dart'; + +class WaypointMapper extends ClassMapperBase { + WaypointMapper._(); + + static WaypointMapper? _instance; + static WaypointMapper ensureInitialized() { + if (_instance == null) { + MapperContainer.globals.use(_instance = WaypointMapper._()); + GlobalVectorDefinitionMapper.ensureInitialized(); + } + return _instance!; + } + + @override + final String id = 'Waypoint'; + + static String _$name(Waypoint v) => v.name; + static const Field _f$name = Field('name', _$name); + static GlobalVectorDefinition _$position(Waypoint v) => v.position; + static const Field _f$position = Field( + 'position', + _$position, + ); + + @override + final MappableFields fields = const { + #name: _f$name, + #position: _f$position, + }; + + static Waypoint _instantiate(DecodingData data) { + return Waypoint(name: data.dec(_f$name), position: data.dec(_f$position)); + } + + @override + final Function instantiate = _instantiate; + + static Waypoint fromMap(Map map) { + return ensureInitialized().decodeMap(map); + } + + static Waypoint fromJson(String json) { + return ensureInitialized().decodeJson(json); + } +} + +mixin WaypointMappable { + String toJson() { + return WaypointMapper.ensureInitialized().encodeJson( + this as Waypoint, + ); + } + + Map toMap() { + return WaypointMapper.ensureInitialized().encodeMap( + this as Waypoint, + ); + } + + WaypointCopyWith get copyWith => + _WaypointCopyWithImpl( + this as Waypoint, + $identity, + $identity, + ); + @override + String toString() { + return WaypointMapper.ensureInitialized().stringifyValue(this as Waypoint); + } + + @override + bool operator ==(Object other) { + return WaypointMapper.ensureInitialized().equalsValue( + this as Waypoint, + other, + ); + } + + @override + int get hashCode { + return WaypointMapper.ensureInitialized().hashValue(this as Waypoint); + } +} + +extension WaypointValueCopy<$R, $Out> on ObjectCopyWith<$R, Waypoint, $Out> { + WaypointCopyWith<$R, Waypoint, $Out> get $asWaypoint => + $base.as((v, t, t2) => _WaypointCopyWithImpl<$R, $Out>(v, t, t2)); +} + +abstract class WaypointCopyWith<$R, $In extends Waypoint, $Out> + implements ClassCopyWith<$R, $In, $Out> { + GlobalVectorDefinitionCopyWith< + $R, + GlobalVectorDefinition, + GlobalVectorDefinition + > + get position; + $R call({String? name, GlobalVectorDefinition? position}); + WaypointCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); +} + +class _WaypointCopyWithImpl<$R, $Out> + extends ClassCopyWithBase<$R, Waypoint, $Out> + implements WaypointCopyWith<$R, Waypoint, $Out> { + _WaypointCopyWithImpl(super.value, super.then, super.then2); + + @override + late final ClassMapperBase $mapper = + WaypointMapper.ensureInitialized(); + @override + GlobalVectorDefinitionCopyWith< + $R, + GlobalVectorDefinition, + GlobalVectorDefinition + > + get position => $value.position.copyWith.$chain((v) => call(position: v)); + @override + $R call({String? name, GlobalVectorDefinition? position}) => $apply( + FieldCopyWithData({ + if (name != null) #name: name, + if (position != null) #position: position, + }), + ); + @override + Waypoint $make(CopyWithData data) => Waypoint( + name: data.get(#name, or: $value.name), + position: data.get(#position, or: $value.position), + ); + + @override + WaypointCopyWith<$R2, Waypoint, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t, + ) => _WaypointCopyWithImpl<$R2, $Out2>($value, $cast, t); +} + diff --git a/app/lib/l10n/app_en.arb b/app/lib/l10n/app_en.arb index 1158a033..259efb45 100644 --- a/app/lib/l10n/app_en.arb +++ b/app/lib/l10n/app_en.arb @@ -279,5 +279,10 @@ "pleaseLink": "Please link your account to the server using the provided link.", "singleplayer": "Singleplayer", "recentGames": "Recent games", - "noRecentGames": "There are no recent games available" + "noRecentGames": "There are no recent games available", + "copy": "Copy", + "waypoints": "Waypoints", + "addWaypoint": "Add waypoint", + "editWaypoint": "Edit waypoint", + "team": "Team" } diff --git a/app/lib/pages/game/drawer.dart b/app/lib/pages/game/drawer.dart index 4a38c846..327b5c65 100644 --- a/app/lib/pages/game/drawer.dart +++ b/app/lib/pages/game/drawer.dart @@ -181,6 +181,59 @@ class GameDrawer extends StatelessWidget { ); }, ), + AdvancedSwitchListTile( + title: Text(AppLocalizations.of(context).waypoints), + leading: const Icon(PhosphorIconsLight.mapPin), + value: false, + onChanged: (value) {}, + onTap: () { + final bloc = context.read(); + final state = bloc.state; + Widget buildWaypointTile(Waypoint waypoint, {String? team}) => + ContextRegion( + builder: (context, button, controller) => ListTile( + title: Text(waypoint.name), + leading: Icon( + team != null + ? PhosphorIconsLight.users + : PhosphorIconsLight.mapPin, + ), + trailing: button, + ), + menuChildren: [ + MenuItemButton( + leadingIcon: const Icon(PhosphorIconsLight.pencil), + child: Text(AppLocalizations.of(context).edit), + onPressed: () {}, + ), + MenuItemButton( + leadingIcon: const Icon(PhosphorIconsLight.trash), + child: Text(AppLocalizations.of(context).delete), + onPressed: () { + bloc.add( + WaypointRemoved(name: waypoint.name, team: team), + ); + }, + ), + ], + ); + showLeapBottomSheet( + context: context, + titleBuilder: (context) => + Text(AppLocalizations.of(context).waypoints), + childrenBuilder: (context) => [ + ...state.world.getTeams().expand( + (e) => + state.info.teams[e]?.waypoints.map( + (waypoint) => buildWaypointTile(waypoint, team: e), + ) ?? + [], + ), + ...state.info.waypoints.map((e) => buildWaypointTile(e)), + ], + ); + }, + ), BlocBuilder( buildWhen: (previous, current) => previous.zoom != current.zoom, builder: (context, state) => ListTile( diff --git a/app/lib/pages/game/waypoint.dart b/app/lib/pages/game/waypoint.dart new file mode 100644 index 00000000..e8b36f8c --- /dev/null +++ b/app/lib/pages/game/waypoint.dart @@ -0,0 +1,75 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:material_leap/material_leap.dart'; +import 'package:setonix/bloc/world/bloc.dart'; +import 'package:setonix/src/generated/i18n/app_localizations.dart'; +import 'package:setonix_api/setonix_api.dart'; + +class WaypointDialog extends StatelessWidget { + final String? team; + final Waypoint? waypoint; + final GlobalVectorDefinition? position; + + const WaypointDialog({super.key, this.team, this.waypoint, this.position}); + + @override + Widget build(BuildContext context) { + final isCreated = position != null; + final loc = AppLocalizations.of(context); + var waypoint = + this.waypoint ?? + Waypoint( + name: '', + position: position ?? GlobalVectorDefinition('', 0, 0), + ); + var team = this.team; + final bloc = context.read(); + return ResponsiveAlertDialog( + title: Text(isCreated ? loc.addWaypoint : loc.editWaypoint), + content: ListView( + shrinkWrap: true, + children: [ + TextFormField( + decoration: InputDecoration(labelText: loc.name), + initialValue: waypoint.name, + onChanged: (value) { + waypoint = waypoint.copyWith(name: value); + }, + ), + const SizedBox(height: 8), + DropdownMenu( + dropdownMenuEntries: [ + DropdownMenuEntry(value: null, label: loc.name), + ...bloc.state.world.getTeams().map( + (e) => DropdownMenuEntry(value: e, label: e), + ), + ], + label: Text(loc.team), + initialSelection: team, + onSelected: (value) { + team = value; + }, + ), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: Text(loc.cancel), + ), + ElevatedButton( + onPressed: () { + bloc.add( + WaypointChanged( + waypoint: waypoint, + team: team, + name: this.waypoint?.name, + ), + ); + }, + child: Text(isCreated ? loc.add : loc.save), + ), + ], + ); + } +} From f692214e89e321c2076292d3e14c37f9bc4e6e3d Mon Sep 17 00:00:00 2001 From: CodeDoctorDE Date: Sun, 28 Sep 2025 20:18:32 +0200 Subject: [PATCH 08/10] Add cell teleport and server cell switch --- api/lib/src/event/event.mapper.dart | 160 ++++++++++++++++++++++++++ api/lib/src/event/hybrid.dart | 8 ++ api/lib/src/event/process/server.dart | 2 + api/lib/src/event/server.dart | 2 +- api/lib/src/models/table.mapper.dart | 7 -- app/lib/bloc/world/bloc.dart | 20 ++-- app/lib/bloc/world/local.dart | 8 -- app/lib/bloc/world/local.mapper.dart | 145 ----------------------- app/lib/board/cell.dart | 18 ++- app/lib/board/game.dart | 13 +++ app/lib/board/hand/figure.dart | 1 - app/lib/board/hand/object.dart | 1 - app/lib/l10n/app_en.arb | 3 +- app/lib/pages/game/drawer.dart | 12 +- app/lib/pages/game/page.dart | 16 +-- app/lib/pages/game/waypoint.dart | 7 +- 16 files changed, 235 insertions(+), 188 deletions(-) diff --git a/api/lib/src/event/event.mapper.dart b/api/lib/src/event/event.mapper.dart index b37aeca1..dc48847d 100644 --- a/api/lib/src/event/event.mapper.dart +++ b/api/lib/src/event/event.mapper.dart @@ -4198,6 +4198,7 @@ class HybridWorldEventMapper extends SubClassMapperBase { if (_instance == null) { MapperContainer.globals.use(_instance = HybridWorldEventMapper._()); WorldEventMapper.ensureInitialized().addSubMapper(_instance!); + CellSwitchedMapper.ensureInitialized(); BackgroundChangedMapper.ensureInitialized(); ObjectsSpawnedMapper.ensureInitialized(); ObjectsMovedMapper.ensureInitialized(); @@ -4269,6 +4270,165 @@ abstract class HybridWorldEventCopyWith<$R, $In extends HybridWorldEvent, $Out> ); } +class CellSwitchedMapper extends SubClassMapperBase { + CellSwitchedMapper._(); + + static CellSwitchedMapper? _instance; + static CellSwitchedMapper ensureInitialized() { + if (_instance == null) { + MapperContainer.globals.use(_instance = CellSwitchedMapper._()); + HybridWorldEventMapper.ensureInitialized().addSubMapper(_instance!); + VectorDefinitionMapper.ensureInitialized(); + } + return _instance!; + } + + @override + final String id = 'CellSwitched'; + + static VectorDefinition? _$cell(CellSwitched v) => v.cell; + static const Field _f$cell = Field( + 'cell', + _$cell, + ); + static bool _$selected(CellSwitched v) => v.selected; + static const Field _f$selected = Field( + 'selected', + _$selected, + opt: true, + def: true, + ); + static bool _$teleport(CellSwitched v) => v.teleport; + static const Field _f$teleport = Field( + 'teleport', + _$teleport, + opt: true, + def: false, + ); + + @override + final MappableFields fields = const { + #cell: _f$cell, + #selected: _f$selected, + #teleport: _f$teleport, + }; + + @override + final String discriminatorKey = 'type'; + @override + final dynamic discriminatorValue = 'CellSwitched'; + @override + late final ClassMapperBase superMapper = + HybridWorldEventMapper.ensureInitialized(); + + static CellSwitched _instantiate(DecodingData data) { + return CellSwitched( + data.dec(_f$cell), + selected: data.dec(_f$selected), + teleport: data.dec(_f$teleport), + ); + } + + @override + final Function instantiate = _instantiate; + + static CellSwitched fromMap(Map map) { + return ensureInitialized().decodeMap(map); + } + + static CellSwitched fromJson(String json) { + return ensureInitialized().decodeJson(json); + } +} + +mixin CellSwitchedMappable { + String toJson() { + return CellSwitchedMapper.ensureInitialized().encodeJson( + this as CellSwitched, + ); + } + + Map toMap() { + return CellSwitchedMapper.ensureInitialized().encodeMap( + this as CellSwitched, + ); + } + + CellSwitchedCopyWith get copyWith => + _CellSwitchedCopyWithImpl( + this as CellSwitched, + $identity, + $identity, + ); + @override + String toString() { + return CellSwitchedMapper.ensureInitialized().stringifyValue( + this as CellSwitched, + ); + } + + @override + bool operator ==(Object other) { + return CellSwitchedMapper.ensureInitialized().equalsValue( + this as CellSwitched, + other, + ); + } + + @override + int get hashCode { + return CellSwitchedMapper.ensureInitialized().hashValue( + this as CellSwitched, + ); + } +} + +extension CellSwitchedValueCopy<$R, $Out> + on ObjectCopyWith<$R, CellSwitched, $Out> { + CellSwitchedCopyWith<$R, CellSwitched, $Out> get $asCellSwitched => + $base.as((v, t, t2) => _CellSwitchedCopyWithImpl<$R, $Out>(v, t, t2)); +} + +abstract class CellSwitchedCopyWith<$R, $In extends CellSwitched, $Out> + implements HybridWorldEventCopyWith<$R, $In, $Out> { + VectorDefinitionCopyWith<$R, VectorDefinition, VectorDefinition>? get cell; + @override + $R call({VectorDefinition? cell, bool? selected, bool? teleport}); + CellSwitchedCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); +} + +class _CellSwitchedCopyWithImpl<$R, $Out> + extends ClassCopyWithBase<$R, CellSwitched, $Out> + implements CellSwitchedCopyWith<$R, CellSwitched, $Out> { + _CellSwitchedCopyWithImpl(super.value, super.then, super.then2); + + @override + late final ClassMapperBase $mapper = + CellSwitchedMapper.ensureInitialized(); + @override + VectorDefinitionCopyWith<$R, VectorDefinition, VectorDefinition>? get cell => + $value.cell?.copyWith.$chain((v) => call(cell: v)); + @override + $R call({Object? cell = $none, bool? selected, bool? teleport}) => $apply( + FieldCopyWithData({ + if (cell != $none) #cell: cell, + if (selected != null) #selected: selected, + if (teleport != null) #teleport: teleport, + }), + ); + @override + CellSwitched $make(CopyWithData data) => CellSwitched( + data.get(#cell, or: $value.cell), + selected: data.get(#selected, or: $value.selected), + teleport: data.get(#teleport, or: $value.teleport), + ); + + @override + CellSwitchedCopyWith<$R2, CellSwitched, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t, + ) => _CellSwitchedCopyWithImpl<$R2, $Out2>($value, $cast, t); +} + class BackgroundChangedMapper extends SubClassMapperBase { BackgroundChangedMapper._(); diff --git a/api/lib/src/event/hybrid.dart b/api/lib/src/event/hybrid.dart index fc197e57..591ede6d 100644 --- a/api/lib/src/event/hybrid.dart +++ b/api/lib/src/event/hybrid.dart @@ -5,6 +5,14 @@ sealed class HybridWorldEvent extends WorldEvent with HybridWorldEventMappable implements ClientWorldEvent, ServerWorldEvent {} +@MappableClass() +final class CellSwitched extends HybridWorldEvent with CellSwitchedMappable { + final VectorDefinition? cell; + final bool selected, teleport; + + CellSwitched(this.cell, {this.selected = true, this.teleport = false}); +} + @MappableClass() final class BackgroundChanged extends HybridWorldEvent with BackgroundChangedMappable { diff --git a/api/lib/src/event/process/server.dart b/api/lib/src/event/process/server.dart index c0d058e4..3a1455d6 100644 --- a/api/lib/src/event/process/server.dart +++ b/api/lib/src/event/process/server.dart @@ -478,5 +478,7 @@ ServerProcessed processServerEvent( } } return ServerProcessed(state.copyWith(info: info)); + case CellSwitched(): + return ServerProcessed(null); } } diff --git a/api/lib/src/event/server.dart b/api/lib/src/event/server.dart index 2bb4e2f3..6252602a 100644 --- a/api/lib/src/event/server.dart +++ b/api/lib/src/event/server.dart @@ -34,7 +34,7 @@ final class WorldInitialized extends ServerWorldEvent clearUserInterface: true, info: state.info.copyWith( teams: mode?.item.teams ?? {}, - script: mode?.location, + gameMode: mode?.location, ), table: mode?.item.tables[state.tableName] ?? GameTable(), teamMembers: const {}, diff --git a/api/lib/src/models/table.mapper.dart b/api/lib/src/models/table.mapper.dart index af7073d6..125d7ecf 100644 --- a/api/lib/src/models/table.mapper.dart +++ b/api/lib/src/models/table.mapper.dart @@ -603,18 +603,11 @@ class ItemLocationMapper extends ClassMapperBase { ); static String _$id(ItemLocation v) => v.id; static const Field _f$id = Field('id', _$id); - static bool _$isEmpty(ItemLocation v) => v.isEmpty; - static const Field _f$isEmpty = Field( - 'isEmpty', - _$isEmpty, - mode: FieldMode.member, - ); @override final MappableFields fields = const { #namespace: _f$namespace, #id: _f$id, - #isEmpty: _f$isEmpty, }; @override diff --git a/app/lib/bloc/world/bloc.dart b/app/lib/bloc/world/bloc.dart index 88e6903e..1e31501b 100644 --- a/app/lib/bloc/world/bloc.dart +++ b/app/lib/bloc/world/bloc.dart @@ -87,6 +87,15 @@ class WorldBloc extends Bloc { }) ..serverEvents.listen(_processEvent); + on((event, emit) { + emit( + state.copyWith( + selectedCell: event.selected ? event.cell : state.selectedCell, + selectedDeck: null, + showHand: true, + ), + ); + }); on((event, emit) async { try { final signature = state.assetManager.createSignature(); @@ -121,17 +130,6 @@ class WorldBloc extends Bloc { ), ); }); - on((event, emit) { - emit( - state.copyWith( - selectedCell: event.toggle && state.selectedCell == event.cell - ? null - : event.cell, - selectedDeck: null, - showHand: true, - ), - ); - }); on((event, emit) { emit(state.copyWith(switchCellOnMove: event.value)); }); diff --git a/app/lib/bloc/world/local.dart b/app/lib/bloc/world/local.dart index bebcfb1e..fa1b9291 100644 --- a/app/lib/bloc/world/local.dart +++ b/app/lib/bloc/world/local.dart @@ -15,14 +15,6 @@ final class HandChanged extends LocalWorldEvent with HandChangedMappable { HandChanged.toggle({this.deck}) : show = null; } -@MappableClass() -final class CellSwitched extends LocalWorldEvent with CellSwitchedMappable { - final VectorDefinition? cell; - final bool toggle; - - CellSwitched(this.cell, {this.toggle = false}); -} - @MappableClass() final class ColorSchemeChanged extends LocalWorldEvent with ColorSchemeChangedMappable { diff --git a/app/lib/bloc/world/local.mapper.dart b/app/lib/bloc/world/local.mapper.dart index 2bf60e8c..98b404fd 100644 --- a/app/lib/bloc/world/local.mapper.dart +++ b/app/lib/bloc/world/local.mapper.dart @@ -151,151 +151,6 @@ class _HandChangedCopyWithImpl<$R, $Out> ) => _HandChangedCopyWithImpl<$R2, $Out2>($value, $cast, t); } -class CellSwitchedMapper extends SubClassMapperBase { - CellSwitchedMapper._(); - - static CellSwitchedMapper? _instance; - static CellSwitchedMapper ensureInitialized() { - if (_instance == null) { - MapperContainer.globals.use(_instance = CellSwitchedMapper._()); - LocalWorldEventMapper.ensureInitialized().addSubMapper(_instance!); - VectorDefinitionMapper.ensureInitialized(); - } - return _instance!; - } - - @override - final String id = 'CellSwitched'; - - static VectorDefinition? _$cell(CellSwitched v) => v.cell; - static const Field _f$cell = Field( - 'cell', - _$cell, - ); - static bool _$toggle(CellSwitched v) => v.toggle; - static const Field _f$toggle = Field( - 'toggle', - _$toggle, - opt: true, - def: false, - ); - - @override - final MappableFields fields = const { - #cell: _f$cell, - #toggle: _f$toggle, - }; - - @override - final String discriminatorKey = 'type'; - @override - final dynamic discriminatorValue = 'CellSwitched'; - @override - late final ClassMapperBase superMapper = - LocalWorldEventMapper.ensureInitialized(); - - static CellSwitched _instantiate(DecodingData data) { - return CellSwitched(data.dec(_f$cell), toggle: data.dec(_f$toggle)); - } - - @override - final Function instantiate = _instantiate; - - static CellSwitched fromMap(Map map) { - return ensureInitialized().decodeMap(map); - } - - static CellSwitched fromJson(String json) { - return ensureInitialized().decodeJson(json); - } -} - -mixin CellSwitchedMappable { - String toJson() { - return CellSwitchedMapper.ensureInitialized().encodeJson( - this as CellSwitched, - ); - } - - Map toMap() { - return CellSwitchedMapper.ensureInitialized().encodeMap( - this as CellSwitched, - ); - } - - CellSwitchedCopyWith get copyWith => - _CellSwitchedCopyWithImpl( - this as CellSwitched, - $identity, - $identity, - ); - @override - String toString() { - return CellSwitchedMapper.ensureInitialized().stringifyValue( - this as CellSwitched, - ); - } - - @override - bool operator ==(Object other) { - return CellSwitchedMapper.ensureInitialized().equalsValue( - this as CellSwitched, - other, - ); - } - - @override - int get hashCode { - return CellSwitchedMapper.ensureInitialized().hashValue( - this as CellSwitched, - ); - } -} - -extension CellSwitchedValueCopy<$R, $Out> - on ObjectCopyWith<$R, CellSwitched, $Out> { - CellSwitchedCopyWith<$R, CellSwitched, $Out> get $asCellSwitched => - $base.as((v, t, t2) => _CellSwitchedCopyWithImpl<$R, $Out>(v, t, t2)); -} - -abstract class CellSwitchedCopyWith<$R, $In extends CellSwitched, $Out> - implements LocalWorldEventCopyWith<$R, $In, $Out> { - VectorDefinitionCopyWith<$R, VectorDefinition, VectorDefinition>? get cell; - @override - $R call({VectorDefinition? cell, bool? toggle}); - CellSwitchedCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); -} - -class _CellSwitchedCopyWithImpl<$R, $Out> - extends ClassCopyWithBase<$R, CellSwitched, $Out> - implements CellSwitchedCopyWith<$R, CellSwitched, $Out> { - _CellSwitchedCopyWithImpl(super.value, super.then, super.then2); - - @override - late final ClassMapperBase $mapper = - CellSwitchedMapper.ensureInitialized(); - @override - VectorDefinitionCopyWith<$R, VectorDefinition, VectorDefinition>? get cell => - $value.cell?.copyWith.$chain((v) => call(cell: v)); - @override - $R call({Object? cell = $none, bool? toggle}) => $apply( - FieldCopyWithData({ - if (cell != $none) #cell: cell, - if (toggle != null) #toggle: toggle, - }), - ); - @override - CellSwitched $make(CopyWithData data) => CellSwitched( - data.get(#cell, or: $value.cell), - toggle: data.get(#toggle, or: $value.toggle), - ); - - @override - CellSwitchedCopyWith<$R2, CellSwitched, $Out2> $chain<$R2, $Out2>( - Then<$Out2, $R2> t, - ) => _CellSwitchedCopyWithImpl<$R2, $Out2>($value, $cast, t); -} - class ColorSchemeChangedMapper extends SubClassMapperBase { ColorSchemeChangedMapper._(); diff --git a/app/lib/board/cell.dart b/app/lib/board/cell.dart index da98625d..d03626e2 100644 --- a/app/lib/board/cell.dart +++ b/app/lib/board/cell.dart @@ -7,6 +7,7 @@ import 'package:flame/events.dart'; import 'package:flame_bloc/flame_bloc.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:setonix/pages/game/waypoint.dart'; import 'package:setonix/src/generated/i18n/app_localizations.dart'; import 'package:material_leap/material_leap.dart'; import 'package:setonix/bloc/world/bloc.dart'; @@ -120,7 +121,7 @@ class GameCell extends PositionComponent if (isSelected) { bloc.process(HandChanged.hide()); } else { - bloc.process(CellSwitched(toDefinition(), toggle: true)); + bloc.process(CellSwitched(isSelected ? null : toDefinition())); } } @@ -291,6 +292,21 @@ class GameCell extends PositionComponent onClose(); }, ), + ContextMenuButtonItem( + label: AppLocalizations.of(context).addWaypoint, + onPressed: () { + onClose(); + showDialog( + context: context, + builder: (context) => BlocProvider.value( + value: bloc, + child: WaypointDialog( + position: toGlobalDefinition(bloc.state), + ), + ), + ); + }, + ), ContextMenuButtonItem( label: AppLocalizations.of(context).teams, onPressed: () { diff --git a/app/lib/board/game.dart b/app/lib/board/game.dart index 16f8f472..eea6b77a 100644 --- a/app/lib/board/game.dart +++ b/app/lib/board/game.dart @@ -14,6 +14,8 @@ import 'package:setonix/board/grid.dart'; import 'package:setonix/board/hand/view.dart'; import 'package:setonix/helpers/scroll.dart'; import 'package:setonix/helpers/secondary.dart'; +import 'package:setonix/helpers/vector.dart'; +import 'package:setonix_api/setonix_api.dart'; class BoardGame extends FlameGame with @@ -172,4 +174,15 @@ class BoardGame extends FlameGame contextMenuBuilder(context, contextMenuController.remove), ); } + + void teleport(GlobalVectorDefinition position) { + final table = position.table; + if (table != bloc.state.world.tableName) { + bloc.add(TableSwitched(table)); + } + final cellSize = grid.cellSize; + camera.moveTo( + (position.position.toVector()..multiply(cellSize)) + cellSize / 2, + ); + } } diff --git a/app/lib/board/hand/figure.dart b/app/lib/board/hand/figure.dart index 120d0188..5a900dac 100644 --- a/app/lib/board/hand/figure.dart +++ b/app/lib/board/hand/figure.dart @@ -1,5 +1,4 @@ import 'package:flame/widgets.dart'; -import 'package:setonix/bloc/world/local.dart'; import 'package:setonix/bloc/world/state.dart'; import 'package:setonix/board/cell.dart'; import 'package:setonix/board/hand/item.dart'; diff --git a/app/lib/board/hand/object.dart b/app/lib/board/hand/object.dart index 6c84f914..003aef47 100644 --- a/app/lib/board/hand/object.dart +++ b/app/lib/board/hand/object.dart @@ -1,7 +1,6 @@ import 'package:flame/components.dart'; import 'package:flutter/widgets.dart'; import 'package:setonix/src/generated/i18n/app_localizations.dart'; -import 'package:setonix/bloc/world/local.dart'; import 'package:setonix/bloc/world/state.dart'; import 'package:setonix/board/cell.dart'; import 'package:setonix/board/hand/item.dart'; diff --git a/app/lib/l10n/app_en.arb b/app/lib/l10n/app_en.arb index 259efb45..98ec9c2c 100644 --- a/app/lib/l10n/app_en.arb +++ b/app/lib/l10n/app_en.arb @@ -284,5 +284,6 @@ "waypoints": "Waypoints", "addWaypoint": "Add waypoint", "editWaypoint": "Edit waypoint", - "team": "Team" + "team": "Team", + "public": "Public" } diff --git a/app/lib/pages/game/drawer.dart b/app/lib/pages/game/drawer.dart index 327b5c65..16ef3add 100644 --- a/app/lib/pages/game/drawer.dart +++ b/app/lib/pages/game/drawer.dart @@ -2,6 +2,7 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:setonix/board/game.dart'; import 'package:setonix/pages/game/multiplayer/dialog.dart'; import 'package:setonix/src/generated/i18n/app_localizations.dart'; import 'package:go_router/go_router.dart'; @@ -20,7 +21,9 @@ import 'package:setonix/pages/packs/dialog.dart'; import 'package:setonix_api/setonix_api.dart'; class GameDrawer extends StatelessWidget { - const GameDrawer({super.key}); + final BoardGame game; + + const GameDrawer({super.key, required this.game}); @override Widget build(BuildContext context) { @@ -191,7 +194,7 @@ class GameDrawer extends StatelessWidget { final state = bloc.state; Widget buildWaypointTile(Waypoint waypoint, {String? team}) => ContextRegion( - builder: (context, button, controller) => ListTile( + builder: (ctx, button, controller) => ListTile( title: Text(waypoint.name), leading: Icon( team != null @@ -199,6 +202,11 @@ class GameDrawer extends StatelessWidget { : PhosphorIconsLight.mapPin, ), trailing: button, + onTap: () { + Navigator.of(ctx).pop(); + game.teleport(waypoint.position); + Scaffold.of(context).closeDrawer(); + }, ), menuChildren: [ MenuItemButton( diff --git a/app/lib/pages/game/page.dart b/app/lib/pages/game/page.dart index 29ebe160..9973168c 100644 --- a/app/lib/pages/game/page.dart +++ b/app/lib/pages/game/page.dart @@ -147,6 +147,12 @@ class _GamePageState extends State { onReconnect: () async => (await _bloc)?.$1.reconnect(), ); } + final game = BoardGame( + bloc: context.read(), + settingsCubit: context.read(), + contextMenuController: _contextMenuController, + onEscape: () => Scaffold.of(context).openDrawer(), + ); return Scaffold( appBar: WindowTitleBar( title: Text(AppLocalizations.of(context).game), @@ -181,7 +187,7 @@ class _GamePageState extends State { ), ], ), - drawer: const GameDrawer(), + drawer: GameDrawer(game: game), endDrawer: BlocBuilder( buildWhen: (previous, current) => previous.drawerView != current.drawerView, @@ -258,13 +264,7 @@ class _GamePageState extends State { ) else GameWidget( - game: BoardGame( - bloc: context.read(), - settingsCubit: context.read(), - contextMenuController: _contextMenuController, - onEscape: () => - Scaffold.of(context).openDrawer(), - ), + game: game, focusNode: _focusNode, initialActiveOverlays: ['dialogs', 'filter'], overlayBuilderMap: { diff --git a/app/lib/pages/game/waypoint.dart b/app/lib/pages/game/waypoint.dart index e8b36f8c..6599d71d 100644 --- a/app/lib/pages/game/waypoint.dart +++ b/app/lib/pages/game/waypoint.dart @@ -26,11 +26,12 @@ class WaypointDialog extends StatelessWidget { final bloc = context.read(); return ResponsiveAlertDialog( title: Text(isCreated ? loc.addWaypoint : loc.editWaypoint), + constraints: const BoxConstraints(maxWidth: LeapBreakpoints.compact), content: ListView( shrinkWrap: true, children: [ TextFormField( - decoration: InputDecoration(labelText: loc.name), + decoration: InputDecoration(labelText: loc.name, filled: true), initialValue: waypoint.name, onChanged: (value) { waypoint = waypoint.copyWith(name: value); @@ -38,8 +39,9 @@ class WaypointDialog extends StatelessWidget { ), const SizedBox(height: 8), DropdownMenu( + expandedInsets: EdgeInsets.symmetric(horizontal: 0, vertical: 12), dropdownMenuEntries: [ - DropdownMenuEntry(value: null, label: loc.name), + DropdownMenuEntry(value: null, label: loc.public), ...bloc.state.world.getTeams().map( (e) => DropdownMenuEntry(value: e, label: e), ), @@ -66,6 +68,7 @@ class WaypointDialog extends StatelessWidget { name: this.waypoint?.name, ), ); + Navigator.of(context).pop(); }, child: Text(isCreated ? loc.add : loc.save), ), From ef4e57e3806cfbda68faaa6d039855d0ec53a10a Mon Sep 17 00:00:00 2001 From: CodeDoctorDE Date: Mon, 29 Sep 2025 16:39:58 +0200 Subject: [PATCH 09/10] Add waypoint display --- app/lib/bloc/world/bloc.dart | 3 + app/lib/bloc/world/local.dart | 8 ++ app/lib/bloc/world/local.mapper.dart | 144 +++++++++++++++++++++++++++ app/lib/bloc/world/state.dart | 3 +- app/lib/bloc/world/state.mapper.dart | 13 +++ app/lib/board/cell.dart | 67 ++++++++++++- app/lib/pages/game/drawer.dart | 125 +++++++++++++---------- 7 files changed, 308 insertions(+), 55 deletions(-) diff --git a/app/lib/bloc/world/bloc.dart b/app/lib/bloc/world/bloc.dart index 1e31501b..47d53761 100644 --- a/app/lib/bloc/world/bloc.dart +++ b/app/lib/bloc/world/bloc.dart @@ -133,6 +133,9 @@ class WorldBloc extends Bloc { on((event, emit) { emit(state.copyWith(switchCellOnMove: event.value)); }); + on((event, emit) { + emit(state.copyWith(showWaypoints: event.value)); + }); on((event, emit) { emit( state.copyWith.world( diff --git a/app/lib/bloc/world/local.dart b/app/lib/bloc/world/local.dart index fa1b9291..dd18bdc3 100644 --- a/app/lib/bloc/world/local.dart +++ b/app/lib/bloc/world/local.dart @@ -31,6 +31,14 @@ final class SwitchCellOnMoveChanged extends LocalWorldEvent SwitchCellOnMoveChanged(this.value); } +@MappableClass() +final class WaypointVisibilityChanged extends LocalWorldEvent + with WaypointVisibilityChangedMappable { + final bool value; + + WaypointVisibilityChanged(this.value); +} + @MappableClass() final class TableSwitched extends LocalWorldEvent with TableSwitchedMappable { final String name; diff --git a/app/lib/bloc/world/local.mapper.dart b/app/lib/bloc/world/local.mapper.dart index 98b404fd..3ac397e9 100644 --- a/app/lib/bloc/world/local.mapper.dart +++ b/app/lib/bloc/world/local.mapper.dart @@ -429,6 +429,150 @@ class _SwitchCellOnMoveChangedCopyWithImpl<$R, $Out> _SwitchCellOnMoveChangedCopyWithImpl<$R2, $Out2>($value, $cast, t); } +class WaypointVisibilityChangedMapper + extends SubClassMapperBase { + WaypointVisibilityChangedMapper._(); + + static WaypointVisibilityChangedMapper? _instance; + static WaypointVisibilityChangedMapper ensureInitialized() { + if (_instance == null) { + MapperContainer.globals.use( + _instance = WaypointVisibilityChangedMapper._(), + ); + LocalWorldEventMapper.ensureInitialized().addSubMapper(_instance!); + } + return _instance!; + } + + @override + final String id = 'WaypointVisibilityChanged'; + + static bool _$value(WaypointVisibilityChanged v) => v.value; + static const Field _f$value = Field( + 'value', + _$value, + ); + + @override + final MappableFields fields = const { + #value: _f$value, + }; + + @override + final String discriminatorKey = 'type'; + @override + final dynamic discriminatorValue = 'WaypointVisibilityChanged'; + @override + late final ClassMapperBase superMapper = + LocalWorldEventMapper.ensureInitialized(); + + static WaypointVisibilityChanged _instantiate(DecodingData data) { + return WaypointVisibilityChanged(data.dec(_f$value)); + } + + @override + final Function instantiate = _instantiate; + + static WaypointVisibilityChanged fromMap(Map map) { + return ensureInitialized().decodeMap(map); + } + + static WaypointVisibilityChanged fromJson(String json) { + return ensureInitialized().decodeJson(json); + } +} + +mixin WaypointVisibilityChangedMappable { + String toJson() { + return WaypointVisibilityChangedMapper.ensureInitialized() + .encodeJson( + this as WaypointVisibilityChanged, + ); + } + + Map toMap() { + return WaypointVisibilityChangedMapper.ensureInitialized() + .encodeMap( + this as WaypointVisibilityChanged, + ); + } + + WaypointVisibilityChangedCopyWith< + WaypointVisibilityChanged, + WaypointVisibilityChanged, + WaypointVisibilityChanged + > + get copyWith => + _WaypointVisibilityChangedCopyWithImpl< + WaypointVisibilityChanged, + WaypointVisibilityChanged + >(this as WaypointVisibilityChanged, $identity, $identity); + @override + String toString() { + return WaypointVisibilityChangedMapper.ensureInitialized().stringifyValue( + this as WaypointVisibilityChanged, + ); + } + + @override + bool operator ==(Object other) { + return WaypointVisibilityChangedMapper.ensureInitialized().equalsValue( + this as WaypointVisibilityChanged, + other, + ); + } + + @override + int get hashCode { + return WaypointVisibilityChangedMapper.ensureInitialized().hashValue( + this as WaypointVisibilityChanged, + ); + } +} + +extension WaypointVisibilityChangedValueCopy<$R, $Out> + on ObjectCopyWith<$R, WaypointVisibilityChanged, $Out> { + WaypointVisibilityChangedCopyWith<$R, WaypointVisibilityChanged, $Out> + get $asWaypointVisibilityChanged => $base.as( + (v, t, t2) => _WaypointVisibilityChangedCopyWithImpl<$R, $Out>(v, t, t2), + ); +} + +abstract class WaypointVisibilityChangedCopyWith< + $R, + $In extends WaypointVisibilityChanged, + $Out +> + implements LocalWorldEventCopyWith<$R, $In, $Out> { + @override + $R call({bool? value}); + WaypointVisibilityChangedCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t, + ); +} + +class _WaypointVisibilityChangedCopyWithImpl<$R, $Out> + extends ClassCopyWithBase<$R, WaypointVisibilityChanged, $Out> + implements + WaypointVisibilityChangedCopyWith<$R, WaypointVisibilityChanged, $Out> { + _WaypointVisibilityChangedCopyWithImpl(super.value, super.then, super.then2); + + @override + late final ClassMapperBase $mapper = + WaypointVisibilityChangedMapper.ensureInitialized(); + @override + $R call({bool? value}) => + $apply(FieldCopyWithData({if (value != null) #value: value})); + @override + WaypointVisibilityChanged $make(CopyWithData data) => + WaypointVisibilityChanged(data.get(#value, or: $value.value)); + + @override + WaypointVisibilityChangedCopyWith<$R2, WaypointVisibilityChanged, $Out2> + $chain<$R2, $Out2>(Then<$Out2, $R2> t) => + _WaypointVisibilityChangedCopyWithImpl<$R2, $Out2>($value, $cast, t); +} + class TableSwitchedMapper extends SubClassMapperBase { TableSwitchedMapper._(); diff --git a/app/lib/bloc/world/state.dart b/app/lib/bloc/world/state.dart index d7ec52ba..9cdbe338 100644 --- a/app/lib/bloc/world/state.dart +++ b/app/lib/bloc/world/state.dart @@ -22,7 +22,7 @@ final class ClientWorldState with ClientWorldStateMappable { final ColorScheme colorScheme; final VectorDefinition? selectedCell; final ItemLocation? selectedDeck; - final bool showHand, switchCellOnMove; + final bool showHand, switchCellOnMove, showWaypoints; final DrawerView drawerView; final String searchTerm; final bool showDuplicates; @@ -36,6 +36,7 @@ final class ClientWorldState with ClientWorldStateMappable { this.selectedDeck, this.showHand = false, this.switchCellOnMove = false, + this.showWaypoints = false, this.drawerView = DrawerView.chat, this.searchTerm = '', this.showDuplicates = false, diff --git a/app/lib/bloc/world/state.mapper.dart b/app/lib/bloc/world/state.mapper.dart index 6bf98b7d..1ea8f43e 100644 --- a/app/lib/bloc/world/state.mapper.dart +++ b/app/lib/bloc/world/state.mapper.dart @@ -158,6 +158,13 @@ class ClientWorldStateMapper extends ClassMapperBase { opt: true, def: false, ); + static bool _$showWaypoints(ClientWorldState v) => v.showWaypoints; + static const Field _f$showWaypoints = Field( + 'showWaypoints', + _$showWaypoints, + opt: true, + def: false, + ); static DrawerView _$drawerView(ClientWorldState v) => v.drawerView; static const Field _f$drawerView = Field( 'drawerView', @@ -190,6 +197,7 @@ class ClientWorldStateMapper extends ClassMapperBase { #selectedDeck: _f$selectedDeck, #showHand: _f$showHand, #switchCellOnMove: _f$switchCellOnMove, + #showWaypoints: _f$showWaypoints, #drawerView: _f$drawerView, #searchTerm: _f$searchTerm, #showDuplicates: _f$showDuplicates, @@ -205,6 +213,7 @@ class ClientWorldStateMapper extends ClassMapperBase { selectedDeck: data.dec(_f$selectedDeck), showHand: data.dec(_f$showHand), switchCellOnMove: data.dec(_f$switchCellOnMove), + showWaypoints: data.dec(_f$showWaypoints), drawerView: data.dec(_f$drawerView), searchTerm: data.dec(_f$searchTerm), showDuplicates: data.dec(_f$showDuplicates), @@ -286,6 +295,7 @@ abstract class ClientWorldStateCopyWith<$R, $In extends ClientWorldState, $Out> ItemLocation? selectedDeck, bool? showHand, bool? switchCellOnMove, + bool? showWaypoints, DrawerView? drawerView, String? searchTerm, bool? showDuplicates, @@ -323,6 +333,7 @@ class _ClientWorldStateCopyWithImpl<$R, $Out> Object? selectedDeck = $none, bool? showHand, bool? switchCellOnMove, + bool? showWaypoints, DrawerView? drawerView, String? searchTerm, bool? showDuplicates, @@ -336,6 +347,7 @@ class _ClientWorldStateCopyWithImpl<$R, $Out> if (selectedDeck != $none) #selectedDeck: selectedDeck, if (showHand != null) #showHand: showHand, if (switchCellOnMove != null) #switchCellOnMove: switchCellOnMove, + if (showWaypoints != null) #showWaypoints: showWaypoints, if (drawerView != null) #drawerView: drawerView, if (searchTerm != null) #searchTerm: searchTerm, if (showDuplicates != null) #showDuplicates: showDuplicates, @@ -351,6 +363,7 @@ class _ClientWorldStateCopyWithImpl<$R, $Out> selectedDeck: data.get(#selectedDeck, or: $value.selectedDeck), showHand: data.get(#showHand, or: $value.showHand), switchCellOnMove: data.get(#switchCellOnMove, or: $value.switchCellOnMove), + showWaypoints: data.get(#showWaypoints, or: $value.showWaypoints), drawerView: data.get(#drawerView, or: $value.drawerView), searchTerm: data.get(#searchTerm, or: $value.searchTerm), showDuplicates: data.get(#showDuplicates, or: $value.showDuplicates), diff --git a/app/lib/board/cell.dart b/app/lib/board/cell.dart index d03626e2..b875129d 100644 --- a/app/lib/board/cell.dart +++ b/app/lib/board/cell.dart @@ -4,6 +4,7 @@ import 'package:flame/collisions.dart'; import 'package:flame/components.dart'; import 'package:flame/effects.dart'; import 'package:flame/events.dart'; +import 'package:flame/text.dart'; import 'package:flame_bloc/flame_bloc.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -38,6 +39,7 @@ class GameCell extends PositionComponent ScrollCallbacks { late final SpriteComponent _selectionComponent; SpriteComponent? _cardComponent, _boardComponent; + TextElementComponent? _waypointComponent; late final BoardGrid grid; List? _effects; @@ -61,6 +63,66 @@ class GameCell extends PositionComponent } } + void _buildWaypointComponent(ClientWorldState state) { + final visible = state.showWaypoints; + _waypointComponent?.removeFromParent(); + _waypointComponent = null; + if (!visible) { + return; + } + final global = toGlobalDefinition(state); + final globalWaypoints = state.info.waypoints + .where((waypoint) => waypoint.position == global) + .map((e) => PlainTextNode(e.name)) + .toList(); + final teamWaypoints = state.world.getTeams().expand((name) { + final team = state.info.teams[name]; + if (team == null) return Iterable.empty(); + return team.waypoints + .where((waypoint) => waypoint.position == global) + .map( + (e) => CustomInlineTextNode( + PlainTextNode(e.name), + styleName: 'team-$name', + ), + ); + }).toList(); + if (globalWaypoints.isEmpty && teamWaypoints.isEmpty) { + return; + } + final blocks = [ + ParagraphNode.group(globalWaypoints), + ParagraphNode.group(teamWaypoints), + ]; + final document = DocumentRoot(blocks); + final component = _waypointComponent = TextElementComponent.fromDocument( + document: document, + size: size, + priority: 2, + style: DocumentStyle( + paragraph: BlockStyle(textAlign: TextAlign.center), + customStyles: { + for (final entry in state.world.getTeams()) + 'team-$entry': InlineTextStyle( + color: + state.info.teams[entry]?.color?.color ?? + state.colorScheme.primary, + ), + }, + text: InlineTextStyle( + shadows: [ + Shadow( + color: Colors.black, + offset: const Offset(0, 0), + blurRadius: 2, + ), + ], + ), + ), + ); + add(component); + } + @override void onLoad() { super.onLoad(); @@ -82,7 +144,8 @@ class GameCell extends PositionComponent previousState.table.cells[definition] != newState.table.cells[definition] || previousState.teamMembers != newState.teamMembers || - previousState.colorScheme != newState.colorScheme; + previousState.colorScheme != newState.colorScheme || + previousState.showWaypoints != newState.showWaypoints; } bool get isSelected => isMounted && bloc.state.selectedCell == toDefinition(); @@ -134,6 +197,7 @@ class GameCell extends PositionComponent @override void onInitialState(ClientWorldState state) { if (state.selectedCell != toDefinition()) _selectionComponent.opacity = 0; + _buildWaypointComponent(state); } bool isClaimed(ClientWorldState state) => state.info.teams.entries.any( @@ -178,6 +242,7 @@ class GameCell extends PositionComponent ), ]); } + _buildWaypointComponent(state); } Future _updateTop() async { diff --git a/app/lib/pages/game/drawer.dart b/app/lib/pages/game/drawer.dart index 16ef3add..3fd63194 100644 --- a/app/lib/pages/game/drawer.dart +++ b/app/lib/pages/game/drawer.dart @@ -184,61 +184,80 @@ class GameDrawer extends StatelessWidget { ); }, ), - AdvancedSwitchListTile( - title: Text(AppLocalizations.of(context).waypoints), - leading: const Icon(PhosphorIconsLight.mapPin), - value: false, - onChanged: (value) {}, - onTap: () { - final bloc = context.read(); - final state = bloc.state; - Widget buildWaypointTile(Waypoint waypoint, {String? team}) => - ContextRegion( - builder: (ctx, button, controller) => ListTile( - title: Text(waypoint.name), - leading: Icon( - team != null - ? PhosphorIconsLight.users - : PhosphorIconsLight.mapPin, - ), - trailing: button, - onTap: () { - Navigator.of(ctx).pop(); - game.teleport(waypoint.position); - Scaffold.of(context).closeDrawer(); - }, - ), - menuChildren: [ - MenuItemButton( - leadingIcon: const Icon(PhosphorIconsLight.pencil), - child: Text(AppLocalizations.of(context).edit), - onPressed: () {}, - ), - MenuItemButton( - leadingIcon: const Icon(PhosphorIconsLight.trash), - child: Text(AppLocalizations.of(context).delete), - onPressed: () { - bloc.add( - WaypointRemoved(name: waypoint.name, team: team), - ); + BlocBuilder( + buildWhen: (previous, current) => + previous.showWaypoints != current.showWaypoints, + builder: (context, state) { + return Padding( + padding: EdgeInsets.only(right: 24), + child: AdvancedSwitchListTile( + title: Text(AppLocalizations.of(context).waypoints), + leading: const Icon(PhosphorIconsLight.mapPin), + value: state.showWaypoints, + onChanged: (value) => context.read().process( + WaypointVisibilityChanged(value), + ), + onTap: () { + final bloc = context.read(); + final state = bloc.state; + Widget buildWaypointTile( + Waypoint waypoint, { + String? team, + }) => ContextRegion( + builder: (ctx, button, controller) => ListTile( + title: Text(waypoint.name), + leading: Icon( + team != null + ? PhosphorIconsLight.users + : PhosphorIconsLight.mapPin, + ), + trailing: button, + onTap: () { + Navigator.of(ctx).pop(); + game.teleport(waypoint.position); + Scaffold.of(context).closeDrawer(); }, ), - ], - ); - showLeapBottomSheet( - context: context, - titleBuilder: (context) => - Text(AppLocalizations.of(context).waypoints), - childrenBuilder: (context) => [ - ...state.world.getTeams().expand( - (e) => - state.info.teams[e]?.waypoints.map( - (waypoint) => buildWaypointTile(waypoint, team: e), - ) ?? - [], - ), - ...state.info.waypoints.map((e) => buildWaypointTile(e)), - ], + menuChildren: [ + MenuItemButton( + leadingIcon: const Icon(PhosphorIconsLight.pencil), + child: Text(AppLocalizations.of(context).edit), + onPressed: () {}, + ), + MenuItemButton( + leadingIcon: const Icon(PhosphorIconsLight.trash), + child: Text(AppLocalizations.of(context).delete), + onPressed: () { + bloc.add( + WaypointRemoved( + name: waypoint.name, + team: team, + ), + ); + }, + ), + ], + ); + showLeapBottomSheet( + context: context, + titleBuilder: (context) => + Text(AppLocalizations.of(context).waypoints), + childrenBuilder: (context) => [ + ...state.world.getTeams().expand( + (e) => + state.info.teams[e]?.waypoints.map( + (waypoint) => + buildWaypointTile(waypoint, team: e), + ) ?? + [], + ), + ...state.info.waypoints.map( + (e) => buildWaypointTile(e), + ), + ], + ); + }, + ), ); }, ), From 25a190ebf0671562486fb8fe854141bc45cf9a9c Mon Sep 17 00:00:00 2001 From: CodeDoctorDE Date: Mon, 29 Sep 2025 20:54:06 +0200 Subject: [PATCH 10/10] Add chips to waypoint dialog --- app/lib/l10n/app_en.arb | 3 +- app/lib/pages/game/drawer.dart | 179 ++++++++++++++++++++++----------- 2 files changed, 121 insertions(+), 61 deletions(-) diff --git a/app/lib/l10n/app_en.arb b/app/lib/l10n/app_en.arb index 98ec9c2c..8b4f2df9 100644 --- a/app/lib/l10n/app_en.arb +++ b/app/lib/l10n/app_en.arb @@ -285,5 +285,6 @@ "addWaypoint": "Add waypoint", "editWaypoint": "Edit waypoint", "team": "Team", - "public": "Public" + "public": "Public", + "noWaypoints": "There are no waypoints available" } diff --git a/app/lib/pages/game/drawer.dart b/app/lib/pages/game/drawer.dart index 3fd63194..39ff998b 100644 --- a/app/lib/pages/game/drawer.dart +++ b/app/lib/pages/game/drawer.dart @@ -4,6 +4,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:setonix/board/game.dart'; import 'package:setonix/pages/game/multiplayer/dialog.dart'; +import 'package:setonix/pages/game/waypoint.dart'; import 'package:setonix/src/generated/i18n/app_localizations.dart'; import 'package:go_router/go_router.dart'; import 'package:material_leap/material_leap.dart'; @@ -197,66 +198,7 @@ class GameDrawer extends StatelessWidget { onChanged: (value) => context.read().process( WaypointVisibilityChanged(value), ), - onTap: () { - final bloc = context.read(); - final state = bloc.state; - Widget buildWaypointTile( - Waypoint waypoint, { - String? team, - }) => ContextRegion( - builder: (ctx, button, controller) => ListTile( - title: Text(waypoint.name), - leading: Icon( - team != null - ? PhosphorIconsLight.users - : PhosphorIconsLight.mapPin, - ), - trailing: button, - onTap: () { - Navigator.of(ctx).pop(); - game.teleport(waypoint.position); - Scaffold.of(context).closeDrawer(); - }, - ), - menuChildren: [ - MenuItemButton( - leadingIcon: const Icon(PhosphorIconsLight.pencil), - child: Text(AppLocalizations.of(context).edit), - onPressed: () {}, - ), - MenuItemButton( - leadingIcon: const Icon(PhosphorIconsLight.trash), - child: Text(AppLocalizations.of(context).delete), - onPressed: () { - bloc.add( - WaypointRemoved( - name: waypoint.name, - team: team, - ), - ); - }, - ), - ], - ); - showLeapBottomSheet( - context: context, - titleBuilder: (context) => - Text(AppLocalizations.of(context).waypoints), - childrenBuilder: (context) => [ - ...state.world.getTeams().expand( - (e) => - state.info.teams[e]?.waypoints.map( - (waypoint) => - buildWaypointTile(waypoint, team: e), - ) ?? - [], - ), - ...state.info.waypoints.map( - (e) => buildWaypointTile(e), - ), - ], - ); - }, + onTap: () => _showWaypointsDialog(context), ), ); }, @@ -726,4 +668,121 @@ class GameDrawer extends StatelessWidget { ], ); } + + void _showWaypointsDialog(BuildContext context) { + final bloc = context.read(); + Widget buildWaypointTile(Waypoint waypoint, {String? team}) { + final gameTeam = bloc.state.info.teams[team]; + return ContextRegion( + builder: (ctx, button, controller) => ListTile( + title: Text(waypoint.name), + leading: Icon( + team != null ? PhosphorIconsLight.users : PhosphorIconsLight.mapPin, + color: gameTeam?.color?.color, + ), + trailing: button, + onTap: () { + Navigator.of(ctx).pop(); + game.teleport(waypoint.position); + Scaffold.of(context).closeDrawer(); + }, + ), + menuChildren: [ + MenuItemButton( + leadingIcon: const Icon(PhosphorIconsLight.pencil), + child: Text(AppLocalizations.of(context).edit), + onPressed: () => showDialog( + context: context, + builder: (context) => BlocProvider.value( + value: bloc, + child: WaypointDialog(waypoint: waypoint, team: team), + ), + ), + ), + MenuItemButton( + leadingIcon: const Icon(PhosphorIconsLight.trash), + child: Text(AppLocalizations.of(context).delete), + onPressed: () { + bloc.add(WaypointRemoved(name: waypoint.name, team: team)); + }, + ), + ], + ); + } + + bool showPublic = true, showTeams = true; + + showLeapBottomSheet( + context: context, + titleBuilder: (context) => Text(AppLocalizations.of(context).waypoints), + childrenBuilder: (context) => [ + StatefulBuilder( + builder: (context, setLocalState) { + return BlocBuilder( + bloc: bloc, + buildWhen: (previous, current) => + previous.info.waypoints != current.info.waypoints || + previous.info.teams != current.info.teams || + previous.teamMembers != current.teamMembers, + builder: (context, state) { + final List waypointTiles = []; + + if (showTeams) { + waypointTiles.addAll( + state.world.getTeams().expand( + (e) => + state.info.teams[e]?.waypoints.map( + (waypoint) => buildWaypointTile(waypoint, team: e), + ) ?? + [], + ), + ); + } + + if (showPublic) { + waypointTiles.addAll( + state.info.waypoints.map((e) => buildWaypointTile(e)), + ); + } + + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Wrap( + spacing: 8, + runSpacing: 4, + children: [ + FilterChip( + label: Text(AppLocalizations.of(context).teams), + selected: showTeams, + onSelected: (v) => setLocalState(() => showTeams = v), + ), + FilterChip( + label: const Text('Public'), + selected: showPublic, + onSelected: (v) => + setLocalState(() => showPublic = v), + ), + ], + ), + const SizedBox(height: 8), + if (waypointTiles.isEmpty) + Padding( + padding: const EdgeInsets.symmetric(vertical: 12), + child: Center( + child: Text(AppLocalizations.of(context).noWaypoints), + ), + ) + else + ...waypointTiles, + ], + ); + }, + ); + }, + ), + ], + ); + } }