Initial flame_lua_runtime package
This commit is contained in:
248
lib/runtime/commands/command_support.dart
Normal file
248
lib/runtime/commands/command_support.dart
Normal file
@@ -0,0 +1,248 @@
|
||||
part of 'command_executor.dart';
|
||||
|
||||
extension _CommandExecutorSupport on CommandExecutor {
|
||||
void _appendCompletionEffect(
|
||||
List<Effect> effects,
|
||||
RuntimeCommand command,
|
||||
String target,
|
||||
int targetEpoch,
|
||||
RuntimeTask<_CommandResult> task,
|
||||
String? scope,
|
||||
int? scopeEpoch,
|
||||
) {
|
||||
effects.add(
|
||||
FunctionEffect((_, __) {
|
||||
if (!_scopeIsAlive(scope) ||
|
||||
!_renderTree.isNodeEpochAlive(target, targetEpoch)) {
|
||||
task.cancel();
|
||||
return;
|
||||
}
|
||||
_emitCompletion(command, target, scope, targetEpoch, scopeEpoch);
|
||||
task.complete(_CommandResult.completed);
|
||||
}, EffectController(duration: 0.01)),
|
||||
);
|
||||
}
|
||||
|
||||
void _emitCompletion(
|
||||
RuntimeCommand command,
|
||||
String target,
|
||||
String? scope, [
|
||||
int? targetEpoch,
|
||||
int? scopeEpoch,
|
||||
]) {
|
||||
final onComplete = _optionalString(
|
||||
command.payload['onComplete'],
|
||||
'onComplete',
|
||||
);
|
||||
if (onComplete == null) {
|
||||
return;
|
||||
}
|
||||
_emitEventIfScopeAlive(
|
||||
RuntimeEvent(
|
||||
type: RuntimeEventType.animationDone,
|
||||
target: target,
|
||||
handler: onComplete,
|
||||
),
|
||||
scope,
|
||||
targetEpoch: targetEpoch,
|
||||
scopeEpoch: scopeEpoch,
|
||||
);
|
||||
}
|
||||
|
||||
void _emitCommandCompletion(RuntimeCommand command, _CommandContext context) {
|
||||
final onComplete = _optionalString(
|
||||
command.payload['onComplete'],
|
||||
'onComplete',
|
||||
);
|
||||
if (onComplete == null) {
|
||||
return;
|
||||
}
|
||||
_emitEventIfScopeAlive(
|
||||
RuntimeEvent(
|
||||
type: RuntimeEventType.animationDone,
|
||||
target: command.target,
|
||||
handler: onComplete,
|
||||
),
|
||||
_completionScopeFor(command, context),
|
||||
scopeEpoch: context.scopeEpoch,
|
||||
);
|
||||
}
|
||||
|
||||
void _emitEventIfScopeAlive(
|
||||
RuntimeEvent event,
|
||||
String? scope, {
|
||||
int? targetEpoch,
|
||||
int? scopeEpoch,
|
||||
}) {
|
||||
if (!_scopeIsAlive(scope) || _disposed) {
|
||||
return;
|
||||
}
|
||||
_eventSink(
|
||||
event.withLifecycle(
|
||||
scope: scope,
|
||||
targetEpoch: targetEpoch,
|
||||
scopeEpoch: scopeEpoch,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Vector2 _requiredVector(RuntimeCommand command) {
|
||||
final x = _readDouble(command.payload['x']);
|
||||
final y = _readDouble(command.payload['y']);
|
||||
if (x == null || y == null) {
|
||||
throw FormatException('${command.type}.x/y are required numbers');
|
||||
}
|
||||
return Vector2(x, y);
|
||||
}
|
||||
|
||||
String _requiredTarget(RuntimeCommand command) {
|
||||
final target = command.target;
|
||||
if (target == null || target.isEmpty) {
|
||||
throw FormatException('${command.type}.target is required');
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
String _requiredText(RuntimeCommand command, String field) {
|
||||
return _optionalString(command.payload['text'], field)!;
|
||||
}
|
||||
|
||||
double _duration(RuntimeCommand command, {required double defaultValue}) {
|
||||
final duration = _readDouble(command.payload['duration']) ?? defaultValue;
|
||||
if (duration < 0) {
|
||||
throw FormatException('${command.type}.duration must be >= 0');
|
||||
}
|
||||
return duration;
|
||||
}
|
||||
|
||||
double _requiredDouble(Object? value, String field) {
|
||||
final result = _readDouble(value);
|
||||
if (result == null) {
|
||||
throw FormatException('$field must be a number');
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void _registerBgmChannel({required String channel, required String? scope}) {
|
||||
_unregisterBgmChannel(channel);
|
||||
_ownedBgmChannels.add(channel);
|
||||
if (scope == null) {
|
||||
return;
|
||||
}
|
||||
_bgmScopeByChannel[channel] = scope;
|
||||
_bgmChannelsByScope.putIfAbsent(scope, () => {}).add(channel);
|
||||
}
|
||||
|
||||
void _unregisterBgmChannel(String channel) {
|
||||
_ownedBgmChannels.remove(channel);
|
||||
final oldScope = _bgmScopeByChannel.remove(channel);
|
||||
if (oldScope == null) {
|
||||
return;
|
||||
}
|
||||
final channels = _bgmChannelsByScope[oldScope];
|
||||
channels?.remove(channel);
|
||||
if (channels != null && channels.isEmpty) {
|
||||
_bgmChannelsByScope.remove(oldScope);
|
||||
}
|
||||
}
|
||||
|
||||
String _audioChannel(RuntimeCommand command) {
|
||||
return _optionalString(command.payload['channel'], 'channel') ??
|
||||
RuntimeAudioChannel.defaultBgm;
|
||||
}
|
||||
|
||||
String _requiredResourceGroup(RuntimeCommand command) {
|
||||
return _optionalString(command.payload['group'], '${command.type}.group')!;
|
||||
}
|
||||
|
||||
void _validateCancelCommands(RuntimeCommand command) {
|
||||
final id = _optionalString(command.payload['id'], 'cancel_commands.id');
|
||||
final group = _optionalString(
|
||||
command.payload['group'],
|
||||
'cancel_commands.group',
|
||||
);
|
||||
final scope = _optionalString(
|
||||
command.payload['scope'],
|
||||
'cancel_commands.scope',
|
||||
);
|
||||
if (id == null && group == null && scope == null) {
|
||||
throw const FormatException(
|
||||
'cancel_commands requires id, group or scope',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _requiredAudioResource(RuntimeCommand command) {
|
||||
final asset = command.payload['asset'] ?? command.payload['name'];
|
||||
return _optionalString(asset, 'play_sound.asset/name')!;
|
||||
}
|
||||
|
||||
String _requiredSpineAnimation(RuntimeCommand command) {
|
||||
final value = _optionalString(
|
||||
command.payload['animation'],
|
||||
'play_spine_animation.animation',
|
||||
);
|
||||
if (value == null) {
|
||||
throw const FormatException(
|
||||
'play_spine_animation.animation must be a non-empty string',
|
||||
);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
bool? _optionalBool(Object? value, String field) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
if (value is bool) {
|
||||
return value;
|
||||
}
|
||||
throw FormatException('$field must be a boolean');
|
||||
}
|
||||
|
||||
double _optionalVolume(RuntimeCommand command) {
|
||||
final value = command.payload['volume'];
|
||||
if (value == null) {
|
||||
return 1;
|
||||
}
|
||||
return _requiredNormalizedDouble(value, 'play_sound.volume');
|
||||
}
|
||||
|
||||
double _requiredNormalizedDouble(Object? value, String field) {
|
||||
final result = _requiredDouble(value, field);
|
||||
if (result < 0 || result > 1) {
|
||||
throw FormatException('$field must be between 0 and 1');
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
String? _optionalString(Object? value, String field) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
if (value is String && value.isNotEmpty) {
|
||||
return value;
|
||||
}
|
||||
throw FormatException('$field must be a non-empty string');
|
||||
}
|
||||
|
||||
int? _optionalInt(Object? value, String field) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
if (value is num) {
|
||||
return value.toInt();
|
||||
}
|
||||
throw FormatException('$field must be an integer');
|
||||
}
|
||||
|
||||
double? _readDouble(Object? value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
if (value is num) {
|
||||
return value.toDouble();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user