6
2026-01-24 12:49:55 +03:00

403 lines
13 KiB
C#

using System.Numerics;
using Content.Shared._RMC14.Attachable.Events;
using Content.Shared.Actions;
using Content.Shared.Camera;
using Content.Shared.DoAfter;
using Content.Shared.Hands;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction;
using Content.Shared.Movement.Pulling.Systems;
using Content.Shared.Movement.Systems;
using Content.Shared.Popups;
using Content.Shared.Toggleable;
using Content.Shared.Weapons.Ranged.Components;
using Content.Shared.Weapons.Ranged.Systems;
using Content.Shared.Wieldable;
using Content.Shared.Wieldable.Components;
using Robust.Shared.Containers;
namespace Content.Shared._RMC14.Scoping;
public abstract partial class SharedScopeSystem : EntitySystem
{
[Dependency] private readonly ActionContainerSystem _actionContainer = default!;
[Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly SharedContentEyeSystem _contentEye = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly SharedEyeSystem _eye = default!;
[Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly PullingSystem _pulling = default!;
public override void Initialize()
{
InitializeUser();
SubscribeLocalEvent<ScopeComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<ScopeComponent, ComponentRemove>(OnShutdown);
SubscribeLocalEvent<ScopeComponent, EntityTerminatingEvent>(OnScopeEntityTerminating);
SubscribeLocalEvent<ScopeComponent, GotUnequippedHandEvent>(OnUnequip);
SubscribeLocalEvent<ScopeComponent, HandDeselectedEvent>(OnDeselectHand);
SubscribeLocalEvent<ScopeComponent, ItemUnwieldedEvent>(OnUnwielded);
SubscribeLocalEvent<ScopeComponent, GetItemActionsEvent>(OnGetActions);
SubscribeLocalEvent<ScopeComponent, ToggleActionEvent>(OnToggleAction);
SubscribeLocalEvent<ScopeComponent, ScopeCycleZoomLevelEvent>(OnCycleZoomLevel);
SubscribeLocalEvent<ScopeComponent, ActivateInWorldEvent>(OnActivateInWorld);
SubscribeLocalEvent<ScopeComponent, GunShotEvent>(OnGunShot);
SubscribeLocalEvent<ScopeComponent, ScopeDoAfterEvent>(OnScopeDoAfter);
SubscribeLocalEvent<GunScopingComponent, GotUnequippedHandEvent>(OnGunUnequip);
SubscribeLocalEvent<GunScopingComponent, HandDeselectedEvent>(OnGunDeselectHand);
SubscribeLocalEvent<GunScopingComponent, ItemUnwieldedEvent>(OnGunUnwielded);
SubscribeLocalEvent<GunScopingComponent, GunShotEvent>(OnGunGunShot);
}
private void OnMapInit(Entity<ScopeComponent> ent, ref MapInitEvent args)
{
_actionContainer.EnsureAction(ent.Owner, ref ent.Comp.ScopingToggleActionEntity, ent.Comp.ScopingToggleAction);
if (ent.Comp.ZoomLevels.Count > 1)
_actionContainer.EnsureAction(ent.Owner, ref ent.Comp.CycleZoomLevelActionEntity, ent.Comp.CycleZoomLevelAction);
Dirty(ent.Owner, ent.Comp);
}
private void OnShutdown(Entity<ScopeComponent> ent, ref ComponentRemove args)
{
if (ent.Comp.User is not { } user)
return;
Unscope(ent);
_actionsSystem.RemoveProvidedActions(user, ent.Owner);
}
private void OnScopeEntityTerminating(Entity<ScopeComponent> ent, ref EntityTerminatingEvent args)
{
Unscope(ent);
}
private void OnUnequip(Entity<ScopeComponent> ent, ref GotUnequippedHandEvent args)
{
Unscope(ent);
}
private void OnDeselectHand(Entity<ScopeComponent> ent, ref HandDeselectedEvent args)
{
Unscope(ent);
}
private void OnUnwielded(Entity<ScopeComponent> ent, ref ItemUnwieldedEvent args)
{
if (ent.Comp.RequireWielding)
Unscope(ent);
}
private void OnGetActions(Entity<ScopeComponent> ent, ref GetItemActionsEvent args)
{
args.AddAction(ref ent.Comp.ScopingToggleActionEntity, ent.Comp.ScopingToggleAction);
if (ent.Comp.ZoomLevels.Count > 1)
args.AddAction(ref ent.Comp.CycleZoomLevelActionEntity, ent.Comp.CycleZoomLevelAction);
}
private void OnToggleAction(Entity<ScopeComponent> ent, ref ToggleActionEvent args)
{
if (args.Handled)
return;
args.Handled = true;
ToggleScoping(ent, args.Performer);
}
private void OnCycleZoomLevel(Entity<ScopeComponent> scope, ref ScopeCycleZoomLevelEvent args)
{
if (args.Handled)
return;
args.Handled = true;
if (scope.Comp.CurrentZoomLevel >= scope.Comp.ZoomLevels.Count - 1)
scope.Comp.CurrentZoomLevel = 0;
else
++scope.Comp.CurrentZoomLevel;
var zoomLevel = GetCurrentZoomLevel(scope);
if (zoomLevel.Name != null)
_popup.PopupClient(Loc.GetString("rcm-action-popup-scope-cycle-zoom", ("zoom", zoomLevel.Name)), args.Performer, args.Performer);
Dirty(scope);
}
private void OnActivateInWorld(Entity<ScopeComponent> ent, ref ActivateInWorldEvent args)
{
if (args.Handled || !args.Complex || !ent.Comp.UseInHand)
return;
args.Handled = true;
ToggleScoping(ent, args.User);
}
private void OnGunShot(Entity<ScopeComponent> ent, ref GunShotEvent args)
{
var dir = Transform(args.User).LocalRotation.GetCardinalDir();
if (ent.Comp.ScopingDirection != dir)
Unscope(ent);
}
private void OnScopeDoAfter(Entity<ScopeComponent> ent, ref ScopeDoAfterEvent args)
{
if (args.Handled)
return;
args.Handled = true;
if (args.Cancelled)
{
DeleteRelay(ent, args.User);
return;
}
var user = args.User;
if (!CanScopePopup(ent, user))
{
DeleteRelay(ent, args.User);
return;
}
Scope(ent, user, args.Direction);
}
private void OnGunUnequip(Entity<GunScopingComponent> ent, ref GotUnequippedHandEvent args)
{
UnscopeGun(ent);
}
private void OnGunDeselectHand(Entity<GunScopingComponent> ent, ref HandDeselectedEvent args)
{
UnscopeGun(ent);
}
private void OnGunUnwielded(Entity<GunScopingComponent> ent, ref ItemUnwieldedEvent args)
{
UnscopeGun(ent);
}
private void OnGunGunShot(Entity<GunScopingComponent> ent, ref GunShotEvent args)
{
var dir = Transform(args.User).LocalRotation.GetCardinalDir();
if (TryComp(ent.Comp.Scope, out ScopeComponent? scope) && scope.ScopingDirection != dir)
UnscopeGun(ent);
}
private bool CanScopePopup(Entity<ScopeComponent> scope, EntityUid user)
{
var ent = scope.Owner;
if (scope.Comp.Attachment && !TryGetActiveEntity(scope, out ent))
{
var msgError = Loc.GetString("cm-action-popup-scoping-must-attach", ("scope", ent));
_popup.PopupClient(msgError, user, user);
return false;
}
if (!_hands.TryGetActiveItem(user, out var heldItem) || !scope.Comp.Attachment && heldItem != scope.Owner)
{
var msgError = Loc.GetString("cm-action-popup-scoping-user-must-hold", ("scope", ent));
_popup.PopupClient(msgError, user, user);
return false;
}
if (_pulling.IsPulled(user))
{
var msgError = Loc.GetString("cm-action-popup-scoping-user-must-not-pulled", ("scope", ent));
_popup.PopupClient(msgError, user, user);
return false;
}
if (_container.IsEntityInContainer(user))
{
var msgError = Loc.GetString("cm-action-popup-scoping-user-must-not-contained", ("scope", ent));
_popup.PopupClient(msgError, user, user);
return false;
}
if (scope.Comp.RequireWielding &&
TryComp(ent, out WieldableComponent? wieldable) &&
!wieldable.Wielded)
{
var msgError = Loc.GetString("cm-action-popup-scoping-user-must-wield", ("scope", ent));
_popup.PopupClient(msgError, user, user);
return false;
}
return true;
}
protected virtual Direction? StartScoping(Entity<ScopeComponent> scope, EntityUid user)
{
if (!CanScopePopup(scope, user))
return null;
// TODO RMC14 make this work properly with rotations
var xform = Transform(user);
var cardinalDir = xform.LocalRotation.GetCardinalDir();
var ev = new ScopeDoAfterEvent(cardinalDir);
var zoomLevel = GetCurrentZoomLevel(scope);
var doAfter = new DoAfterArgs(EntityManager, user, zoomLevel.DoAfter, ev, scope, null, scope)
{
BreakOnMove = !zoomLevel.AllowMovement
};
if (_doAfter.TryStartDoAfter(doAfter))
return cardinalDir;
return null;
}
private void Scope(Entity<ScopeComponent> scope, EntityUid user, Direction direction)
{
if (TryComp(user, out ScopingComponent? scoping))
UserStopScoping((user, scoping));
var zoomLevel = GetCurrentZoomLevel(scope);
scope.Comp.User = user;
scope.Comp.ScopingDirection = direction;
Dirty(scope);
scoping = EnsureComp<ScopingComponent>(user);
scoping.Scope = scope;
scoping.AllowMovement = zoomLevel.AllowMovement;
Dirty(user, scoping);
if (scope.Comp.Attachment && TryGetActiveEntity(scope, out var active))
{
var gunScoping = EnsureComp<GunScopingComponent>(active);
gunScoping.Scope = scope;
Dirty(active, gunScoping);
}
var targetOffset = GetScopeOffset(scope, direction);
scoping.EyeOffset = targetOffset;
var msgUser = Loc.GetString("cm-action-popup-scoping-user", ("scope", scope.Owner));
_popup.PopupClient(msgUser, user, user);
_actionsSystem.SetToggled(scope.Comp.ScopingToggleActionEntity, true);
_contentEye.SetZoom(user, Vector2.One * zoomLevel.Zoom, true);
UpdateOffset(user);
}
protected virtual bool Unscope(Entity<ScopeComponent> scope)
{
if (scope.Comp.User is not { } user)
return false;
RemCompDeferred<ScopingComponent>(user);
if (scope.Comp.Attachment && TryGetActiveEntity(scope, out var active))
RemCompDeferred<GunScopingComponent>(active);
if (scope.Comp.Attachment && scope.Comp.User != null)
{
var interruptEvent = new AttachableToggleableInterruptEvent(scope.Comp.User.Value);
RaiseLocalEvent(scope.Owner, ref interruptEvent);
}
scope.Comp.User = null;
scope.Comp.ScopingDirection = null;
Dirty(scope);
var msgUser = Loc.GetString("cm-action-popup-scoping-stopping-user", ("scope", scope.Owner));
_popup.PopupClient(msgUser, user, user);
_actionsSystem.SetToggled(scope.Comp.ScopingToggleActionEntity, false);
_contentEye.ResetZoom(user);
return true;
}
private void UnscopeGun(Entity<GunScopingComponent> gun)
{
if (TryComp(gun.Comp.Scope, out ScopeComponent? scope))
Unscope((gun.Comp.Scope.Value, scope));
}
private void ToggleScoping(Entity<ScopeComponent> scope, EntityUid user)
{
if (HasComp<ScopingComponent>(user))
{
Unscope(scope);
if (TryComp(user, out ScopingComponent? scoping))
UserStopScoping((user, scoping));
return;
}
StartScoping(scope, user);
}
private bool TryGetActiveEntity(Entity<ScopeComponent> scope, out EntityUid active)
{
if (!scope.Comp.Attachment)
{
active = scope;
return true;
}
if (!_container.TryGetContainingContainer((scope, null), out var container) ||
!HasComp<GunComponent>(container.Owner))
{
active = default;
return false;
}
active = container.Owner;
return true;
}
protected Vector2 GetScopeOffset(Entity<ScopeComponent> scope, Direction direction)
{
var zoomLevel = GetCurrentZoomLevel(scope);
return direction.ToVec() * ((zoomLevel.Offset * zoomLevel.Zoom - 1) / 2);
}
protected virtual void DeleteRelay(Entity<ScopeComponent> scope, EntityUid? user)
{
}
private ScopeZoomLevel GetCurrentZoomLevel(Entity<ScopeComponent> scope)
{
ValidateCurrentZoomLevel(scope);
return scope.Comp.ZoomLevels[scope.Comp.CurrentZoomLevel];
}
private void ValidateCurrentZoomLevel(Entity<ScopeComponent> scope)
{
bool dirty = false;
if (scope.Comp.ZoomLevels == null || scope.Comp.ZoomLevels.Count <= 0)
{
scope.Comp.ZoomLevels = new List<ScopeZoomLevel>(){ new ScopeZoomLevel(null, 1f, 15, false, TimeSpan.FromSeconds(1)) };
dirty = true;
}
if (scope.Comp.CurrentZoomLevel >= scope.Comp.ZoomLevels.Count)
{
scope.Comp.CurrentZoomLevel = 0;
dirty = true;
}
if (dirty)
Dirty(scope);
}
private void UpdateOffset(EntityUid user)
{
var ev = new GetEyeOffsetEvent();
RaiseLocalEvent(user, ref ev);
_eye.SetOffset(user, ev.Offset);
}
}