Climbing system (#1750)
* Initial commit * Climbing uses its own controller now * Missed a check * Get rid of hands check * Cleanup * Get rid of speciescomponent stuff * Remove unneeded check, add separate case for moving other players. * Add DoAfter * IClientDraggable added to ClimbingComponent * Added some basic integration tests. Renamed ClimbMode to Climbing. * oops * Minor fixes * ffff * Table fix * Revamped system so its more predicted, uses proper logic for de-climbing. Get hype!!! * Flag check fix * Distance check and reset numticksblocked * get rid
This commit is contained in:
@@ -0,0 +1,12 @@
|
|||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Content.Shared.GameObjects.Components.Movement;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Movement
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
[ComponentReference(typeof(IClimbable))]
|
||||||
|
public class ClimbableComponent : SharedClimbableComponent
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.GameObjects.Components;
|
||||||
|
using Content.Shared.GameObjects.Components.Movement;
|
||||||
|
using Content.Client.Interfaces.GameObjects.Components.Interaction;
|
||||||
|
using Content.Shared.Physics;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Movement
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
public class ClimbingComponent : SharedClimbingComponent, IClientDraggable
|
||||||
|
{
|
||||||
|
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
|
||||||
|
{
|
||||||
|
if (!(curState is ClimbModeComponentState climbModeState) || Body == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
IsClimbing = climbModeState.Climbing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool IsClimbing { get; set; }
|
||||||
|
|
||||||
|
bool IClientDraggable.ClientCanDropOn(CanDropEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
return eventArgs.Target.HasComponent<IClimbable>();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IClientDraggable.ClientCanDrag(CanDragEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
#nullable enable
|
||||||
|
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.Map;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
using Content.Server.GameObjects.Components.Movement;
|
||||||
|
using Content.Shared.Physics;
|
||||||
|
using Robust.Shared.GameObjects.Components;
|
||||||
|
|
||||||
|
namespace Content.IntegrationTests.Tests.GameObjects.Components.Movement
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
[TestOf(typeof(ClimbableComponent))]
|
||||||
|
[TestOf(typeof(ClimbingComponent))]
|
||||||
|
public class ClimbUnitTest : ContentIntegrationTest
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public async Task Test()
|
||||||
|
{
|
||||||
|
var server = StartServerDummyTicker();
|
||||||
|
|
||||||
|
IEntity human;
|
||||||
|
IEntity table;
|
||||||
|
IEntity carpet;
|
||||||
|
ClimbableComponent climbable;
|
||||||
|
ClimbingComponent climbing;
|
||||||
|
|
||||||
|
server.Assert(() =>
|
||||||
|
{
|
||||||
|
var mapManager = IoCManager.Resolve<IMapManager>();
|
||||||
|
mapManager.CreateNewMapEntity(MapId.Nullspace);
|
||||||
|
|
||||||
|
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||||
|
|
||||||
|
// Spawn the entities
|
||||||
|
human = entityManager.SpawnEntity("HumanMob_Content", MapCoordinates.Nullspace);
|
||||||
|
table = entityManager.SpawnEntity("Table", MapCoordinates.Nullspace);
|
||||||
|
|
||||||
|
// Test for climb components existing
|
||||||
|
// Players and tables should have these in their prototypes.
|
||||||
|
Assert.True(human.TryGetComponent(out climbing), "Human has no climbing");
|
||||||
|
Assert.True(table.TryGetComponent(out climbable), "Table has no climbable");
|
||||||
|
|
||||||
|
// Now let's make the player enter a climbing transitioning state.
|
||||||
|
climbing.IsClimbing = true;
|
||||||
|
climbing.TryMoveTo(human.Transform.WorldPosition, table.Transform.WorldPosition);
|
||||||
|
human.TryGetComponent(out ICollidableComponent body);
|
||||||
|
|
||||||
|
Assert.True(body.HasController<ClimbController>(), "Player has no ClimbController");
|
||||||
|
|
||||||
|
// Force the player out of climb state. It should immediately remove the ClimbController.
|
||||||
|
climbing.IsClimbing = false;
|
||||||
|
|
||||||
|
Assert.True(!body.HasController<ClimbController>(), "Player wrongly has a ClimbController");
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.WaitIdleAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,235 @@
|
|||||||
|
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.GameObjects.Components;
|
||||||
|
using Robust.Shared.GameObjects.Systems;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Localization;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
using Robust.Shared.ViewVariables;
|
||||||
|
using Robust.Server.Interfaces.Player;
|
||||||
|
using Content.Server.Interfaces;
|
||||||
|
using Content.Shared.GameObjects.EntitySystems;
|
||||||
|
using Content.Shared.Interfaces.GameObjects.Components;
|
||||||
|
using Content.Shared.GameObjects.Components.Movement;
|
||||||
|
using Content.Shared.Interfaces;
|
||||||
|
using Content.Server.GameObjects.Components.Body;
|
||||||
|
using Content.Server.GameObjects.EntitySystems.DoAfter;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Content.Server.GameObjects.Components.Movement
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
[ComponentReference(typeof(IClimbable))]
|
||||||
|
public class ClimbableComponent : SharedClimbableComponent, IDragDropOn
|
||||||
|
{
|
||||||
|
#pragma warning disable 649
|
||||||
|
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
|
||||||
|
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||||
|
#pragma warning restore 649
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The range from which this entity can be climbed.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
private float _range;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The time it takes to climb onto the entity.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
private float _climbDelay;
|
||||||
|
|
||||||
|
private ICollidableComponent _collidableComponent;
|
||||||
|
private DoAfterSystem _doAfterSystem;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
_collidableComponent = Owner.GetComponent<ICollidableComponent>();
|
||||||
|
_doAfterSystem = EntitySystem.Get<DoAfterSystem>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ExposeData(ObjectSerializer serializer)
|
||||||
|
{
|
||||||
|
base.ExposeData(serializer);
|
||||||
|
|
||||||
|
serializer.DataField(ref _range, "range", SharedInteractionSystem.InteractionRange / 1.4f);
|
||||||
|
serializer.DataField(ref _climbDelay, "delay", 0.8f);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IDragDropOn.CanDragDropOn(DragDropEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
if (!ActionBlockerSystem.CanInteract(eventArgs.User))
|
||||||
|
{
|
||||||
|
_notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("You can't do that!"));
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eventArgs.User == eventArgs.Dropped) // user is dragging themselves onto a climbable
|
||||||
|
{
|
||||||
|
if (!eventArgs.User.HasComponent<ClimbingComponent>())
|
||||||
|
{
|
||||||
|
_notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("You are incapable of climbing!"));
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bodyManager = eventArgs.User.GetComponent<BodyManagerComponent>();
|
||||||
|
|
||||||
|
if (bodyManager.GetBodyPartsOfType(Shared.GameObjects.Components.Body.BodyPartType.Leg).Count == 0 ||
|
||||||
|
bodyManager.GetBodyPartsOfType(Shared.GameObjects.Components.Body.BodyPartType.Foot).Count == 0)
|
||||||
|
{
|
||||||
|
_notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("You are unable to climb!"));
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var userPosition = eventArgs.User.Transform.MapPosition;
|
||||||
|
var climbablePosition = eventArgs.Target.Transform.MapPosition;
|
||||||
|
var interaction = EntitySystem.Get<SharedInteractionSystem>();
|
||||||
|
bool Ignored(IEntity entity) => (entity == eventArgs.Target || entity == eventArgs.User);
|
||||||
|
|
||||||
|
if (!interaction.InRangeUnobstructed(userPosition, climbablePosition, _range, predicate: Ignored))
|
||||||
|
{
|
||||||
|
_notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("You can't reach there!"));
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else // user is dragging some other entity onto a climbable
|
||||||
|
{
|
||||||
|
if (eventArgs.Target == null || !eventArgs.Dropped.HasComponent<ClimbingComponent>())
|
||||||
|
{
|
||||||
|
_notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("You can't do that!"));
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var userPosition = eventArgs.User.Transform.MapPosition;
|
||||||
|
var otherUserPosition = eventArgs.Dropped.Transform.MapPosition;
|
||||||
|
var climbablePosition = eventArgs.Target.Transform.MapPosition;
|
||||||
|
var interaction = EntitySystem.Get<SharedInteractionSystem>();
|
||||||
|
bool Ignored(IEntity entity) => (entity == eventArgs.Target || entity == eventArgs.User || entity == eventArgs.Dropped);
|
||||||
|
|
||||||
|
if (!interaction.InRangeUnobstructed(userPosition, climbablePosition, _range, predicate: Ignored) ||
|
||||||
|
!interaction.InRangeUnobstructed(userPosition, otherUserPosition, _range, predicate: Ignored))
|
||||||
|
{
|
||||||
|
_notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("You can't reach there!"));
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IDragDropOn.DragDropOn(DragDropEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
if (eventArgs.User == eventArgs.Dropped)
|
||||||
|
{
|
||||||
|
TryClimb(eventArgs.User);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TryMoveEntity(eventArgs.User, eventArgs.Dropped);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void TryMoveEntity(IEntity user, IEntity entityToMove)
|
||||||
|
{
|
||||||
|
var doAfterEventArgs = new DoAfterEventArgs(user, _climbDelay, default, entityToMove)
|
||||||
|
{
|
||||||
|
BreakOnTargetMove = true,
|
||||||
|
BreakOnUserMove = true,
|
||||||
|
BreakOnDamage = true,
|
||||||
|
BreakOnStun = true
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await _doAfterSystem.DoAfter(doAfterEventArgs);
|
||||||
|
|
||||||
|
if (result != DoAfterStatus.Cancelled && entityToMove.TryGetComponent(out ICollidableComponent body) && body.PhysicsShapes.Count >= 1)
|
||||||
|
{
|
||||||
|
var direction = (Owner.Transform.WorldPosition - entityToMove.Transform.WorldPosition).Normalized;
|
||||||
|
var endPoint = Owner.Transform.WorldPosition;
|
||||||
|
|
||||||
|
var climbMode = entityToMove.GetComponent<ClimbingComponent>();
|
||||||
|
climbMode.IsClimbing = true;
|
||||||
|
|
||||||
|
if (MathF.Abs(direction.X) < 0.6f) // user climbed mostly vertically so lets make it a clean straight line
|
||||||
|
{
|
||||||
|
endPoint = new Vector2(entityToMove.Transform.WorldPosition.X, endPoint.Y);
|
||||||
|
}
|
||||||
|
else if (MathF.Abs(direction.Y) < 0.6f) // user climbed mostly horizontally so lets make it a clean straight line
|
||||||
|
{
|
||||||
|
endPoint = new Vector2(endPoint.X, entityToMove.Transform.WorldPosition.Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
climbMode.TryMoveTo(entityToMove.Transform.WorldPosition, endPoint);
|
||||||
|
// we may potentially need additional logic since we're forcing a player onto a climbable
|
||||||
|
// there's also the cases where the user might collide with the person they are forcing onto the climbable that i haven't accounted for
|
||||||
|
|
||||||
|
PopupMessageOtherClientsInRange(user, Loc.GetString("{0:them} forces {1:them} onto {2:theName}!", user, entityToMove, Owner), 15);
|
||||||
|
_notifyManager.PopupMessage(user, user, Loc.GetString("You force {0:them} onto {1:theName}!", entityToMove, Owner));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void TryClimb(IEntity user)
|
||||||
|
{
|
||||||
|
var doAfterEventArgs = new DoAfterEventArgs(user, _climbDelay, default, Owner)
|
||||||
|
{
|
||||||
|
BreakOnTargetMove = true,
|
||||||
|
BreakOnUserMove = true,
|
||||||
|
BreakOnDamage = true,
|
||||||
|
BreakOnStun = true
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await _doAfterSystem.DoAfter(doAfterEventArgs);
|
||||||
|
|
||||||
|
if (result != DoAfterStatus.Cancelled && user.TryGetComponent(out ICollidableComponent body) && body.PhysicsShapes.Count >= 1)
|
||||||
|
{
|
||||||
|
var direction = (Owner.Transform.WorldPosition - user.Transform.WorldPosition).Normalized;
|
||||||
|
var endPoint = Owner.Transform.WorldPosition;
|
||||||
|
|
||||||
|
var climbMode = user.GetComponent<ClimbingComponent>();
|
||||||
|
climbMode.IsClimbing = true;
|
||||||
|
|
||||||
|
if (MathF.Abs(direction.X) < 0.6f) // user climbed mostly vertically so lets make it a clean straight line
|
||||||
|
{
|
||||||
|
endPoint = new Vector2(user.Transform.WorldPosition.X, endPoint.Y);
|
||||||
|
}
|
||||||
|
else if (MathF.Abs(direction.Y) < 0.6f) // user climbed mostly horizontally so lets make it a clean straight line
|
||||||
|
{
|
||||||
|
endPoint = new Vector2(endPoint.X, user.Transform.WorldPosition.Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
climbMode.TryMoveTo(user.Transform.WorldPosition, endPoint);
|
||||||
|
|
||||||
|
PopupMessageOtherClientsInRange(user, Loc.GetString("{0:them} jumps onto {1:theName}!", user, Owner), 15);
|
||||||
|
_notifyManager.PopupMessage(user, user, Loc.GetString("You jump onto {0:theName}!", Owner));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PopupMessageOtherClientsInRange(IEntity source, string message, int maxReceiveDistance)
|
||||||
|
{
|
||||||
|
var viewers = _playerManager.GetPlayersInRange(source.Transform.GridPosition, maxReceiveDistance);
|
||||||
|
|
||||||
|
foreach (var viewer in viewers)
|
||||||
|
{
|
||||||
|
var viewerEntity = viewer.AttachedEntity;
|
||||||
|
|
||||||
|
if (viewerEntity == null || source == viewerEntity)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
source.PopupMessage(viewer.AttachedEntity, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
using Robust.Shared.GameObjects.Components;
|
||||||
|
using Content.Shared.Physics;
|
||||||
|
using Content.Shared.Maps;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Content.Shared.GameObjects.Components.Movement;
|
||||||
|
using Content.Shared.GameObjects.EntitySystems;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Robust.Shared.Physics;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace Content.Server.GameObjects.Components.Movement
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
public class ClimbingComponent : SharedClimbingComponent, IActionBlocker
|
||||||
|
{
|
||||||
|
private bool _isClimbing = false;
|
||||||
|
private ClimbController _climbController = default;
|
||||||
|
|
||||||
|
public override bool IsClimbing
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _isClimbing;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (!value && Body != null)
|
||||||
|
{
|
||||||
|
Body.TryRemoveController<ClimbController>();
|
||||||
|
}
|
||||||
|
|
||||||
|
_isClimbing = value;
|
||||||
|
Dirty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Make the owner climb from one point to another
|
||||||
|
/// </summary>
|
||||||
|
public void TryMoveTo(Vector2 from, Vector2 to)
|
||||||
|
{
|
||||||
|
if (Body != null)
|
||||||
|
{
|
||||||
|
_climbController = Body.EnsureController<ClimbController>();
|
||||||
|
_climbController.TryMoveTo(from, to);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update(float frameTime)
|
||||||
|
{
|
||||||
|
if (Body != null && IsClimbing)
|
||||||
|
{
|
||||||
|
if (_climbController != null && (_climbController.IsBlocked || !_climbController.IsActive))
|
||||||
|
{
|
||||||
|
if (Body.TryRemoveController<ClimbController>())
|
||||||
|
{
|
||||||
|
_climbController = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IsOnClimbableThisFrame && IsClimbing && _climbController == null)
|
||||||
|
{
|
||||||
|
IsClimbing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
IsOnClimbableThisFrame = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ComponentState GetComponentState()
|
||||||
|
{
|
||||||
|
return new ClimbModeComponentState(_isClimbing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
18
Content.Server/GameObjects/EntitySystems/ClimbSystem.cs
Normal file
18
Content.Server/GameObjects/EntitySystems/ClimbSystem.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using Content.Server.GameObjects.Components.Movement;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Shared.GameObjects.Systems;
|
||||||
|
|
||||||
|
namespace Content.Server.GameObjects.EntitySystems
|
||||||
|
{
|
||||||
|
[UsedImplicitly]
|
||||||
|
internal sealed class ClimbSystem : EntitySystem
|
||||||
|
{
|
||||||
|
public override void Update(float frameTime)
|
||||||
|
{
|
||||||
|
foreach (var comp in ComponentManager.EntityQuery<ClimbingComponent>())
|
||||||
|
{
|
||||||
|
comp.Update(frameTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Shared.GameObjects.Components.Movement
|
||||||
|
{
|
||||||
|
public interface IClimbable { };
|
||||||
|
|
||||||
|
public class SharedClimbableComponent : Component, IClimbable
|
||||||
|
{
|
||||||
|
public sealed override string Name => "Climbable";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
using Content.Shared.GameObjects.EntitySystems;
|
||||||
|
using Content.Shared.Physics;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.GameObjects.Components;
|
||||||
|
using Robust.Shared.Physics;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Content.Shared.GameObjects.Components.Movement
|
||||||
|
{
|
||||||
|
public abstract class SharedClimbingComponent : Component, IActionBlocker, ICollideSpecial
|
||||||
|
{
|
||||||
|
public sealed override string Name => "Climbing";
|
||||||
|
public sealed override uint? NetID => ContentNetIDs.CLIMBING;
|
||||||
|
|
||||||
|
protected ICollidableComponent Body;
|
||||||
|
protected bool IsOnClimbableThisFrame = false;
|
||||||
|
|
||||||
|
protected bool OwnerIsTransitioning
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (Body.TryGetController<ClimbController>(out var controller))
|
||||||
|
{
|
||||||
|
return controller.IsActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract bool IsClimbing { get; set; }
|
||||||
|
|
||||||
|
bool IActionBlocker.CanMove() => !OwnerIsTransitioning;
|
||||||
|
bool IActionBlocker.CanChangeDirection() => !OwnerIsTransitioning;
|
||||||
|
|
||||||
|
bool ICollideSpecial.PreventCollide(IPhysBody collided)
|
||||||
|
{
|
||||||
|
if (((CollisionGroup)collided.CollisionLayer).HasFlag(CollisionGroup.VaultImpassable) && collided.Entity.HasComponent<IClimbable>())
|
||||||
|
{
|
||||||
|
IsOnClimbableThisFrame = true;
|
||||||
|
return IsClimbing;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
Owner.TryGetComponent(out Body);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
protected sealed class ClimbModeComponentState : ComponentState
|
||||||
|
{
|
||||||
|
public ClimbModeComponentState(bool climbing) : base(ContentNetIDs.CLIMBING)
|
||||||
|
{
|
||||||
|
Climbing = climbing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Climbing { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -54,7 +54,6 @@
|
|||||||
public const uint STUNNABLE = 1048;
|
public const uint STUNNABLE = 1048;
|
||||||
public const uint HUNGER = 1049;
|
public const uint HUNGER = 1049;
|
||||||
public const uint THIRST = 1050;
|
public const uint THIRST = 1050;
|
||||||
|
|
||||||
public const uint FLASHABLE = 1051;
|
public const uint FLASHABLE = 1051;
|
||||||
public const uint BUCKLE = 1052;
|
public const uint BUCKLE = 1052;
|
||||||
public const uint PROJECTILE = 1053;
|
public const uint PROJECTILE = 1053;
|
||||||
@@ -65,6 +64,7 @@
|
|||||||
public const uint DO_AFTER = 1058;
|
public const uint DO_AFTER = 1058;
|
||||||
public const uint RADIATION_PULSE = 1059;
|
public const uint RADIATION_PULSE = 1059;
|
||||||
public const uint BODY_MANAGER = 1060;
|
public const uint BODY_MANAGER = 1060;
|
||||||
|
public const uint CLIMBING = 1061;
|
||||||
|
|
||||||
// Net IDs for integration tests.
|
// Net IDs for integration tests.
|
||||||
public const uint PREDICTION_TEST = 10001;
|
public const uint PREDICTION_TEST = 10001;
|
||||||
|
|||||||
@@ -10,21 +10,13 @@ namespace Content.Shared.GameObjects.EntitySystems
|
|||||||
public interface IActionBlocker
|
public interface IActionBlocker
|
||||||
{
|
{
|
||||||
bool CanMove() => true;
|
bool CanMove() => true;
|
||||||
|
|
||||||
bool CanInteract() => true;
|
bool CanInteract() => true;
|
||||||
|
|
||||||
bool CanUse() => true;
|
bool CanUse() => true;
|
||||||
|
|
||||||
bool CanThrow() => true;
|
bool CanThrow() => true;
|
||||||
|
|
||||||
bool CanSpeak() => true;
|
bool CanSpeak() => true;
|
||||||
|
|
||||||
bool CanDrop() => true;
|
bool CanDrop() => true;
|
||||||
|
|
||||||
bool CanPickup() => true;
|
bool CanPickup() => true;
|
||||||
|
|
||||||
bool CanEmote() => true;
|
bool CanEmote() => true;
|
||||||
|
|
||||||
bool CanAttack() => true;
|
bool CanAttack() => true;
|
||||||
|
|
||||||
bool CanEquip() => true;
|
bool CanEquip() => true;
|
||||||
|
|||||||
87
Content.Shared/Physics/ClimbController.cs
Normal file
87
Content.Shared/Physics/ClimbController.cs
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
#nullable enable
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
using Robust.Shared.Physics;
|
||||||
|
|
||||||
|
namespace Content.Shared.Physics
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Movement controller used by the climb system. Lerps the player from A to B.
|
||||||
|
/// Also does checks to make sure the player isn't blocked.
|
||||||
|
/// </summary>
|
||||||
|
public class ClimbController : VirtualController
|
||||||
|
{
|
||||||
|
private Vector2? _movingTo = null;
|
||||||
|
private Vector2 _lastKnownPosition = default;
|
||||||
|
private int _numTicksBlocked = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If 5 ticks have passed and our position has not changed then something is blocking us.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsBlocked => _numTicksBlocked > 5 || _isMovingWrongDirection;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If the controller is currently moving the player somewhere, it is considered active.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsActive => _movingTo.HasValue;
|
||||||
|
|
||||||
|
private float _initialDist = default;
|
||||||
|
private bool _isMovingWrongDirection = false;
|
||||||
|
|
||||||
|
public void TryMoveTo(Vector2 from, Vector2 to)
|
||||||
|
{
|
||||||
|
if (ControlledComponent == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_initialDist = (from - to).Length;
|
||||||
|
_numTicksBlocked = 0;
|
||||||
|
_lastKnownPosition = from;
|
||||||
|
_movingTo = to;
|
||||||
|
_isMovingWrongDirection = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void UpdateAfterProcessing()
|
||||||
|
{
|
||||||
|
base.UpdateAfterProcessing();
|
||||||
|
|
||||||
|
if (ControlledComponent == null || _movingTo == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((ControlledComponent.Owner.Transform.WorldPosition - _lastKnownPosition).Length <= 0.05f)
|
||||||
|
{
|
||||||
|
_numTicksBlocked++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_numTicksBlocked = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
_lastKnownPosition = ControlledComponent.Owner.Transform.WorldPosition;
|
||||||
|
|
||||||
|
if ((ControlledComponent.Owner.Transform.WorldPosition - _movingTo.Value).Length <= 0.05f)
|
||||||
|
{
|
||||||
|
_movingTo = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_movingTo.HasValue)
|
||||||
|
{
|
||||||
|
var dist = (_lastKnownPosition - _movingTo.Value).Length;
|
||||||
|
|
||||||
|
if (dist > _initialDist)
|
||||||
|
{
|
||||||
|
_isMovingWrongDirection = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var diff = _movingTo.Value - ControlledComponent.Owner.Transform.WorldPosition;
|
||||||
|
LinearVelocity = diff.Normalized * 5;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LinearVelocity = Vector2.Zero;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@
|
|||||||
- type: IconSmooth
|
- type: IconSmooth
|
||||||
key: generic
|
key: generic
|
||||||
base: solid_
|
base: solid_
|
||||||
|
- type: Climbable
|
||||||
- type: Destructible
|
- type: Destructible
|
||||||
maxHP: 50
|
maxHP: 50
|
||||||
spawnOnDestroy: SteelSheet1
|
spawnOnDestroy: SteelSheet1
|
||||||
|
|||||||
@@ -141,6 +141,7 @@
|
|||||||
- type: RotationVisualizer
|
- type: RotationVisualizer
|
||||||
- type: BuckleVisualizer
|
- type: BuckleVisualizer
|
||||||
- type: CombatMode
|
- type: CombatMode
|
||||||
|
- type: Climbing
|
||||||
- type: Teleportable
|
- type: Teleportable
|
||||||
- type: CharacterInfo
|
- type: CharacterInfo
|
||||||
- type: FootstepSound
|
- type: FootstepSound
|
||||||
|
|||||||
Reference in New Issue
Block a user