Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
using System;
using System.Linq;
using System.Numerics;
using Content.Goobstation.Common.BlockTeleport;
using Content.Goobstation.Common.Weapons;
using Content.Pirate.Shared._JustDecor.Weapons.Melee;
using Content.Shared.ActionBlocker;
using Content.Shared.Interaction;
using Content.Shared.Movement.Events;
using Content.Shared.Movement.Systems;
using Content.Shared.Physics;
using Content.Shared.Weapons.Melee;
using Content.Shared.Weapons.Melee.Events;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Maths;
using Robust.Shared.Network;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Content.Shared.Interaction.Events;

namespace Content.Pirate.Shared._JustDecor.Weapons.Melee;

public sealed class SharedTeleportStrikeSystem : EntitySystem
{
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedTransformSystem _xform = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly SharedMeleeWeaponSystem _melee = default!;
[Dependency] private readonly MovementSpeedModifierSystem _movementSpeed = default!;
[Dependency] private readonly RotateToFaceSystem _rotateToFace = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly INetManager _net = default!;

/// <summary>
/// Delay before performing the attack after teleporting.
/// </summary>
private const float AttackDelay = 0.1f;

/// <summary>
/// Additional delay added to return time after attack.
/// </summary>
private const float ExtraReturnDelay = 0.2f;

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

SubscribeLocalEvent<TeleportStrikeComponent, GetLightAttackRangeEvent>(OnGetRange);
SubscribeLocalEvent<TeleportStrikeComponent, LightAttackSpecialInteractionEvent>(OnSpecialAttack);
SubscribeLocalEvent<TeleportStrikeLockComponent, RefreshMovementSpeedModifiersEvent>(OnRefreshMovespeed);
SubscribeLocalEvent<TeleportStrikeLockComponent, ChangeDirectionAttemptEvent>(OnChangeDirection);
}

private void OnGetRange(Entity<TeleportStrikeComponent> ent, ref GetLightAttackRangeEvent args)
{
if (_net.IsServer)
return;

args.Range = Math.Max(args.Range, ent.Comp.MaxRange);
}

private void OnSpecialAttack(Entity<TeleportStrikeComponent> ent, ref LightAttackSpecialInteractionEvent args)
{
if (_net.IsClient)
return;

if (args.Target == null)
return;

var user = args.User;
var target = args.Target.Value;

if (HasComp<TeleportStrikeLockComponent>(user))
return;

if (!TryComp(ent, out MeleeWeaponComponent? melee) || melee.NextAttack > _timing.CurTime)
return;

var userXform = Transform(user);
var targetXform = Transform(target);

if (userXform.MapID != targetXform.MapID)
return;

var userPos = _xform.GetWorldPosition(userXform);
var targetPos = _xform.GetWorldPosition(targetXform);

var dir = targetPos - userPos;
var distance = dir.Length();

if (distance <= args.Range || distance > ent.Comp.MaxRange)
return;

var ev = new TeleportAttemptEvent(false);
RaiseLocalEvent(user, ref ev);
if (ev.Cancelled)
return;

var normalized = new Vector2(dir.X / distance, dir.Y / distance);
var ray = new CollisionRay(
userPos,
normalized,
(int) (CollisionGroup.Impassable | CollisionGroup.InteractImpassable));

var result = _physics.IntersectRay(userXform.MapID, ray, distance, user).FirstOrNull();
if (result != null && result.Value.HitEntity != target)
return;

var behindPos = targetPos + normalized * ent.Comp.BehindOffset;

var originalCoords = userXform.Coordinates;
var originalVelocity = Vector2.Zero;
if (TryComp<PhysicsComponent>(user, out var physics))
{
originalVelocity = physics.LinearVelocity;
_physics.SetLinearVelocity(user, Vector2.Zero, body: physics);
}

var lockComp = EnsureComp<TeleportStrikeLockComponent>(user);
lockComp.ReturnCoordinates = originalCoords;
lockComp.ReturnVelocity = originalVelocity;
lockComp.Target = target;
lockComp.Weapon = ent.Owner;
lockComp.AttackTime = _timing.CurTime + TimeSpan.FromSeconds(AttackDelay);
lockComp.ReturnTime = _timing.CurTime + TimeSpan.FromSeconds(ent.Comp.ReturnDelay + ExtraReturnDelay);
Dirty(user, lockComp);

_movementSpeed.RefreshMovementSpeedModifiers(user);

if (ent.Comp.TeleportSound != null)
_audio.PlayPredicted(ent.Comp.TeleportSound, user, user);

// Teleport behind the target
_xform.SetWorldPosition(user, behindPos);

// Calculate and set rotation to face the target
var dirToTarget = targetPos - behindPos;
if (dirToTarget.LengthSquared() > 0.01f)
{
var angle = Angle.FromWorldVec(dirToTarget);
_xform.SetWorldRotation(user, angle);
}

args.Cancel = true;
}

private void OnRefreshMovespeed(EntityUid uid, TeleportStrikeLockComponent comp, ref RefreshMovementSpeedModifiersEvent args)
{
args.ModifySpeed(0f, 0f);
}

private void OnChangeDirection(EntityUid uid, TeleportStrikeLockComponent comp, ChangeDirectionAttemptEvent args)
{
args.Cancel();
}

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

if (_net.IsClient)
return;

var query = EntityQueryEnumerator<TeleportStrikeLockComponent>();
while (query.MoveNext(out var uid, out var lockComp))
{
if (!Exists(uid))
continue;

// Perform attack when attack time is reached
if (lockComp.AttackTime != TimeSpan.Zero && _timing.CurTime >= lockComp.AttackTime)
{
lockComp.AttackTime = TimeSpan.Zero;
Dirty(uid, lockComp);

if (TryComp<MeleeWeaponComponent>(lockComp.Weapon, out var melee) && Exists(lockComp.Target))
{
_melee.AttemptLightAttack(uid, lockComp.Weapon, melee, lockComp.Target);

// Set cooldown after the attack
melee.NextAttack += TimeSpan.FromSeconds(0.2);
Dirty(lockComp.Weapon, melee);
}
}

// Return to original position after return time
if (_timing.CurTime < lockComp.ReturnTime)
continue;

_xform.SetCoordinates(uid, lockComp.ReturnCoordinates);

if (TryComp<PhysicsComponent>(uid, out var physics))
_physics.SetLinearVelocity(uid, lockComp.ReturnVelocity, body: physics);

RemComp<TeleportStrikeLockComponent>(uid);
_movementSpeed.RefreshMovementSpeedModifiers(uid);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Robust.Shared.Audio;

namespace Content.Pirate.Shared._JustDecor.Weapons.Melee;

[RegisterComponent]
public sealed partial class TeleportStrikeComponent : Component
{
[DataField]
public float MaxRange = 7f;

[DataField]
public float BehindOffset = 0.5f;

[DataField]
public float ReturnDelay = 0.25f;

[DataField]
public SoundSpecifier? TeleportSound;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System;
using System.Numerics;
using Robust.Shared.GameStates;
using Robust.Shared.Map;

namespace Content.Pirate.Shared._JustDecor.Weapons.Melee;

[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class TeleportStrikeLockComponent : Component
{
[DataField, AutoNetworkedField]
public EntityCoordinates ReturnCoordinates;

[DataField, AutoNetworkedField]
public Vector2 ReturnVelocity;

[DataField, AutoNetworkedField]
public TimeSpan ReturnTime;

[DataField, AutoNetworkedField]
public TimeSpan AttackTime;

[DataField, AutoNetworkedField]
public EntityUid Target;

[DataField, AutoNetworkedField]
public EntityUid Weapon;
}
22 changes: 22 additions & 0 deletions Resources/Locale/uk-UA/_Pirate/clothing/slot-blocker.ftl
Original file line number Diff line number Diff line change
@@ -1,2 +1,24 @@
slot-blocker-blocked-generic = Ви повинні зняти {THE($blocker)}, щоб зробити це!
slot-blocker-blocked-equipped = Ви повинні зняти {THE($blocker)}, щоб одягнути це!
slot-blocker-blocked-unequipped = Ви повинні зняти {THE($blocker)}, щоб зняти це!

slot-blocker-examine-blocks = Це може блокувати наступні слоти при екіпіруванні: [bold]{$slots}[/bold]
slot-blocker-examine-blocked-by = Це може бути заблоковано наступними слотами: [bold]{$slots}[/bold]

# Named generic slot flags. Add more if more get added, but do not add slot flags that are not supposed to be named (e.g. WITHOUT_POCKET)
# We do not use the names from InventoryTemplate because those character-specific and they suck.
slot-name-HEAD = голова
slot-name-EYES = очі
slot-name-EARS = вуха
slot-name-MASK = маска
slot-name-OUTERCLOTHING = верхній одяг
slot-name-INNERCLOTHING = нижній одяг
slot-name-NECK = шия
slot-name-BACK = спина
slot-name-BELT = пояс
slot-name-GLOVES = руки
slot-name-IDCARD = КПК
slot-name-POCKET = кишені
slot-name-LEGS = ноги
slot-name-FEET = стопи
slot-name-SUITSTORAGE = зберігання костюма
Loading