Add pointing (#1435)

* Add pointing keybind and simple message

* Add turning the player when pointing

* Add pointing arrow

* Make the popup message appear on the pointing entity

* Add pointing to tiles and space and proper grammar

* Move pointing bind from HandsSystem to PointingSystem

* Add more messages for pointing depending on the viewer perspective

* Fix non nullable reference type

* Serialize pointing arrow duration

* Serialize pointing arrow step and add summaries

* Make arrow speed serializable and make it depend on frame time

* Add 0.2 second delay between pointings

* Add pointing arrow yaml examples

* Add the ability for pointing arrows to be rogue

* Remove rogue package reference

* Add point to verb

https://cdn.discordapp.com/attachments/313107470031650816/735268651636228197/unknown.png

* Add shift middle clicking an entity in the verb menu to point at it

* Add VV to PointingArrowComponent

* Increase pointing delay from 0.2 to 0.5 seconds

* Address reviews

* Fix nullability errors

* Separate pointing and rogue pointing code

* Fix rogue pointing arrow visuals

* Made rogue pointing arrow rotation adjustment readable for mortals

* Make rogue pointing arrows less destructive

* Cleanup more of the rogue pointing arrow code
This commit is contained in:
DrSmugleaf
2020-07-24 14:51:18 +02:00
committed by GitHub
parent 1eac63e5bb
commit 245dbdaa3a
19 changed files with 671 additions and 4 deletions

View File

@@ -0,0 +1,21 @@
using Content.Shared.GameObjects.Components.Pointing;
using Robust.Client.GameObjects;
using Robust.Shared.GameObjects;
using DrawDepth = Content.Shared.GameObjects.DrawDepth;
namespace Content.Client.GameObjects.Components.Pointing
{
[RegisterComponent]
public class PointingArrowComponent : SharedPointingArrowComponent
{
protected override void Startup()
{
base.Startup();
if (Owner.TryGetComponent(out SpriteComponent sprite))
{
sprite.DrawDepth = (int) DrawDepth.Overlays;
}
}
}
}

View File

@@ -0,0 +1,21 @@
using Content.Shared.GameObjects.Components.Pointing;
using Robust.Client.GameObjects;
using Robust.Shared.GameObjects;
using DrawDepth = Content.Shared.GameObjects.DrawDepth;
namespace Content.Client.GameObjects.Components.Pointing
{
[RegisterComponent]
public class RoguePointingArrowComponent : SharedRoguePointingArrowComponent
{
protected override void Startup()
{
base.Startup();
if (Owner.TryGetComponent(out SpriteComponent sprite))
{
sprite.DrawDepth = (int) DrawDepth.Overlays;
}
}
}
}

View File

@@ -0,0 +1,67 @@
using System;
using Content.Shared.GameObjects.Components.Mobs;
using Content.Shared.GameObjects.Components.Pointing;
using JetBrains.Annotations;
using Robust.Client.Animations;
using Robust.Client.GameObjects;
using Robust.Client.GameObjects.Components.Animations;
using Robust.Client.Interfaces.GameObjects.Components;
using Robust.Shared.Animations;
using Robust.Shared.Maths;
namespace Content.Client.GameObjects.Components.Pointing
{
[UsedImplicitly]
public class RoguePointingArrowVisualizer : AppearanceVisualizer
{
public override void OnChangeData(AppearanceComponent component)
{
base.OnChangeData(component);
if (component.Deleted)
{
return;
}
if (component.TryGetData<double>(RoguePointingArrowVisuals.Rotation, out var degrees))
{
SetRotation(component, Angle.FromDegrees(degrees));
}
}
private void SetRotation(AppearanceComponent component, Angle rotation)
{
var sprite = component.Owner.GetComponent<ISpriteComponent>();
if (!sprite.Owner.TryGetComponent(out AnimationPlayerComponent animation))
{
sprite.Rotation = rotation;
return;
}
if (animation.HasRunningAnimation("rotate"))
{
animation.Stop("rotate");
}
animation.Play(new Animation
{
Length = TimeSpan.FromSeconds(0.125),
AnimationTracks =
{
new AnimationTrackComponentProperty
{
ComponentType = typeof(ISpriteComponent),
Property = nameof(ISpriteComponent.Rotation),
InterpolationMode = AnimationInterpolationMode.Linear,
KeyFrames =
{
new AnimationTrackProperty.KeyFrame(sprite.Rotation, 0),
new AnimationTrackProperty.KeyFrame(rotation, 0.125f)
}
}
}
}, "rotate");
}
}
}

View File

@@ -440,7 +440,8 @@ namespace Content.Client.GameObjects.EntitySystems
return;
}
if (args.Function == EngineKeyFunctions.Use)
if (args.Function == EngineKeyFunctions.Use ||
args.Function == ContentKeyFunctions.Point)
{
var inputSys = _master.EntitySystemManager.GetEntitySystem<InputSystem>();

View File

@@ -35,6 +35,7 @@ namespace Content.Client.Input
human.AddFunction(ContentKeyFunctions.MouseMiddle);
human.AddFunction(ContentKeyFunctions.ToggleCombatMode);
human.AddFunction(ContentKeyFunctions.WideAttack);
human.AddFunction(ContentKeyFunctions.Point);
var ghost = contexts.New("ghost", "common");
ghost.AddFunction(EngineKeyFunctions.MoveUp);

View File

@@ -82,6 +82,7 @@ Do wide attack: [color=#a4885c]{23}[/color]
Use targeted entity: [color=#a4885c]{11}[/color]
Throw held item: [color=#a4885c]{12}[/color]
Examine entity: [color=#a4885c]{13}[/color]
Point somewhere: [color=#a4885c]{28}[/color]
Open entity context menu: [color=#a4885c]{14}[/color]
Toggle combat mode: [color=#a4885c]{15}[/color]
Toggle console: [color=#a4885c]{16}[/color]
@@ -114,7 +115,8 @@ Toggle sandbox window: [color=#a4885c]{21}[/color]",
Key(SmartEquipBackpack),
Key(SmartEquipBelt),
Key(FocusOOC),
Key(FocusAdminChat)));
Key(FocusAdminChat),
Key(Point)));
//Gameplay
VBox.AddChild(new Label { FontOverride = headerFont, Text = "\nGameplay" });

View File

@@ -0,0 +1,101 @@
#nullable enable
using Content.Shared.GameObjects.Components.Pointing;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
using DrawDepth = Content.Shared.GameObjects.DrawDepth;
namespace Content.Server.GameObjects.Components.Pointing
{
[RegisterComponent]
public class PointingArrowComponent : SharedPointingArrowComponent
{
/// <summary>
/// The current amount of seconds left on this arrow.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
private float _duration;
/// <summary>
/// The amount of seconds before the arrow changes movement direction.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
private float _step;
/// <summary>
/// The amount of units that this arrow will move by when multiplied
/// by the frame time.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
private float _speed;
/// <summary>
/// The current amount of seconds left before the arrow changes
/// movement direction.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
private float _currentStep;
/// <summary>
/// Whether or not this arrow is currently going up.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
private bool _up;
/// <summary>
/// Whether or not this arrow will convert into a
/// <see cref="RoguePointingArrowComponent"/> when its duration runs out.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
private bool _rogue;
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _duration, "duration", 4);
serializer.DataField(ref _step, "step", 0.5f);
serializer.DataField(ref _speed, "speed", 1);
serializer.DataField(ref _rogue, "rogue", false);
}
protected override void Startup()
{
base.Startup();
if (Owner.TryGetComponent(out SpriteComponent sprite))
{
sprite.DrawDepth = (int) DrawDepth.Overlays;
}
}
public void Update(float frameTime)
{
var movement = _speed * frameTime * (_up ? 1 : -1);
Owner.Transform.LocalPosition += (0, movement);
_duration -= frameTime;
_currentStep -= frameTime;
if (_duration <= 0)
{
if (_rogue)
{
Owner.RemoveComponent<PointingArrowComponent>();
Owner.AddComponent<RoguePointingArrowComponent>();
return;
}
Owner.Delete();
return;
}
if (_currentStep <= 0)
{
_currentStep = _step;
_up ^= true;
}
}
}
}

View File

@@ -0,0 +1,137 @@
#nullable enable
using System;
using System.Linq;
using Content.Server.Explosions;
using Content.Shared.GameObjects.Components.Pointing;
using Robust.Server.GameObjects;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Server.Interfaces.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Random;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Random;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
using DrawDepth = Content.Shared.GameObjects.DrawDepth;
namespace Content.Server.GameObjects.Components.Pointing
{
[RegisterComponent]
public class RoguePointingArrowComponent : SharedRoguePointingArrowComponent
{
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[ViewVariables]
private IEntity? _chasing;
[ViewVariables(VVAccess.ReadWrite)]
private float _turningDelay;
[ViewVariables(VVAccess.ReadWrite)]
private float _chasingDelay;
[ViewVariables(VVAccess.ReadWrite)]
private float _chasingSpeed;
[ViewVariables(VVAccess.ReadWrite)]
private float _chasingTime;
private IEntity? RandomNearbyPlayer()
{
var players = _playerManager
.GetPlayersInRange(Owner.Transform.GridPosition, 15)
.Where(player => player.AttachedEntity != null)
.ToArray();
if (players.Length == 0)
{
return null;
}
return _random.Pick(players).AttachedEntity;
}
private void UpdateAppearance()
{
if (_chasing == null ||
!Owner.TryGetComponent(out AppearanceComponent appearance))
{
return;
}
appearance.SetData(RoguePointingArrowVisuals.Rotation, Owner.Transform.LocalRotation.Degrees);
}
protected override void Startup()
{
base.Startup();
if (Owner.TryGetComponent(out SpriteComponent sprite))
{
sprite.DrawDepth = (int) DrawDepth.Overlays;
}
}
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _turningDelay, "turningDelay", 2);
serializer.DataField(ref _chasingDelay, "chasingDelay", 1);
serializer.DataField(ref _chasingSpeed, "chasingSpeed", 5);
serializer.DataField(ref _chasingTime, "chasingTime", 1f);
}
public void Update(float frameTime)
{
_chasing ??= RandomNearbyPlayer();
if (_chasing == null)
{
Owner.Delete();
return;
}
_turningDelay -= frameTime;
if (_turningDelay > 0)
{
var difference = _chasing.Transform.WorldPosition - Owner.Transform.WorldPosition;
var angle = difference.ToAngle();
var adjusted = angle.Degrees + 90;
var newAngle = Angle.FromDegrees(adjusted);
Owner.Transform.LocalRotation = newAngle;
UpdateAppearance();
return;
}
_chasingDelay -= frameTime;
Owner.Transform.WorldRotation += Angle.FromDegrees(20);
UpdateAppearance();
var toChased = _chasing.Transform.WorldPosition - Owner.Transform.WorldPosition;
Owner.Transform.WorldPosition += toChased * frameTime * _chasingSpeed;
_chasingTime -= frameTime;
if (_chasingTime > 0)
{
return;
}
ExplosionHelper.SpawnExplosion(Owner.Transform.GridPosition, 0, 2, 1, 1);
EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/Effects/explosion.ogg", Owner);
Owner.Delete();
}
}
}

View File

@@ -20,6 +20,8 @@ using Content.Server.GameObjects;
using Content.Server.GameObjects.Components;
using Content.Server.GameObjects.Components.Items.Storage;
using Content.Server.GameObjects.EntitySystems.Click;
using Content.Shared.Interfaces;
using Robust.Shared.Maths;
namespace Content.Server.Interfaces.GameObjects.Components.Interaction
{
@@ -47,8 +49,7 @@ namespace Content.Server.Interfaces.GameObjects.Components.Interaction
.Bind(ContentKeyFunctions.ActivateItemInHand, InputCmdHandler.FromDelegate(HandleActivateItem))
.Bind(ContentKeyFunctions.ThrowItemInHand, new PointerInputCmdHandler(HandleThrowItem))
.Bind(ContentKeyFunctions.SmartEquipBackpack, InputCmdHandler.FromDelegate(HandleSmartEquipBackpack))
.Bind(ContentKeyFunctions.SmartEquipBelt, InputCmdHandler.FromDelegate(HandleSmartEquipBelt))
.Register<HandsSystem>();
.Bind(ContentKeyFunctions.SmartEquipBelt, InputCmdHandler.FromDelegate(HandleSmartEquipBelt)).Register<HandsSystem>();
}
/// <inheritdoc />

View File

@@ -0,0 +1,172 @@
#nullable enable
using System;
using System.Collections.Generic;
using Content.Server.GameObjects.Components.Pointing;
using Content.Shared.Input;
using Content.Shared.Interfaces;
using JetBrains.Annotations;
using Robust.Server.Interfaces.Player;
using Robust.Server.Player;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Input.Binding;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Map;
using Robust.Shared.Interfaces.Timing;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Players;
namespace Content.Server.GameObjects.EntitySystems
{
[UsedImplicitly]
public class PointingSystem : EntitySystem
{
#pragma warning disable 649
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
#pragma warning restore 649
private static readonly TimeSpan PointDelay = TimeSpan.FromSeconds(0.5f);
/// <summary>
/// A dictionary of players to the last time that they
/// pointed at something.
/// </summary>
private readonly Dictionary<ICommonSession, TimeSpan> _pointers = new Dictionary<ICommonSession, TimeSpan>();
private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
{
if (e.NewStatus != SessionStatus.Disconnected)
{
return;
}
_pointers.Remove(e.Session);
}
// TODO: FOV
private void SendMessage(IEntity source, IList<IPlayerSession> viewers, IEntity? pointed, string selfMessage,
string viewerMessage, string? viewerPointedAtMessage = null)
{
foreach (var viewer in viewers)
{
var viewerEntity = viewer.AttachedEntity;
if (viewerEntity == null)
{
continue;
}
var message = viewerEntity == source
? selfMessage
: viewerEntity == pointed && viewerPointedAtMessage != null
? viewerPointedAtMessage
: viewerMessage;
source.PopupMessage(viewer.AttachedEntity, message);
}
}
public bool InRange(GridCoordinates from, GridCoordinates to)
{
return from.InRange(_mapManager, to, 15);
}
public bool TryPoint(ICommonSession? session, GridCoordinates coords, EntityUid uid)
{
var player = session?.AttachedEntity;
if (player == null)
{
return false;
}
if (_pointers.TryGetValue(session!, out var lastTime) &&
_gameTiming.CurTime < lastTime + PointDelay)
{
return false;
}
if (!InRange(coords, player.Transform.GridPosition))
{
player.PopupMessage(player, Loc.GetString("You can't reach there!"));
return false;
}
var diff = coords.ToMapPos(_mapManager) - player.Transform.MapPosition.Position;
if (diff.LengthSquared > 0.01f)
{
player.Transform.LocalRotation = new Angle(diff);
}
var viewers = _playerManager.GetPlayersInRange(player.Transform.GridPosition, 15);
EntityManager.SpawnEntity("pointingarrow", coords);
string selfMessage;
string viewerMessage;
string? viewerPointedAtMessage = null;
if (EntityManager.TryGetEntity(uid, out var pointed))
{
selfMessage = player == pointed
? Loc.GetString("You point at yourself.")
: Loc.GetString("You point at {0:theName}.", pointed);
viewerMessage = player == pointed
? $"{player.Name} {Loc.GetString("points at {0:themself}.", player)}"
: $"{player.Name} {Loc.GetString("points at {0:theName}.", pointed)}";
viewerPointedAtMessage = $"{player.Name} {Loc.GetString("points at you.")}";
}
else
{
var tileRef = _mapManager.GetGrid(coords.GridID).GetTileRef(coords);
var tileDef = _tileDefinitionManager[tileRef.Tile.TypeId];
selfMessage = Loc.GetString("You point at {0}.", tileDef.DisplayName);
viewerMessage = $"{player.Name} {Loc.GetString("points at {0}.", tileDef.DisplayName)}";
}
_pointers[session!] = _gameTiming.CurTime;
SendMessage(player, viewers, pointed, selfMessage, viewerMessage, viewerPointedAtMessage);
return true;
}
public override void Initialize()
{
base.Initialize();
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
EntityQuery = new TypeEntityQuery(typeof(PointingArrowComponent));
CommandBinds.Builder
.Bind(ContentKeyFunctions.Point, new PointerInputCmdHandler(TryPoint))
.Register<PointingSystem>();
}
public override void Shutdown()
{
base.Shutdown();
_playerManager.PlayerStatusChanged -= OnPlayerStatusChanged;
_pointers.Clear();
}
public override void Update(float frameTime)
{
foreach (var entity in RelevantEntities)
{
entity.GetComponent<PointingArrowComponent>().Update(frameTime);
}
}
}
}

View File

@@ -0,0 +1,26 @@
using Content.Server.GameObjects.Components.Pointing;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
namespace Content.Server.GameObjects.EntitySystems
{
[UsedImplicitly]
public class RoguePointingSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
EntityQuery = new TypeEntityQuery(typeof(RoguePointingArrowComponent));
}
public override void Update(float frameTime)
{
foreach (var entity in RelevantEntities)
{
entity.GetComponent<RoguePointingArrowComponent>().Update(frameTime);
}
}
}
}

View File

@@ -0,0 +1,53 @@
using Content.Server.GameObjects.Components.Pointing;
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.GameObjects;
using Robust.Server.Interfaces.GameObjects;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Localization;
namespace Content.Server.GlobalVerbs
{
/// <summary>
/// Global verb that points at an entity.
/// </summary>
[GlobalVerb]
public class PointingVerb : GlobalVerb
{
public override bool RequireInteractionRange => false;
public override void GetData(IEntity user, IEntity target, VerbData data)
{
data.Visibility = VerbVisibility.Invisible;
if (!user.HasComponent<IActorComponent>())
{
return;
}
if (!EntitySystem.Get<PointingSystem>().InRange(user.Transform.GridPosition, target.Transform.GridPosition))
{
return;
}
if (target.HasComponent<PointingArrowComponent>())
{
return;
}
data.Visibility = VerbVisibility.Visible;
data.Text = Loc.GetString("Point at");
}
public override void Activate(IEntity user, IEntity target)
{
if (!user.TryGetComponent(out IActorComponent actor))
{
return;
}
EntitySystem.Get<PointingSystem>().TryPoint(actor.playerSession, target.Transform.GridPosition, target.Uid);
}
}
}

View File

@@ -0,0 +1,11 @@
using System;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
namespace Content.Shared.GameObjects.Components.Pointing
{
public class SharedPointingArrowComponent : Component
{
public sealed override string Name => "PointingArrow";
}
}

View File

@@ -0,0 +1,17 @@
using System;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
namespace Content.Shared.GameObjects.Components.Pointing
{
public class SharedRoguePointingArrowComponent : Component
{
public sealed override string Name => "RoguePointingArrow";
}
[Serializable, NetSerializable]
public enum RoguePointingArrowVisuals
{
Rotation
}
}

View File

@@ -29,5 +29,6 @@ namespace Content.Shared.Input
public static readonly BoundKeyFunction OpenTileSpawnWindow = "OpenTileSpawnWindow";
public static readonly BoundKeyFunction TakeScreenshot = "TakeScreenshot";
public static readonly BoundKeyFunction TakeScreenshotNoUI = "TakeScreenshotNoUI";
public static readonly BoundKeyFunction Point = "Point";
}
}

View File

@@ -0,0 +1,15 @@
- type: entity
name: pointing arrow
id: pointingarrow
components:
- type: Sprite
netsync: false
sprite: Interface/Misc/pointing.rsi
state: pointing
- type: PointingArrow
duration: 4
step: 0.5
speed: 1
- type: Appearance
visuals:
- type: RoguePointingArrowVisualizer

View File

@@ -0,0 +1,16 @@
{
"version": 1,
"size": {
"x": 32,
"y": 32
},
"license": "CC-BY-SA-3.0",
"copyright": "https://github.com/tgstation/tgstation/tree/96ac5662eb4977aedbff7ddc061eee100b3bf365",
"states": [
{
"name": "pointing",
"directions": 1,
"delays": [[1]]
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 631 B

View File

@@ -256,3 +256,7 @@ binds:
type: state
key: Tab
mod1: Shift
- function: Point
type: State
key: MouseMiddle
mod1: Shift