using AcDream.Core.Chat; using AcDream.Core.Combat; using AcDream.Core.Items; using AcDream.Core.Net; using AcDream.Core.Net.Messages; using AcDream.Core.Selection; using AcDream.Core.Spells; using AcDream.Runtime.Entities; using AcDream.Runtime.Gameplay; namespace AcDream.Runtime.Session; /// /// Typed command surface for a presentation-free Runtime host. K1 supplies /// session, local chat, and recall operations; later Slice-K work extends the /// same adapter rather than introducing a second bot command model. /// public sealed class DirectGameRuntimeCommandAdapter : IGameRuntimeCommands, IRuntimeSessionCommands, IRuntimeSelectionCommands, IRuntimeCombatCommands, IRuntimeMagicCommands, IRuntimeMovementCommands, IRuntimeChatCommands, IRuntimePortalCommands, IRuntimeInventoryStateCommands, IRuntimeSpellbookCommands, IRuntimeCharacterCommands, IRuntimeSocialCommands, IRuntimeInteractionTransport { private sealed class CommandRoute( DirectGameRuntimeCommandAdapter owner, WorldSession session, RuntimeGenerationToken generation) : ILiveSessionCommandRouting { private bool _active; public void Activate() { if (_active) return; owner.Activate(session, generation, this); _active = true; } public void Dispose() { owner.Deactivate(this); _active = false; } } private readonly object _gate = new(); private readonly GameRuntime _runtime; private readonly IRuntimeSessionCommands _sessionCommands; private WorldSession? _session; private CommandRoute? _route; private RuntimeGenerationToken _routeGeneration; public DirectGameRuntimeCommandAdapter( GameRuntime runtime, IRuntimeSessionCommands sessionCommands) { _runtime = runtime ?? throw new ArgumentNullException(nameof(runtime)); _sessionCommands = sessionCommands ?? throw new ArgumentNullException(nameof(sessionCommands)); } public IRuntimeSessionCommands Session => this; public IRuntimeSelectionCommands Selection => this; public IRuntimeCombatCommands Combat => this; public IRuntimeMagicCommands Magic => this; public IRuntimeMovementCommands Movement => this; public IRuntimeChatCommands Chat => this; public IRuntimePortalCommands Portal => this; public IRuntimeInventoryStateCommands InventoryState => this; public IRuntimeSpellbookCommands Spellbook => this; public IRuntimeCharacterCommands Character => this; public IRuntimeSocialCommands Social => this; public ILiveSessionCommandRouting CreateRoute(WorldSession session) { ArgumentNullException.ThrowIfNull(session); return new CommandRoute(this, session, _runtime.Generation); } public RuntimeSessionStartResult Start( RuntimeGenerationToken expectedGeneration) { RuntimeLifecycleState previous = _runtime.Lifecycle.State; RuntimeSessionStartResult result = _sessionCommands.Start(expectedGeneration); _runtime.EventSink.EmitLifecycle( previous, _runtime.Lifecycle.State); return result; } public RuntimeSessionStartResult Reconnect( RuntimeGenerationToken expectedGeneration) { RuntimeLifecycleState previous = _runtime.Lifecycle.State; RuntimeSessionStartResult result = _sessionCommands.Reconnect(expectedGeneration); _runtime.EventSink.EmitLifecycle( previous, _runtime.Lifecycle.State); return result; } public RuntimeTeardownAcknowledgement Stop( RuntimeGenerationToken expectedGeneration) { RuntimeLifecycleState previous = _runtime.Lifecycle.State; RuntimeTeardownAcknowledgement result = _sessionCommands.Stop(expectedGeneration); _runtime.EventSink.EmitLifecycle( previous, _runtime.Lifecycle.State); return result; } public RuntimeCommandResult Execute( RuntimeGenerationToken expectedGeneration, in RuntimeChatCommand command) { RuntimeCommandStatus gate = Validate(expectedGeneration, out WorldSession? session); if (gate != RuntimeCommandStatus.Accepted) return Result(gate); if (string.IsNullOrWhiteSpace(command.Text)) return EmitUnsupported( RuntimeCommandDomain.Chat, (int)command.Channel, RuntimeCommandStatus.Rejected); switch (command.Channel) { case RuntimeChatChannel.Say: session!.SendTalk(command.Text); break; case RuntimeChatChannel.Tell when !string.IsNullOrWhiteSpace(command.TargetName): session!.SendTell(command.TargetName, command.Text); break; default: if (!TrySendChannel(session!, command.Channel, command.Text)) { return EmitUnsupported( RuntimeCommandDomain.Chat, (int)command.Channel); } break; } _runtime.EventSink.EmitCommand( RuntimeCommandDomain.Chat, (int)command.Channel, RuntimeCommandStatus.Accepted, text: command.Text); return Result(RuntimeCommandStatus.Accepted); } public RuntimeCommandResult Execute( RuntimeGenerationToken expectedGeneration, RuntimePortalCommand command) { RuntimeCommandStatus gate = Validate(expectedGeneration, out WorldSession? session); if (gate != RuntimeCommandStatus.Accepted) return Result(gate); switch (command) { case RuntimePortalCommand.RecallLifestone: session!.SendTeleportToLifestone(); break; case RuntimePortalCommand.RecallMarketplace: session!.SendTeleportToMarketplace(); break; case RuntimePortalCommand.RecallHouse: session!.SendTeleportToHouse(); break; case RuntimePortalCommand.RecallMansion: session!.SendTeleportToMansion(); break; default: return EmitUnsupported( RuntimeCommandDomain.Portal, (int)command); } _runtime.EventSink.EmitCommand( RuntimeCommandDomain.Portal, (int)command, RuntimeCommandStatus.Accepted); return Result(RuntimeCommandStatus.Accepted); } public RuntimeCommandResult Execute( RuntimeGenerationToken expectedGeneration, RuntimeSelectionCommand command) { RuntimeCommandStatus gate = Validate(expectedGeneration, out WorldSession? session); if (gate != RuntimeCommandStatus.Accepted) return Result(gate); RuntimeCommandStatus status = RuntimeCommandStatus.Accepted; SelectionState selection = _runtime.ActionOwner.Selection; switch (command) { case RuntimeSelectionCommand.SelectClosestHostile: { uint? closest = RuntimeHostileTargetQuery.FindClosest(_runtime); if (closest is { } objectId) { selection.Select( objectId, SelectionChangeSource.Keyboard); } else { selection.Clear( SelectionChangeSource.Keyboard); } break; } case RuntimeSelectionCommand.SelectPrevious: selection.SelectPrevious(); break; case RuntimeSelectionCommand.ExamineSelected: if (selection.SelectedObjectId is { } appraisalId) { _runtime.ActionOwner.Transactions.TryRequestAppraisal( appraisalId, session!.SendAppraise); } else { _runtime.ActionOwner.Interaction.EnterExamine(); } break; case RuntimeSelectionCommand.UseSelected: status = UseSelected(session!); break; case RuntimeSelectionCommand.PickUpSelected: status = PickUpSelected(); break; default: status = RuntimeCommandStatus.Unsupported; break; } uint selected = selection.SelectedObjectId ?? 0u; return EmitResult( RuntimeCommandDomain.Selection, (int)command, status, selected); } public RuntimeCommandResult SelectObject( RuntimeGenerationToken expectedGeneration, uint objectId) { RuntimeCommandStatus gate = Validate(expectedGeneration, out _); if (gate != RuntimeCommandStatus.Accepted) return Result(gate); RuntimeCommandStatus status = objectId != 0u && _runtime.InventoryOwner.Objects.Get(objectId) is not null ? RuntimeCommandStatus.Accepted : RuntimeCommandStatus.Rejected; if (status == RuntimeCommandStatus.Accepted) { _runtime.ActionOwner.Selection.Select( objectId, SelectionChangeSource.Plugin); } return EmitResult( RuntimeCommandDomain.Selection, operation: 0x100, status, objectId); } public RuntimeCommandResult Clear( RuntimeGenerationToken expectedGeneration) { RuntimeCommandStatus gate = Validate(expectedGeneration, out _); if (gate != RuntimeCommandStatus.Accepted) return Result(gate); _runtime.ActionOwner.Selection.Clear( SelectionChangeSource.Plugin); return EmitResult( RuntimeCommandDomain.Selection, operation: 0x101, RuntimeCommandStatus.Accepted); } public RuntimeCommandResult Execute( RuntimeGenerationToken expectedGeneration, RuntimeCombatCommand command) { RuntimeCommandStatus gate = Validate(expectedGeneration, out _); if (gate != RuntimeCommandStatus.Accepted) return Result(gate); RuntimeCommandStatus status; if (command == RuntimeCombatCommand.ToggleMode) { RuntimeCombatModeRequestResult result = _runtime.ActionOwner.CombatMode.Toggle(); status = result.Status switch { RuntimeCombatModeRequestStatus.Sent => RuntimeCommandStatus.Accepted, RuntimeCombatModeRequestStatus.Inactive => RuntimeCommandStatus.Inactive, _ => RuntimeCommandStatus.Rejected, }; } else { status = RuntimeCommandStatus.Unsupported; } return EmitResult( RuntimeCommandDomain.Combat, (int)command, status); } public RuntimeCommandResult ExecuteAttack( RuntimeGenerationToken expectedGeneration, in RuntimeCombatAttackInput command) { RuntimeCommandStatus gate = Validate(expectedGeneration, out _); if (gate != RuntimeCommandStatus.Accepted) return Result(gate); RuntimeCommandStatus status = _runtime.ActionOwner.CombatAttack.HandleCommand(command) ? RuntimeCommandStatus.Accepted : RuntimeCommandStatus.Unsupported; return EmitResult( RuntimeCommandDomain.Combat, 0x100 + (int)command.Command, status); } public RuntimeCommandResult Execute( RuntimeGenerationToken expectedGeneration, in RuntimeMagicCommand command) { RuntimeCommandStatus gate = Validate(expectedGeneration, out _); if (gate != RuntimeCommandStatus.Accepted) return Result(gate); CastRequestResult cast = _runtime.ActionOwner.SpellCast.Cast(command.SpellId); RuntimeCommandStatus status = cast == CastRequestResult.Sent ? RuntimeCommandStatus.Accepted : RuntimeCommandStatus.Rejected; return EmitResult( RuntimeCommandDomain.Magic, (int)cast, status, command.SpellId); } public RuntimeCommandResult Execute( RuntimeGenerationToken expectedGeneration, RuntimeMovementCommand command) { RuntimeCommandStatus gate = Validate(expectedGeneration, out _); if (gate != RuntimeCommandStatus.Accepted) return Result(gate); RuntimeCommandStatus status = _runtime.MovementOwner.Execute(command) ? RuntimeCommandStatus.Accepted : RuntimeCommandStatus.Unsupported; return EmitResult( RuntimeCommandDomain.Movement, (int)command, status); } public RuntimeCommandResult SetIntent( RuntimeGenerationToken expectedGeneration, in MovementInput input) { RuntimeCommandStatus gate = Validate(expectedGeneration, out _); if (gate != RuntimeCommandStatus.Accepted) return Result(gate); _runtime.MovementOwner.SetCommandInput(input); return EmitResult( RuntimeCommandDomain.Movement, operation: 0x100, RuntimeCommandStatus.Accepted); } public RuntimeCommandResult ClearIntent( RuntimeGenerationToken expectedGeneration) { RuntimeCommandStatus gate = Validate(expectedGeneration, out _); if (gate != RuntimeCommandStatus.Accepted) return Result(gate); _runtime.MovementOwner.ClearCommandInput(); return EmitResult( RuntimeCommandDomain.Movement, operation: 0x101, RuntimeCommandStatus.Accepted); } public RuntimeCommandResult AddShortcut( RuntimeGenerationToken expectedGeneration, in RuntimeShortcutCommand command) { RuntimeCommandStatus gate = Validate(expectedGeneration, out WorldSession? session); if (gate != RuntimeCommandStatus.Accepted) return Result(gate); var entry = new ShortcutEntry( command.Index, command.ObjectId, command.SpellId); RuntimeCommandStatus status = _runtime.InventoryOwner.TryAddShortcut( entry, () => session!.SendAddShortcut(entry)) ? RuntimeCommandStatus.Accepted : RuntimeCommandStatus.Rejected; return EmitResult( RuntimeCommandDomain.InventoryState, operation: 0, status, command.ObjectId); } public RuntimeCommandResult RemoveShortcut( RuntimeGenerationToken expectedGeneration, int index) { RuntimeCommandStatus gate = Validate(expectedGeneration, out WorldSession? session); if (gate != RuntimeCommandStatus.Accepted) return Result(gate); RuntimeCommandStatus status = _runtime.InventoryOwner.TryRemoveShortcut( index, () => session!.SendRemoveShortcut((uint)index)) ? RuntimeCommandStatus.Accepted : RuntimeCommandStatus.Rejected; return EmitResult( RuntimeCommandDomain.InventoryState, operation: 1, status); } public RuntimeCommandResult AddFavorite( RuntimeGenerationToken expectedGeneration, int tabIndex, int position, uint spellId) { RuntimeCommandStatus gate = Validate(expectedGeneration, out WorldSession? session); if (gate != RuntimeCommandStatus.Accepted) return Result(gate); RuntimeCommandStatus status = _runtime.CharacterOwner.TryAddFavorite( tabIndex, position, spellId, () => session!.SendAddSpellFavorite( spellId, position, tabIndex)) ? RuntimeCommandStatus.Accepted : RuntimeCommandStatus.Rejected; return EmitResult( RuntimeCommandDomain.Spellbook, operation: 0, status, spellId); } public RuntimeCommandResult RemoveFavorite( RuntimeGenerationToken expectedGeneration, int tabIndex, uint spellId) { RuntimeCommandStatus gate = Validate(expectedGeneration, out WorldSession? session); if (gate != RuntimeCommandStatus.Accepted) return Result(gate); RuntimeCommandStatus status = _runtime.CharacterOwner.TryRemoveFavorite( tabIndex, spellId, () => session!.SendRemoveSpellFavorite( spellId, tabIndex)) ? RuntimeCommandStatus.Accepted : RuntimeCommandStatus.Rejected; return EmitResult( RuntimeCommandDomain.Spellbook, operation: 1, status, spellId); } public RuntimeCommandResult SetFilter( RuntimeGenerationToken expectedGeneration, uint filters) { RuntimeCommandStatus gate = Validate(expectedGeneration, out WorldSession? session); if (gate != RuntimeCommandStatus.Accepted) return Result(gate); _runtime.CharacterOwner.SetSpellbookFilter( filters, () => session!.SendSpellbookFilter(filters)); return EmitResult( RuntimeCommandDomain.Spellbook, operation: 2, RuntimeCommandStatus.Accepted); } public RuntimeCommandResult ForgetSpell( RuntimeGenerationToken expectedGeneration, uint spellId) { RuntimeCommandStatus gate = Validate(expectedGeneration, out WorldSession? session); if (gate != RuntimeCommandStatus.Accepted) return Result(gate); RuntimeCommandStatus status = spellId == 0u ? RuntimeCommandStatus.Rejected : RuntimeCommandStatus.Accepted; if (status == RuntimeCommandStatus.Accepted) session!.SendRemoveSpell(spellId); return EmitResult( RuntimeCommandDomain.Spellbook, operation: 3, status, spellId); } public RuntimeCommandResult SetDesiredComponent( RuntimeGenerationToken expectedGeneration, uint componentId, uint amount) { RuntimeCommandStatus gate = Validate(expectedGeneration, out WorldSession? session); if (gate != RuntimeCommandStatus.Accepted) return Result(gate); RuntimeCommandStatus status = _runtime.CharacterOwner.TrySetDesiredComponent( componentId, amount, () => session!.SendSetDesiredComponentLevel( componentId, amount)) ? RuntimeCommandStatus.Accepted : RuntimeCommandStatus.Rejected; return EmitResult( RuntimeCommandDomain.Spellbook, operation: 4, status, componentId); } public RuntimeCommandResult ClearDesiredComponents( RuntimeGenerationToken expectedGeneration) { RuntimeCommandStatus gate = Validate(expectedGeneration, out WorldSession? session); if (gate != RuntimeCommandStatus.Accepted) return Result(gate); _runtime.CharacterOwner.ClearDesiredComponents( session!.SendClearDesiredComponents); return EmitResult( RuntimeCommandDomain.Spellbook, operation: 5, RuntimeCommandStatus.Accepted); } public RuntimeCommandResult Advance( RuntimeGenerationToken expectedGeneration, in RuntimeAdvancementCommand command) { RuntimeCommandStatus gate = Validate(expectedGeneration, out WorldSession? session); if (gate != RuntimeCommandStatus.Accepted) return Result(gate); RuntimeCommandStatus status = RuntimeCommandStatus.Accepted; if (command.StatId == 0u || command.Cost == 0u) { status = RuntimeCommandStatus.Rejected; } else { switch (command.Kind) { case RuntimeAdvancementKind.Attribute: session!.SendRaiseAttribute( command.StatId, command.Cost); break; case RuntimeAdvancementKind.Vital: session!.SendRaiseVital( command.StatId, command.Cost); break; case RuntimeAdvancementKind.Skill: session!.SendRaiseSkill( command.StatId, command.Cost); break; case RuntimeAdvancementKind.TrainSkill when command.Cost <= uint.MaxValue: session!.SendTrainSkill( command.StatId, (uint)command.Cost); break; default: status = RuntimeCommandStatus.Rejected; break; } } return EmitResult( RuntimeCommandDomain.Character, (int)command.Kind, status, command.StatId); } public RuntimeCommandResult SetOptions1( RuntimeGenerationToken expectedGeneration, uint options) { RuntimeCommandStatus gate = Validate(expectedGeneration, out WorldSession? session); if (gate != RuntimeCommandStatus.Accepted) return Result(gate); session!.SendSetCharacterOptions(options); return EmitResult( RuntimeCommandDomain.Character, operation: 4, RuntimeCommandStatus.Accepted); } public RuntimeCommandResult Execute( RuntimeGenerationToken expectedGeneration, in RuntimeFriendCommand command) { RuntimeCommandStatus gate = Validate(expectedGeneration, out WorldSession? session); if (gate != RuntimeCommandStatus.Accepted) return Result(gate); RuntimeCommandStatus status = RuntimeCommandStatus.Accepted; switch (command.Kind) { case RuntimeFriendCommandKind.Add when !string.IsNullOrWhiteSpace(command.Name): session!.SendAddFriend(command.Name); break; case RuntimeFriendCommandKind.Remove when command.CharacterId != 0u: session!.SendRemoveFriend(command.CharacterId); break; case RuntimeFriendCommandKind.Clear: session!.SendClearFriends(); break; case RuntimeFriendCommandKind.RequestLegacyList: session!.SendLegacyFriendsListRequest(); break; default: status = RuntimeCommandStatus.Rejected; break; } return EmitResult( RuntimeCommandDomain.Social, (int)command.Kind, status, command.CharacterId, command.Name); } public RuntimeCommandResult Execute( RuntimeGenerationToken expectedGeneration, in RuntimeSquelchCommand command) { RuntimeCommandStatus gate = Validate(expectedGeneration, out WorldSession? session); if (gate != RuntimeCommandStatus.Accepted) return Result(gate); RuntimeCommandStatus status = RuntimeCommandStatus.Accepted; switch (command.Scope) { case RuntimeSquelchScope.Character when command.CharacterId != 0u && !string.IsNullOrWhiteSpace(command.Name): session!.SendModifyCharacterSquelch( command.Add, command.CharacterId, command.Name, command.MessageType); break; case RuntimeSquelchScope.Account when !string.IsNullOrWhiteSpace(command.Name): session!.SendModifyAccountSquelch( command.Add, command.Name); break; case RuntimeSquelchScope.Global: session!.SendModifyGlobalSquelch( command.Add, command.MessageType); break; default: status = RuntimeCommandStatus.Rejected; break; } return EmitResult( RuntimeCommandDomain.Social, 4 + (int)command.Scope, status, command.CharacterId, command.Name); } bool IRuntimeInteractionTransport.IsInWorld { get { lock (_gate) { return _route is not null && _session is not null && _routeGeneration == _runtime.Generation && _runtime.Session.IsInWorld; } } } bool IRuntimeInteractionTransport.TrySendUse( uint serverGuid, out uint sequence) { lock (_gate) { if (_route is null || _session is null || _routeGeneration != _runtime.Generation || !_runtime.Session.IsInWorld) { sequence = 0u; return false; } sequence = _session.NextGameActionSequence(); _session.SendGameAction( InteractRequests.BuildUse(sequence, serverGuid)); return true; } } bool IRuntimeInteractionTransport.TrySendPickup( uint itemGuid, uint destinationContainerId, int placement, out uint sequence) { lock (_gate) { if (_route is null || _session is null || _routeGeneration != _runtime.Generation || !_runtime.Session.IsInWorld) { sequence = 0u; return false; } sequence = _session.NextGameActionSequence(); _session.SendGameAction( InteractRequests.BuildPickUp( sequence, itemGuid, destinationContainerId, placement)); return true; } } private RuntimeCommandStatus UseSelected(WorldSession session) { if (_runtime.ActionOwner.Selection.SelectedObjectId is not uint selected) { _runtime.ActionOwner.Interaction.EnterUse(); return RuntimeCommandStatus.Accepted; } if (_runtime.InventoryOwner.Objects.Get(selected) is not { } item) { return RuntimeCommandStatus.Rejected; } long nowMs = checked((long)Math.Floor( _runtime.Clock.SimulationTimeSeconds * 1000d)); if (!_runtime.ActionOwner.Transactions .TryConsumeUseThrottle(nowMs)) { return RuntimeCommandStatus.Accepted; } uint useability = item.Useability ?? ItemUseability.Undef; if (ItemUseability.IsTargeted(useability)) { uint targetId; if (ItemUseability.AllowsSelfTarget(useability)) targetId = _runtime.PlayerIdentity.ServerGuid; else if (ItemUseability.AllowsObjectSelfTarget(useability)) targetId = selected; else { _runtime.ActionOwner.Interaction .EnterUseItemOnTarget(selected); return RuntimeCommandStatus.Accepted; } bool sent = _runtime.ActionOwner.Transactions .TryDispatchTargetedUse( selected, targetId, session.SendUseWithTarget, incrementBusy: true); return sent ? RuntimeCommandStatus.Accepted : RuntimeCommandStatus.Rejected; } bool ownedByPlayer = IsOwnedByPlayer(item); ItemUseRequestReservation reservation; try { reservation = _runtime.ActionOwner.Transactions .BeginUseRequestReservation(); } catch (InvalidOperationException) { return RuntimeCommandStatus.Rejected; } RuntimeInteractionDispatchResult dispatched = _runtime.ActionOwner.Transactions.TryDispatchUse( selected, ownedByPlayer, ownedByPlayer || ItemUseability.IsUseable(useability), reservation, this, out _); return dispatched == RuntimeInteractionDispatchResult.Dispatched ? RuntimeCommandStatus.Accepted : RuntimeCommandStatus.Rejected; } private RuntimeCommandStatus PickUpSelected() { if (_runtime.ActionOwner.Selection.SelectedObjectId is not uint selected) { return RuntimeCommandStatus.Accepted; } ClientObject? item = _runtime.InventoryOwner.Objects.Get(selected); if (item is null || (item.Type & ItemType.Creature) != 0) { return RuntimeCommandStatus.Rejected; } uint playerGuid = _runtime.PlayerIdentity.ServerGuid; if (playerGuid == 0u) return RuntimeCommandStatus.Inactive; uint localEntityId = _runtime.EntityObjects.Entities.TryGetActive( selected, out RuntimeEntityRecord record) ? record.LocalEntityId ?? 0u : 0u; var pickup = new RuntimePendingPickup( Token: 0u, selected, localEntityId, playerGuid, Placement: 0, PendingPlacementToken: 0u, ApproachToken: default); return _runtime.ActionOwner.Transactions.TryDispatchPickup( pickup, this, out _) ? RuntimeCommandStatus.Accepted : RuntimeCommandStatus.Rejected; } private bool IsOwnedByPlayer(ClientObject item) { uint playerGuid = _runtime.PlayerIdentity.ServerGuid; if (playerGuid == 0u) return false; ClientObject current = item; for (int depth = 0; depth < 16; depth++) { if (current.ObjectId == playerGuid || current.WielderId == playerGuid || current.ContainerId == playerGuid) { return true; } if (current.ContainerId == 0u || _runtime.InventoryOwner.Objects.Get( current.ContainerId) is not { } parent) { return false; } current = parent; } return false; } private bool TrySendChannel( WorldSession session, RuntimeChatChannel channel, string text) { if (TryMapTurbine( channel, out ChatChannelKindLite turbineKind, out TurbineChat.ChatType chatType)) { TurbineChatState state = _runtime.CommunicationOwner.TurbineChat; uint roomId = state.RoomFor(turbineKind); if (state.Enabled && roomId != 0u) { session.SendTurbineChatTo( roomId, (uint)chatType, (uint)TurbineChat.DispatchType.SendToRoomById, _runtime.PlayerIdentity.ServerGuid, text, state.NextContextId()); return true; } } uint? legacyChannel = channel switch { RuntimeChatChannel.Fellowship => 0x00000800u, RuntimeChatChannel.Allegiance => 0x02000000u, RuntimeChatChannel.Vassals => 0x00001000u, RuntimeChatChannel.Patron => 0x00002000u, RuntimeChatChannel.Monarch => 0x00004000u, RuntimeChatChannel.CoVassals => 0x01000000u, _ => null, }; if (legacyChannel is not { } channelId) return false; session.SendChannel(channelId, text); return true; } private static bool TryMapTurbine( RuntimeChatChannel channel, out ChatChannelKindLite kind, out TurbineChat.ChatType chatType) { (kind, chatType) = channel switch { RuntimeChatChannel.Allegiance => ( ChatChannelKindLite.Allegiance, TurbineChat.ChatType.Allegiance), RuntimeChatChannel.General => ( ChatChannelKindLite.General, TurbineChat.ChatType.General), RuntimeChatChannel.Trade => ( ChatChannelKindLite.Trade, TurbineChat.ChatType.Trade), RuntimeChatChannel.LookingForGroup => ( ChatChannelKindLite.Lfg, TurbineChat.ChatType.Lfg), RuntimeChatChannel.Roleplay => ( ChatChannelKindLite.Roleplay, TurbineChat.ChatType.Roleplay), RuntimeChatChannel.Society => ( ChatChannelKindLite.Society, TurbineChat.ChatType.Society), RuntimeChatChannel.Olthoi => ( ChatChannelKindLite.Olthoi, TurbineChat.ChatType.Olthoi), _ => default, }; return channel is RuntimeChatChannel.Allegiance or RuntimeChatChannel.General or RuntimeChatChannel.Trade or RuntimeChatChannel.LookingForGroup or RuntimeChatChannel.Roleplay or RuntimeChatChannel.Society or RuntimeChatChannel.Olthoi; } private RuntimeCommandResult EmitUnsupported( RuntimeCommandDomain domain, int operation, RuntimeCommandStatus status = RuntimeCommandStatus.Unsupported, uint primaryObjectId = 0u) { _runtime.EventSink.EmitCommand( domain, operation, status, primaryObjectId); return Result(status, primaryObjectId); } private RuntimeCommandResult EmitResult( RuntimeCommandDomain domain, int operation, RuntimeCommandStatus status, uint primaryObjectId = 0u, string? text = null) { _runtime.EventSink.EmitCommand( domain, operation, status, primaryObjectId, text); return Result(status, primaryObjectId); } private RuntimeCommandStatus Validate( RuntimeGenerationToken expectedGeneration, out WorldSession? session) { lock (_gate) { if (expectedGeneration != _runtime.Generation || (_route is not null && _routeGeneration != expectedGeneration)) { session = null; return RuntimeCommandStatus.StaleGeneration; } session = _session; return _route is not null && session is not null && _runtime.Session.IsInWorld ? RuntimeCommandStatus.Accepted : RuntimeCommandStatus.Inactive; } } private RuntimeCommandResult Result( RuntimeCommandStatus status, uint objectId = 0u) => new(status, _runtime.Generation, objectId); private void Activate( WorldSession session, RuntimeGenerationToken generation, CommandRoute route) { lock (_gate) { if (_route is not null) { throw new InvalidOperationException( "A direct Runtime command route is already active."); } _session = session; _routeGeneration = generation; _route = route; } } private void Deactivate(CommandRoute route) { lock (_gate) { if (!ReferenceEquals(_route, route)) return; _route = null; _session = null; _routeGeneration = default; } } }