feat(headless): complete deterministic bot command parity

This commit is contained in:
Erik 2026-07-27 08:23:36 +02:00
parent 7e8acb74dd
commit 38e83640d9
37 changed files with 2805 additions and 295 deletions

View file

@ -1,4 +1,11 @@
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;
@ -20,7 +27,8 @@ public sealed class DirectGameRuntimeCommandAdapter
IRuntimeInventoryStateCommands,
IRuntimeSpellbookCommands,
IRuntimeCharacterCommands,
IRuntimeSocialCommands
IRuntimeSocialCommands,
IRuntimeInteractionTransport
{
private sealed class CommandRoute(
DirectGameRuntimeCommandAdapter owner,
@ -139,9 +147,13 @@ public sealed class DirectGameRuntimeCommandAdapter
session!.SendTell(command.TargetName, command.Text);
break;
default:
return EmitUnsupported(
RuntimeCommandDomain.Chat,
(int)command.Channel);
if (!TrySendChannel(session!, command.Channel, command.Text))
{
return EmitUnsupported(
RuntimeCommandDomain.Chat,
(int)command.Channel);
}
break;
}
_runtime.EventSink.EmitCommand(
@ -190,167 +202,829 @@ public sealed class DirectGameRuntimeCommandAdapter
public RuntimeCommandResult Execute(
RuntimeGenerationToken expectedGeneration,
RuntimeSelectionCommand command) =>
Unsupported(
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);
(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) =>
Unsupported(
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);
(int)command,
status);
}
public RuntimeCommandResult ExecuteAttack(
RuntimeGenerationToken expectedGeneration,
in RuntimeCombatAttackInput command) =>
Unsupported(
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,
(int)command.Command);
0x100 + (int)command.Command,
status);
}
public RuntimeCommandResult Execute(
RuntimeGenerationToken expectedGeneration,
in RuntimeMagicCommand command) =>
Unsupported(
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,
operation: 0,
(int)cast,
status,
command.SpellId);
}
public RuntimeCommandResult Execute(
RuntimeGenerationToken expectedGeneration,
RuntimeMovementCommand command) =>
Unsupported(
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);
(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) =>
Unsupported(
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) =>
Unsupported(
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);
operation: 1,
status);
}
public RuntimeCommandResult AddFavorite(
RuntimeGenerationToken expectedGeneration,
int tabIndex,
int position,
uint spellId) =>
Unsupported(
expectedGeneration,
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) =>
Unsupported(
expectedGeneration,
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) =>
Unsupported(
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);
operation: 2,
RuntimeCommandStatus.Accepted);
}
public RuntimeCommandResult ForgetSpell(
RuntimeGenerationToken expectedGeneration,
uint spellId) =>
Unsupported(
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) =>
Unsupported(
expectedGeneration,
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) =>
Unsupported(
expectedGeneration,
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);
operation: 5,
RuntimeCommandStatus.Accepted);
}
public RuntimeCommandResult Advance(
RuntimeGenerationToken expectedGeneration,
in RuntimeAdvancementCommand command) =>
Unsupported(
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) =>
Unsupported(
expectedGeneration,
RuntimeCommandDomain.Character,
operation: 4);
public RuntimeCommandResult Execute(
RuntimeGenerationToken expectedGeneration,
in RuntimeFriendCommand command) =>
Unsupported(
expectedGeneration,
RuntimeCommandDomain.Social,
(int)command.Kind,
command.CharacterId);
public RuntimeCommandResult Execute(
RuntimeGenerationToken expectedGeneration,
in RuntimeSquelchCommand command) =>
Unsupported(
expectedGeneration,
RuntimeCommandDomain.Social,
(int)command.Scope,
command.CharacterId);
private RuntimeCommandResult Unsupported(
RuntimeGenerationToken expectedGeneration,
RuntimeCommandDomain domain,
int operation,
uint primaryObjectId = 0u)
uint options)
{
RuntimeCommandStatus gate =
Validate(expectedGeneration, out _);
return gate == RuntimeCommandStatus.Accepted
? EmitUnsupported(
domain,
operation,
RuntimeCommandStatus.Unsupported,
primaryObjectId)
: Result(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(
@ -367,6 +1041,22 @@ public sealed class DirectGameRuntimeCommandAdapter
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)