acdream/src/AcDream.Runtime/Session/DirectGameRuntimeCommandAdapter.cs

1117 lines
37 KiB
C#

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;
/// <summary>
/// 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.
/// </summary>
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;
}
}
}