ace/Source/ACE.Server/Physics/Animation/MotionInterp.cs
Mag-nus 1b3002e617
Update MotionInterp.cs (#4473)
Re-apply bug fix
2026-08-01 12:30:18 +00:00

835 lines
29 KiB
C#

using System;
using System.Collections.Generic;
using System.Numerics;
using ACE.Entity.Enum;
using ACE.Server.Physics.Common;
namespace ACE.Server.Physics.Animation
{
/// <summary>
/// Motion network command interpretor
/// </summary>
public class MotionInterp
{
public bool Initted;
public WeenieObject WeenieObj;
public PhysicsObj PhysicsObj;
public RawMotionState RawState;
public InterpretedMotionState InterpretedState;
public float CurrentSpeedFactor;
public bool StandingLongJump;
public float JumpExtent;
public int ServerActionStamp;
public float MyRunRate;
public LinkedList<MotionNode> PendingMotions;
public const float BackwardsFactor = 6.4999998e-1f;
public const float MaxSidestepAnimRate = 3.0f;
public const float RunAnimSpeed = 4.0f;
public const float RunTurnFactor = 1.5f;
public const float SidestepAnimSpeed = 1.25f;
public const float SidestepFactor = 0.5f;
public const float WalkAnimSpeed = 3.1199999f;
public MotionInterp() { }
public MotionInterp(PhysicsObj obj, WeenieObject wobj)
{
CurrentSpeedFactor = 1.0f;
MyRunRate = 1.0f;
SetPhysicsObject(obj);
SetWeenieObject(wobj);
}
public static MotionInterp Create(PhysicsObj obj, WeenieObject wobj)
{
var motionInterp = new MotionInterp(obj, wobj);
return motionInterp;
}
public WeenieError DoInterpretedMotion(uint motion, MovementParameters movementParams)
{
if (PhysicsObj == null) return WeenieError.NoPhysicsObject;
var result = WeenieError.None;
if (contact_allows_move(motion))
{
if (StandingLongJump && (motion == (uint)MotionCommand.WalkForward || motion == (uint)MotionCommand.RunForward || motion == (uint)MotionCommand.SideStepRight))
{
if (movementParams.ModifyInterpretedState)
InterpretedState.ApplyMotion(motion, movementParams);
}
else
{
if (motion == (uint)MotionCommand.Dead)
PhysicsObj.RemoveLinkAnimations();
result = PhysicsObj.DoInterpretedMotion(motion, movementParams);
if (result == WeenieError.None)
{
var jump_error_code = WeenieError.None;
if (movementParams.DisableJumpDuringLink)
jump_error_code = WeenieError.YouCantJumpFromThisPosition;
else
{
jump_error_code = motion_allows_jump(motion);
if (jump_error_code == WeenieError.None && (motion & (uint)CommandMask.Action) == 0)
jump_error_code = motion_allows_jump(InterpretedState.ForwardCommand);
}
add_to_queue(movementParams.ContextID, motion, jump_error_code);
if (movementParams.ModifyInterpretedState)
InterpretedState.ApplyMotion(motion, movementParams);
}
}
}
else
{
if ((motion & (uint)CommandMask.Action) != 0)
result = WeenieError.YouCantJumpWhileInTheAir;
else
{
if (movementParams.ModifyInterpretedState)
InterpretedState.ApplyMotion(motion, movementParams);
result = WeenieError.None;
}
}
if (PhysicsObj.CurCell == null)
PhysicsObj.RemoveLinkAnimations();
return result;
}
public WeenieError DoMotion(uint motion, MovementParameters movementParams)
{
if (PhysicsObj == null)
return WeenieError.NoPhysicsObject;
// movementparams ref?
var currentParams = new MovementParameters();
currentParams.CopySome(movementParams);
var currentMotion = motion;
if (movementParams.CancelMoveTo)
PhysicsObj.cancel_moveto();
if (movementParams.SetHoldKey)
SetHoldKey(movementParams.HoldKeyToApply, movementParams.CancelMoveTo);
adjust_motion(ref currentMotion, ref currentParams.Speed, movementParams.HoldKeyToApply);
if (InterpretedState.CurrentStyle != (uint)MotionCommand.NonCombat)
{
switch (motion)
{
case (uint)MotionCommand.Crouch:
return WeenieError.CantCrouchInCombat;
case (uint)MotionCommand.Sitting:
return WeenieError.CantSitInCombat;
case (uint)MotionCommand.Sleeping:
return WeenieError.CantLieDownInCombat;
}
if ((motion & (uint)CommandMask.ChatEmote) != 0)
return WeenieError.CantChatEmoteInCombat;
}
if ((motion & (uint)CommandMask.Action) != 0)
{
if (InterpretedState.GetNumActions() >= 6)
return WeenieError.TooManyActions;
}
var result = DoInterpretedMotion(currentMotion, currentParams);
if (result == WeenieError.None && movementParams.ModifyRawState)
RawState.ApplyMotion(motion, movementParams);
return result;
}
public void HandleExitWorld()
{
foreach (var pendingMotion in PendingMotions)
{
if (PhysicsObj != null && (pendingMotion.Motion & (uint)CommandMask.Action) != 0)
{
PhysicsObj.unstick_from_object();
InterpretedState.RemoveAction();
RawState.RemoveAction();
}
}
PendingMotions.Clear();
if (PhysicsObj != null) PhysicsObj.IsAnimating = false;
}
public void HitGround()
{
if (PhysicsObj == null) return;
if (WeenieObj != null && !WeenieObj.IsCreature) return;
if (!PhysicsObj.State.HasFlag(PhysicsState.Gravity)) return;
PhysicsObj.RemoveLinkAnimations();
apply_current_movement(false, true);
}
public long InqStyle()
{
return InterpretedState.CurrentStyle;
}
public void LeaveGround()
{
if (PhysicsObj == null) return;
if (WeenieObj != null && !WeenieObj.IsCreature) return;
if (!PhysicsObj.State.HasFlag(PhysicsState.Gravity)) return;
var velocity = get_leave_ground_velocity();
PhysicsObj.set_local_velocity(velocity, true);
StandingLongJump = false;
JumpExtent = 0;
PhysicsObj.RemoveLinkAnimations();
apply_current_movement(false, true);
}
public void MotionDone(bool success)
{
if (PhysicsObj == null) return;
var motionData = PendingMotions.First;
// null or != last in list?
if (motionData != null)
{
var pendingMotion = motionData.Value;
if ((pendingMotion.Motion & (uint)CommandMask.Action) != 0)
{
PhysicsObj.unstick_from_object();
InterpretedState.RemoveAction();
RawState.RemoveAction();
}
motionData = PendingMotions.First;
if (motionData != null)
{
PendingMotions.Remove(motionData);
PhysicsObj.IsAnimating = PendingMotions.Count > 0;
}
}
}
public WeenieError PerformMovement(MovementStruct mvs)
{
var result = WeenieError.None;
switch (mvs.Type)
{
case MovementType.RawCommand:
result = DoMotion(mvs.Motion, mvs.Params);
break;
case MovementType.InterpretedCommand:
result = DoInterpretedMotion(mvs.Motion, mvs.Params);
break;
case MovementType.StopRawCommand:
result = StopMotion(mvs.Motion, mvs.Params);
break;
case MovementType.StopInterpretedCommand:
result = StopInterpretedMotion(mvs.Motion, mvs.Params);
break;
case MovementType.StopCompletely:
result = StopCompletely();
break;
default:
return WeenieError.GeneralMovementFailure;
}
PhysicsObj.CheckForCompletedMotions();
return result;
}
public void ReportExhaustion()
{
if (PhysicsObj == null || !Initted) return;
if (WeenieObj == null || WeenieObj.IsCreature && PhysicsObj.movement_is_autonomous())
apply_raw_movement(false, false);
else
apply_interpreted_movement(false, false);
}
public void SetHoldKey(HoldKey key, bool cancelMoveTo)
{
if (key == RawState.CurrentHoldKey) return;
switch (key)
{
case HoldKey.None:
if (RawState.CurrentHoldKey == HoldKey.Run)
{
RawState.CurrentHoldKey = HoldKey.None;
apply_current_movement(cancelMoveTo, true);
}
break;
}
}
public void SetPhysicsObject(PhysicsObj obj)
{
PhysicsObj = obj;
apply_current_movement(true, true);
}
public void SetWeenieObject(WeenieObject wobj)
{
WeenieObj = wobj;
apply_current_movement(true, true);
}
public WeenieError StopCompletely()
{
if (PhysicsObj == null) return WeenieError.NoPhysicsObject;
PhysicsObj.cancel_moveto();
var jump = motion_allows_jump(InterpretedState.ForwardCommand);
RawState.ForwardCommand = (uint)MotionCommand.Ready;
RawState.ForwardSpeed = 1.0f;
RawState.SideStepCommand = 0;
RawState.TurnCommand = 0;
InterpretedState.ForwardCommand = (uint)MotionCommand.Ready;
InterpretedState.ForwardSpeed = 1.0f;
InterpretedState.SideStepCommand = 0;
InterpretedState.TurnCommand = 0;
PhysicsObj.StopCompletely_Internal();
add_to_queue(0, (uint)MotionCommand.Ready, jump);
if (PhysicsObj.CurCell == null)
PhysicsObj.RemoveLinkAnimations();
return WeenieError.None;
}
public WeenieError StopInterpretedMotion(uint motion, MovementParameters movementParams)
{
if (PhysicsObj == null) return WeenieError.NoPhysicsObject;
var result = WeenieError.None;
if (contact_allows_move(motion))
{
if (StandingLongJump && (motion == (uint)MotionCommand.WalkForward || motion == (uint)MotionCommand.RunForward || motion == (uint)MotionCommand.SideStepRight))
{
if (movementParams.ModifyInterpretedState)
InterpretedState.RemoveMotion(motion);
}
else
{
result = PhysicsObj.StopInterpretedMotion(motion, movementParams);
if (result == WeenieError.None)
{
add_to_queue(movementParams.ContextID, (uint)MotionCommand.Ready, WeenieError.None);
if (movementParams.ModifyInterpretedState)
InterpretedState.RemoveMotion(motion);
}
}
}
else
{
if (movementParams.ModifyInterpretedState)
InterpretedState.RemoveMotion(motion);
}
if (PhysicsObj.CurCell == null)
PhysicsObj.RemoveLinkAnimations();
return result;
}
public WeenieError StopMotion(uint motion, MovementParameters movementParams)
{
if (PhysicsObj == null) return WeenieError.NoPhysicsObject;
if (movementParams.CancelMoveTo)
PhysicsObj.cancel_moveto();
var currentMotion = motion;
var currentParams = new MovementParameters();
currentParams.CopySome(movementParams);
adjust_motion(ref currentMotion, ref currentParams.Speed, movementParams.HoldKeyToApply);
var result = StopInterpretedMotion(currentMotion, currentParams);
if (result == WeenieError.None && movementParams.ModifyRawState)
RawState.RemoveMotion(motion);
return result;
}
public void add_to_queue(int contextID, uint motion, WeenieError jumpErrorCode)
{
PendingMotions.AddLast(new MotionNode(contextID, motion, jumpErrorCode));
PhysicsObj.IsAnimating = true;
}
public void adjust_motion(ref uint motion, ref float speed, HoldKey holdKey)
{
if (WeenieObj != null && !WeenieObj.IsCreature)
return;
switch (motion)
{
case (uint)MotionCommand.RunForward:
return;
case (uint)MotionCommand.WalkBackwards:
motion = (uint)MotionCommand.WalkForward;
speed *= -BackwardsFactor;
break;
case (uint)MotionCommand.TurnLeft:
motion = (uint)MotionCommand.TurnRight;
speed *= -1.0f;
break;
case (uint)MotionCommand.SideStepLeft:
motion = (uint)MotionCommand.SideStepRight;
speed *= -1.0f;
break;
}
if (motion == (uint)MotionCommand.SideStepRight)
speed *= SidestepFactor * (WalkAnimSpeed / SidestepAnimSpeed);
if (holdKey == HoldKey.Invalid)
holdKey = RawState.CurrentHoldKey;
if (holdKey == HoldKey.Run)
apply_run_to_command(ref motion, ref speed);
}
public void apply_current_movement(bool cancelMoveTo, bool allowJump)
{
if (PhysicsObj == null || !Initted) return;
if (WeenieObj != null && !WeenieObj.IsCreature || !PhysicsObj.movement_is_autonomous())
apply_interpreted_movement(cancelMoveTo, allowJump);
else
apply_raw_movement(cancelMoveTo, allowJump);
}
public void apply_interpreted_movement(bool cancelMoveTo, bool allowJump)
{
if (PhysicsObj == null) return;
var movementParams = new MovementParameters();
movementParams.SetHoldKey = false;
movementParams.ModifyInterpretedState = false;
movementParams.CancelMoveTo = cancelMoveTo;
movementParams.DisableJumpDuringLink = !allowJump;
if (InterpretedState.ForwardCommand == (uint)MotionCommand.RunForward)
MyRunRate = InterpretedState.ForwardSpeed;
DoInterpretedMotion(InterpretedState.CurrentStyle, movementParams);
if (contact_allows_move(InterpretedState.ForwardCommand))
{
if (!StandingLongJump)
{
movementParams.Speed = InterpretedState.ForwardSpeed;
DoInterpretedMotion(InterpretedState.ForwardCommand, movementParams);
if (InterpretedState.SideStepCommand != 0)
{
movementParams.Speed = InterpretedState.SideStepSpeed;
DoInterpretedMotion(InterpretedState.SideStepCommand, movementParams);
}
else
StopInterpretedMotion((uint)MotionCommand.SideStepRight, movementParams);
}
else
{
movementParams.Speed = 1.0f;
DoInterpretedMotion((uint)MotionCommand.Ready, movementParams);
StopInterpretedMotion((uint)MotionCommand.SideStepRight, movementParams);
}
}
else
{
movementParams.Speed = 1.0f;
DoInterpretedMotion((uint)MotionCommand.Falling, movementParams);
}
if (InterpretedState.TurnCommand != 0)
{
movementParams.Speed = InterpretedState.TurnSpeed;
DoInterpretedMotion(InterpretedState.TurnCommand, movementParams);
}
else
{
var result = PhysicsObj.StopInterpretedMotion((uint)MotionCommand.TurnRight, movementParams);
if (result == WeenieError.None)
{
add_to_queue(movementParams.ContextID, (uint)MotionCommand.Ready, WeenieError.None);
if (movementParams.ModifyInterpretedState)
InterpretedState.RemoveMotion((uint)MotionCommand.TurnRight);
}
if (PhysicsObj.CurCell == null)
PhysicsObj.RemoveLinkAnimations();
}
}
public void apply_raw_movement(bool cancelMoveTo, bool allowJump)
{
if (PhysicsObj == null) return;
InterpretedState.CurrentStyle = RawState.CurrentStyle;
InterpretedState.ForwardCommand = RawState.ForwardCommand;
InterpretedState.ForwardSpeed = RawState.ForwardSpeed;
InterpretedState.SideStepCommand = RawState.SideStepCommand;
InterpretedState.SideStepSpeed = RawState.SideStepSpeed;
InterpretedState.TurnCommand = RawState.TurnCommand;
InterpretedState.TurnSpeed = RawState.TurnSpeed;
adjust_motion(ref InterpretedState.ForwardCommand, ref InterpretedState.ForwardSpeed, RawState.ForwardHoldKey);
adjust_motion(ref InterpretedState.SideStepCommand, ref InterpretedState.SideStepSpeed, RawState.SideStepHoldKey);
adjust_motion(ref InterpretedState.TurnCommand, ref InterpretedState.TurnSpeed, RawState.TurnHoldKey);
apply_interpreted_movement(cancelMoveTo, allowJump);
}
public void apply_run_to_command(ref uint motion, ref float speed)
{
var speedMod = 1.0f;
if (WeenieObj != null)
{
var runFactor = 0.0f;
if (WeenieObj.InqRunRate(ref runFactor))
speedMod = runFactor;
else
speedMod = MyRunRate;
}
switch (motion)
{
case (uint)MotionCommand.WalkForward:
if (speed > 0.0f)
motion = (uint)MotionCommand.RunForward;
speed *= speedMod;
break;
case (uint)MotionCommand.TurnRight:
speed *= RunTurnFactor;
break;
case (uint)MotionCommand.SideStepRight:
speed *= speedMod;
if (MaxSidestepAnimRate < Math.Abs(speed))
{
if (speed > 0.0f)
speed = MaxSidestepAnimRate * 1.0f;
else
speed = MaxSidestepAnimRate * -1.0f;
}
break;
}
}
public int charge_jump()
{
if (WeenieObj != null && !WeenieObj.CanJump(JumpExtent))
return 0x49;
var forward = InterpretedState.ForwardCommand;
if (forward == (uint)MotionCommand.Falling || forward >= (uint)MotionCommand.Crouch && forward < (uint)MotionCommand.Sleeping)
return 0x48;
else
{
if (PhysicsObj.TransientState.HasFlag(TransientStateFlags.Contact | TransientStateFlags.OnWalkable) && forward == (uint)MotionCommand.Ready &&
InterpretedState.SideStepCommand == 0 && InterpretedState.TurnCommand == 0)
{
StandingLongJump = true;
}
}
return 0;
}
public bool contact_allows_move(uint motion)
{
if (PhysicsObj == null) return false;
if (motion == (uint)MotionCommand.Dead || motion == (uint)MotionCommand.Falling || motion >= (uint)MotionCommand.TurnRight && motion <= (uint)MotionCommand.TurnLeft)
return true;
if (WeenieObj != null && !WeenieObj.IsCreature)
return true;
if (!PhysicsObj.State.HasFlag(PhysicsState.Gravity))
return true;
if (!PhysicsObj.TransientState.HasFlag(TransientStateFlags.Contact))
return false;
if (PhysicsObj.TransientState.HasFlag(TransientStateFlags.OnWalkable))
return true;
return false;
}
public void enter_default_state()
{
RawState = new RawMotionState();
InterpretedState = new InterpretedMotionState();
PhysicsObj.InitializeMotionTables();
PendingMotions = new LinkedList<MotionNode>(); // ??
add_to_queue(0, (uint)MotionCommand.Ready, 0);
Initted = true;
LeaveGround();
}
public float get_adjusted_max_speed()
{
var rate = 1.0f;
if (WeenieObj != null)
{
if (!WeenieObj.InqRunRate(ref rate))
rate = MyRunRate;
}
if (InterpretedState.ForwardCommand == (uint)MotionCommand.RunForward)
rate = InterpretedState.ForwardSpeed / CurrentSpeedFactor;
return rate * 4.0f;
}
public float get_jump_v_z()
{
if (JumpExtent < PhysicsGlobals.EPSILON)
return 0.0f;
var extent = JumpExtent;
if (extent > 1.0f)
extent = 1.0f;
if (WeenieObj == null)
return 10.0f;
float vz = extent;
if (WeenieObj.InqJumpVelocity(extent, out vz))
return vz;
return 0.0f;
}
public Vector3 get_leave_ground_velocity()
{
var velocity = get_state_velocity();
velocity.Z = get_jump_v_z();
if (Vec.IsZero(velocity))
velocity = PhysicsObj.Position.GlobalToLocalVec(PhysicsObj.Velocity);
return velocity;
}
public float get_max_speed()
{
var rate = 1.0f;
if (WeenieObj != null)
{
if (!WeenieObj.InqRunRate(ref rate))
rate = MyRunRate;
}
return RunAnimSpeed * rate;
}
public Vector3 get_state_velocity()
{
var velocity = Vector3.Zero;
if (InterpretedState.SideStepCommand == (uint)MotionCommand.SideStepRight)
velocity.X = SidestepAnimSpeed * InterpretedState.SideStepSpeed;
if (InterpretedState.ForwardCommand == (uint)MotionCommand.WalkForward)
velocity.Y = WalkAnimSpeed * InterpretedState.ForwardSpeed;
else if (InterpretedState.ForwardCommand == (uint)MotionCommand.RunForward)
velocity.Y = RunAnimSpeed * InterpretedState.ForwardSpeed;
var rate = MyRunRate;
if (WeenieObj != null) WeenieObj.InqRunRate(ref rate);
var maxSpeed = RunAnimSpeed * rate;
if (velocity.Length() > maxSpeed)
{
velocity = Vector3.Normalize(velocity);
velocity *= maxSpeed;
}
return velocity;
}
public bool is_standing_still()
{
return PhysicsObj.TransientState.HasFlag(TransientStateFlags.Contact | TransientStateFlags.OnWalkable) &&
InterpretedState.ForwardCommand == (uint)MotionCommand.Ready &&
InterpretedState.SideStepCommand == 0 &&
InterpretedState.TurnCommand == 0;
}
public WeenieError jump(float extent, int adjustStamina)
{
if (PhysicsObj == null) return WeenieError.NoPhysicsObject;
PhysicsObj.cancel_moveto();
var result = jump_is_allowed(extent, adjustStamina);
if (result == WeenieError.None)
{
JumpExtent = extent;
PhysicsObj.set_on_walkable(false);
}
else
StandingLongJump = false;
return result;
}
public WeenieError jump_charge_is_allowed()
{
if (WeenieObj != null && !WeenieObj.CanJump(JumpExtent))
return WeenieError.CantJumpLoadedDown;
var forward = InterpretedState.ForwardCommand;
if (forward == (uint)MotionCommand.Fallen || forward >= (uint)MotionCommand.Crouch && forward <= (uint)MotionCommand.Sleeping)
return WeenieError.YouCantJumpFromThisPosition;
return WeenieError.None;
}
public WeenieError jump_is_allowed(float extent, int staminaCost)
{
if (PhysicsObj == null)
return WeenieError.NoPhysicsObject;
if (WeenieObj == null || !WeenieObj.IsCreature || !PhysicsObj.State.HasFlag(PhysicsState.Gravity) ||
PhysicsObj.TransientState.HasFlag(TransientStateFlags.Contact | TransientStateFlags.OnWalkable))
{
if (PhysicsObj.IsFullyConstrained())
return WeenieError.GeneralMovementFailure;
if (PendingMotions.Count > 1 && PendingMotions.First.Value.JumpErrorCode != 0)
return PendingMotions.First.Value.JumpErrorCode;
var jumpError = jump_charge_is_allowed();
if (jumpError == WeenieError.None)
{
jumpError = motion_allows_jump(InterpretedState.ForwardCommand);
if (jumpError == WeenieError.None && WeenieObj != null && WeenieObj.JumpStaminaCost(extent, staminaCost) == 0)
jumpError = WeenieError.GeneralMovementFailure;
}
return jumpError;
}
return WeenieError.YouCantJumpWhileInTheAir;
}
public WeenieError motion_allows_jump(uint substate)
{
if (substate >= (uint)MotionCommand.Reload && substate <= (uint)MotionCommand.Pickup || substate >= (uint)MotionCommand.TripleThrustLow && substate <= (uint)MotionCommand.MagicPowerUp07Purple ||
substate >= (uint)MotionCommand.MagicPowerUp01 && substate <= (uint)MotionCommand.MagicPowerUp10 || substate >= (uint)MotionCommand.Crouch && substate <= (uint)MotionCommand.Sleeping ||
substate >= (uint)MotionCommand.AimLevel && substate <= (uint)MotionCommand.MagicPray || substate == (uint)MotionCommand.Falling)
{
return WeenieError.YouCantJumpFromThisPosition;
}
return WeenieError.None;
}
/// <summary>
/// Alternatively, you can use PhysicsObj.IsAnimating for better performance.
/// </summary>
public bool motions_pending()
{
return PendingMotions.Count > 0;
}
public bool move_to_interpreted_state(InterpretedMotionState state)
{
if (PhysicsObj == null) return false;
RawState.CurrentStyle = state.CurrentStyle;
PhysicsObj.cancel_moveto();
var allowJump = motion_allows_jump(InterpretedState.ForwardCommand) == WeenieError.None ? true : false;
InterpretedState.copy_movement_from(state);
apply_current_movement(true, allowJump);
var movementParams = new MovementParameters();
foreach (var action in state.Actions)
{
var currentStamp = action.Stamp & 0x7FFF;
var serverStamp = ServerActionStamp & 0x7FFFF;
var deltaStamp = Math.Abs(currentStamp - serverStamp);
var diff = deltaStamp <= 0x3FFF ? serverStamp < currentStamp : currentStamp < serverStamp;
if (diff)
{
if (WeenieObj != null && WeenieObj.IsCreature || action.Autonomous)
{
ServerActionStamp = action.Stamp;
movementParams.Speed = action.Speed;
movementParams.Autonomous = action.Autonomous;
DoInterpretedMotion(action.Action, movementParams);
}
}
}
return true;
}
public void set_hold_run(int val, bool cancelMoveTo)
{
if ((val == 0) != (RawState.CurrentHoldKey != HoldKey.Run))
{
RawState.CurrentHoldKey = val != 0 ? HoldKey.Run : HoldKey.None;
apply_current_movement(cancelMoveTo, true);
}
}
}
}