Skip to content

Commit

Permalink
Aiming system (#499)
Browse files Browse the repository at this point in the history
* FollowCamera

* fix follow distance

* one more shit

* Fixes and polishing

---------

Co-authored-by: JerryImMouse <[email protected]>
  • Loading branch information
Vonsant and JerryImMouse authored Sep 4, 2024
1 parent 9807371 commit 50cd154
Show file tree
Hide file tree
Showing 24 changed files with 540 additions and 6 deletions.
86 changes: 86 additions & 0 deletions Content.Client/_NC/CameraFollow/CameraFollowSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// License granted by JerryImMouse to Corvax Forge.
// Non-exclusive, non-transferable, perpetual license to use, distribute, and modify.
// All other rights reserved by JerryImMouse.
using System.Numerics;
using Content.Shared._NC.CameraFollow.Components;
using Content.Shared._NC.CameraFollow.Events;
using Content.Shared._NC.FollowDistance.Components;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.Player;
using Robust.Shared.Timing;

namespace Content.Client._NC.CameraFollow;

/// <summary>
/// Use to make camera follow for player's mouse, uses Lerp() func. to follow player's mouse
/// </summary>
public sealed class CameraFollowSystem : EntitySystem
{
[Dependency] private readonly IInputManager _input = default!;
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IEyeManager _manager = default!;
[Dependency] private readonly TransformSystem _transform = default!;

public override void FrameUpdate(float frameTime)
{
base.FrameUpdate(frameTime);

// Check if mouse position is valid
if (!_timing.IsFirstTimePredicted || !_input.MouseScreenPosition.IsValid)
return;

var player = _player.LocalSession?.AttachedEntity;

if (player == null || !TryComp<CameraFollowComponent>(player, out var followComponent))
return;
if (!TryComp<EyeComponent>(player, out var eye) || !followComponent.Enabled)
return;

// Get player map position and eye offset
var xform = Transform(player.Value);
var playerPos = _transform.GetMapCoordinates(xform).Position;
var eyeOffset = new Vector2(eye.Offset.X, eye.Offset.Y);

// Get mouse position on map
var coords = _input.MouseScreenPosition;
var mapPos = _manager.ScreenToMap(coords);

// Get currentLerpProgress and difference between player position and mouse position
float currentLerpTime = 0;
currentLerpTime += frameTime;
if (currentLerpTime > followComponent.LerpTime)
{
currentLerpTime = followComponent.LerpTime;
}

// Counts diff between player and mouse position
// Counts our progress through lerp, don't touch it please, idk how it works.
var lerpProgress = currentLerpTime / followComponent.LerpTime;
var diff = new Vector2(mapPos.X, mapPos.Y) - playerPos;

// Checks if player pointed his mouse on UI
if (mapPos is { X: 0, Y: 0 })
return;

var offset = followComponent.Offset;
// If eye offset is higher than max distance lerp offset to max distance and apply "back strength"
// TODO: Prob need to change the multiplication of lerpProgress to 4f, because this can cause the camera to jerk around


if (Math.Abs(eyeOffset.X) >= Math.Abs(followComponent.MaxDistance.X) ||
Math.Abs(eyeOffset.Y) >= Math.Abs(followComponent.MaxDistance.Y))
{
offset = Vector2.Lerp(offset, followComponent.MaxDistance, lerpProgress * followComponent.BackStrength);
}
// Just lerp to calculated offset position
offset = Vector2.Lerp(offset, diff, lerpProgress);

RaisePredictiveEvent(new ChangeCamOffsetEvent
{
Offset = offset
});
}
}
19 changes: 19 additions & 0 deletions Content.Shared/Camera/GetEyeOffsetEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System.Numerics;
using Content.Shared.Movement.Systems;

namespace Content.Shared.Camera;

/// <summary>
/// Raised directed by-ref when <see cref="SharedContentEyeSystem.UpdateEyeOffset"/> is called.
/// Should be subscribed to by any systems that want to modify an entity's eye offset,
/// so that they do not override each other.
/// </summary>
/// <param name="Offset">
/// The total offset to apply.
/// </param>
/// <remarks>
/// Note that in most cases <see cref="Offset"/> should be incremented or decremented by subscribers, not set.
/// Otherwise, any offsets applied by previous subscribing systems will be overridden.
/// </remarks>
[ByRefEvent]
public record struct GetEyeOffsetEvent(Vector2 Offset);
34 changes: 29 additions & 5 deletions Content.Shared/Camera/SharedCameraRecoilSystem.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Numerics;
using Content.Shared._NC.CameraFollow.Components; // NC changes
using Content.Shared.Actions; // NC changes
using JetBrains.Annotations;
using Robust.Shared.Player;
using Robust.Shared.Serialization;
Expand Down Expand Up @@ -29,6 +31,7 @@ public abstract class SharedCameraRecoilSystem : EntitySystem
protected const float KickMagnitudeMax = 1f;

[Dependency] private readonly SharedEyeSystem _eye = default!;
[Dependency] private readonly SharedActionsSystem _actionsSystem = default!; // NC changes

/// <summary>
/// Applies explosion/recoil/etc kickback to the view of the entity.
Expand All @@ -37,21 +40,29 @@ public abstract class SharedCameraRecoilSystem : EntitySystem
/// If the entity is missing <see cref="CameraRecoilComponent" /> and/or <see cref="EyeComponent" />,
/// this call will have no effect. It is safe to call this function on any entity.
/// </remarks>

/// NC changes start
public override void Initialize()
{
SubscribeLocalEvent<CameraFollowComponent, ComponentInit>(OnCameraFollowInit);
}
/// NC changes end
public abstract void KickCamera(EntityUid euid, Vector2 kickback, CameraRecoilComponent? component = null);

public override void FrameUpdate(float frameTime)
public override void Update(float frameTime) // NC changes
{
base.FrameUpdate(frameTime);

var query = AllEntityQuery<EyeComponent, CameraRecoilComponent>();
var query = AllEntityQuery<EyeComponent, CameraRecoilComponent, CameraFollowComponent>(); // NC changes

while (query.MoveNext(out var uid, out var eye, out var recoil))
while (query.MoveNext(out var uid, out var eye, out var recoil, out var follow)) // NC changes
{
var magnitude = recoil.CurrentKick.Length();
if (magnitude <= 0.005f)
{
recoil.CurrentKick = Vector2.Zero;
_eye.SetOffset(uid, recoil.BaseOffset + recoil.CurrentKick, eye);
var offset = recoil.BaseOffset + recoil.CurrentKick + (follow.Enabled ? follow.Offset : Vector2.Zero); // NC-Changes
_eye.SetOffset(uid, offset, eye); // NC-Changes
}
else // Continually restore camera to 0.
{
Expand All @@ -66,10 +77,23 @@ public override void FrameUpdate(float frameTime)

recoil.CurrentKick = new Vector2(x, y);

_eye.SetOffset(uid, recoil.BaseOffset + recoil.CurrentKick, eye);
var offset = recoil.BaseOffset + recoil.CurrentKick + (follow.Enabled ? follow.Offset : Vector2.Zero); // NC-Changes
_eye.SetOffset(uid, offset, eye); // NC changes
}
}
}

/// NC changes start
private void OnCameraFollowInit(EntityUid uid, CameraFollowComponent component, ComponentInit args)
{
_actionsSystem.AddAction(uid, ref component.ActionEntity, component.Action);
}

private void OnCameraFollowRemove(EntityUid uid, CameraFollowComponent component, ComponentRemove args)
{
_actionsSystem.RemoveAction(uid, component.ActionEntity);
}
/// NC changes end
}

[Serializable]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// License granted by JerryImMouse to Corvax Forge.
// Non-exclusive, non-transferable, perpetual license to use, distribute, and modify.
// All other rights reserved by JerryImMouse.
using System.Numerics;
using Content.Shared.Actions;
using Content.Shared._NC.CameraFollow.EntitySystems;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;

namespace Content.Shared._NC.CameraFollow.Components;
/// <summary>
/// Component to set eye offset to make camera follow player's mouse
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class CameraFollowComponent : Component
{
// TODO: Probably need to remove max distance cause it doesn't influence on max distance lol
[DataField, AutoNetworkedField]
public Vector2 MaxDistance = new(0.0001f, 0.0001f);

[DataField, AutoNetworkedField]
public Vector2 DefaultMaxDistance = new(0.0001f, 0.0001f);

[DataField, AutoNetworkedField]
public Vector2 Offset;

[DataField("lerpTime")]
public float LerpTime = 1f;

// Max distance controller btw
// I think its broken cause it looks weird
[DataField("backStrength"), AutoNetworkedField]
public float BackStrength = 10f;

[DataField("defaultBackStrength")]
public float DefaultBackStrength = 10f;

[DataField("action", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string Action = "ActionToggleCamera";

// Action entity to remove it from player on component remove
public EntityUid? ActionEntity;

[DataField, AutoNetworkedField]
public bool Enabled = false;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// License granted by JerryImMouse to Corvax Forge.
// Non-exclusive, non-transferable, perpetual license to use, distribute, and modify.
// All other rights reserved by JerryImMouse.
using System.Numerics;
using Content.Shared._NC.CameraFollow.Components;
using Content.Shared._NC.CameraFollow.Events;
using Content.Shared.Bed.Sleep;
using Content.Shared.Stunnable;

namespace Content.Shared._NC.CameraFollow.EntitySystems;

public sealed class CameraActionsSystem : EntitySystem
{
[Dependency] private readonly SharedEyeSystem _eye = default!;

public override void Initialize()
{
base.Initialize();

SubscribeLocalEvent<CameraFollowComponent, ToggleCameraEvent>(OnToggleCamera);
SubscribeLocalEvent<CameraFollowComponent, SleepStateChangedEvent>(OnSleeping);
}

private void OnSleeping(Entity<CameraFollowComponent> ent, ref SleepStateChangedEvent args)
{
SetCameraEnabled(ent, false);
Dirty(ent);
}

private void OnToggleCamera(EntityUid uid, CameraFollowComponent component, ToggleCameraEvent args)
{
if (HasComp<SleepingComponent>(uid) || HasComp<StunnedComponent>(uid)) // Check if entity is sleeping right now(when sleeping entity has a shader without shadows, it can cause wallhacking)
{
args.Handled = true;
return;
}

SetCameraEnabled((uid,component), !component.Enabled);
Dirty(uid, component);
args.Handled = true;
}


/// <summary>
/// Sets the enabled state of the camera on server and client side
/// </summary>
/// <param name="component">CameraFollowComponent</param>
/// <param name="enabled">Enabled boolean value</param>
private void SetCameraEnabled(Entity<CameraFollowComponent> component, bool enabled)
{
component.Comp.Enabled = enabled;
component.Comp.Offset = new Vector2(0, 0);
_eye.SetOffset(component, component.Comp.Offset);
}
}
21 changes: 21 additions & 0 deletions Content.Shared/_NC/CameraFollow/Events/ToggleCameraEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// License granted by JerryImMouse to Corvax Forge.
// Non-exclusive, non-transferable, perpetual license to use, distribute, and modify.
// All other rights reserved by JerryImMouse.
using System.Numerics;
using Content.Shared.Actions;
using Robust.Shared.Serialization;

namespace Content.Shared._NC.CameraFollow.Events;

/// <summary>
/// Turns on/off the camera following the player's mouse position.
/// </summary>
public sealed partial class ToggleCameraEvent : InstantActionEvent
{
}

[Serializable, NetSerializable]
public sealed partial class ChangeCamOffsetEvent : EntityEventArgs
{
public Vector2 Offset = Vector2.Zero;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// License granted by JerryImMouse to Corvax Forge.
// Non-exclusive, non-transferable, perpetual license to use, distribute, and modify.
// All other rights reserved by JerryImMouse.
using System.Numerics;
using Robust.Shared.Serialization;

namespace Content.Shared._NC.FollowDistance.Components;

/// <summary>
/// Component to set new values for <see cref="CameraFollow"/>
/// </summary>
[RegisterComponent]
public sealed partial class FollowDistanceComponent : Component
{
// Probably don't touch this field in your prototype, this can cause unpredictable behavior
// But you can try...
[DataField("maxDistance"), ViewVariables(VVAccess.ReadWrite)]
public Vector2 MaxDistance = new(0.0001f, 0.0001f);

// Change this field in your prototype to set max distance, the lower the value, the more the player will be able to see.
// DON'T SET THIS TO 0, it will cause max distance wouldn't work and player will be able to look all over the map.
// If you want to set a value lower than 1, use float values, but never 0 and less.
[DataField("backStrength"), ViewVariables(VVAccess.ReadWrite)]
public float BackStrength = 4f;

// This fields for default player's max distance and back strength
// This will set automatically on player pickup entity with FollowDistanceComponent
[DataField, ViewVariables(VVAccess.ReadOnly)]
public Vector2 DefaultMaxDistance = new Vector2(0.0001f, 0.0001f);

[DataField, ViewVariables(VVAccess.ReadOnly)]
public float DefaultBackStrength = 4f;
}
Loading

0 comments on commit 50cd154

Please sign in to comment.