feat(headless): complete deterministic bot command parity
This commit is contained in:
parent
7e8acb74dd
commit
38e83640d9
37 changed files with 2805 additions and 295 deletions
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue