Skip to content

Commit

Permalink
Make melee great again (#1051)
Browse files Browse the repository at this point in the history
## Описание PR
<!-- Что вы изменили в этом пулл реквесте? -->
Была добавлена новая механика - граб. Когда игрок тащит кого-либо, он
может усилить захват с лёгкого до удушающего.
Всего у граба присутствует три стадии - слабый, сильный и удушающий.
Переход со стадии на стадию производится через DoAfter.
Чтобы выбраться из захвата требуется так же дождаться завершения
DoAfter'а.
Каждая стадия имеет настройку количества требуемых рук, скорости
захвата, скорости попытки выбраться и число этих попыток для успеха.
Когда игрок держит сущность в захвате, он может:
- Ударить его головой об стол, перетащив модельку оппонента на него
- Кинуть оппонента в сторону, сбивая им всех, кто находится на пути
(учитывая самого инициатора!)

## Почему / Баланс
<!-- Почему оно было изменено? Ссылайтесь на любые обсуждения или
вопросы здесь. Пожалуйста, обсудите, как это повлияет на игровой баланс.
-->
1. Было в сс13
2. Сырость и скука нынешней боёвки
3. Старый мой проект, на который официальные разработчики положили
большой и толстый

## TODO
- [x] Убедиться, что критических багов нет
- [x] _Возможно_ добавить урон при столкновении со стеной после броска
грабом
- [x] Локализация и доделать попапы
- [x] Тесты на деве для спонсоров
- [x] Запрет разговора на удушающем
- [x] Выпадение предметов из рук на удушающем

## Техническая информация
если б вы знали, как мне впадлу, вы бы расплакались.

## Медиа
ыы

## Требования
- [ ] Я прочитал(а) и следую [Руководство по созданию пулл
реквестов](https://docs.spacestation14.com/en/general-development/codebase-info/pull-request-guidelines.html).
Я понимаю, что в противном случае мой ПР может быть закрыт по усмотрению
мейнтейнера.
- [ ] Я добавил скриншоты/видео к этому пулл реквесту, демонстрирующие
его изменения в игре, **или** этот пулл реквест не требует демонстрации
в игре

## Критические изменения
мяу. Тут был Шрёдя @Schrodinger71 

**Чейнджлог**
:cl: Котя
- add: Добавлена механика граба! Ctrl+ЛКМ в комбат моде ещё никогда не
был так полезен...
- tweak: Дизарм больше не является зависящей от рандома атакой. Каждые 3
удара на ПКМ (подряд!) происходит дизарм/толчок.

---------

Co-authored-by: Schrödinger <[email protected]>
  • Loading branch information
FaDeOkno and Schrodinger71 authored Feb 5, 2025
1 parent c9c679e commit e040fa1
Show file tree
Hide file tree
Showing 43 changed files with 1,424 additions and 42 deletions.
72 changes: 72 additions & 0 deletions Content.Client/ADT/Grab/PullingSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
using Content.Client.Effects;
using Content.Client.Popups;
using Content.Shared.IdentityManagement;
using Content.Shared.Movement.Pulling.Components;
using Content.Shared.Movement.Pulling.Systems;
using Content.Shared.Popups;
using Robust.Client.Audio;
using Robust.Shared.Audio;
using Robust.Shared.Player;

namespace Content.Client.ADT.Pulling.Systems;

/// <summary>
/// Allows one entity to pull another behind them via a physics distance joint.
/// </summary>
public sealed partial class ClientPullingSystem : PullingSystem
{
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly AudioSystem _audio = default!;
[Dependency] private readonly ColorFlashEffectSystem _color = default!;

public override bool TryIncreaseGrabStage(Entity<PullerComponent> puller, Entity<PullableComponent> pullable)
{
if (!base.TryIncreaseGrabStage(puller, pullable))
return false;

var targetStage = puller.Comp.Stage + 1;
// Switch the popup type based on the new grab stage
var popupType = targetStage switch
{
GrabStage.Soft => PopupType.Small,
GrabStage.Hard => PopupType.SmallCaution,
GrabStage.Choke => PopupType.MediumCaution,
_ => PopupType.Small,
};

// Do grab stage change effects
_popup.PopupPredicted(
Loc.GetString($"grab-increase-{targetStage.ToString().ToLower()}-popup-self",
("target", Identity.Entity(pullable, EntityManager))),
Loc.GetString($"grab-increase-{targetStage.ToString().ToLower()}-popup-others",
("target", Identity.Entity(pullable, EntityManager)),
("puller", Identity.Entity(pullable, EntityManager))),
pullable, puller, popupType);
_audio.PlayPredicted(new SoundPathSpecifier("/Audio/Effects/thudswoosh.ogg"), pullable, puller);
_color.RaiseEffect(Color.Yellow, new List<EntityUid>() { pullable.Owner }, Filter.Pvs(pullable.Owner));

return true;
}

public override bool TryLowerGrabStage(Entity<PullerComponent> puller, Entity<PullableComponent> pullable, EntityUid user)
{
if (!base.TryLowerGrabStage(puller, pullable, user))
return false;

var targetStage = puller.Comp.Stage - 1;
// Do grab stage change effects
if (user != pullable.Owner)
{
_popup.PopupPredicted(
Loc.GetString($"grab-lower-{targetStage.ToString().ToLower()}-popup-self",
("target", Identity.Entity(pullable, EntityManager))),
Loc.GetString($"grab-lower-{targetStage.ToString().ToLower()}-popup-others",
("target", Identity.Entity(pullable, EntityManager)),
("puller", Identity.Entity(pullable, EntityManager))),
pullable, puller, PopupType.Small);
_audio.PlayPredicted(new SoundPathSpecifier("/Audio/Effects/thudswoosh.ogg"), pullable, puller);
}

return true;
}
}
127 changes: 127 additions & 0 deletions Content.Server/ADT/Grab/PullingSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
using System.Numerics;
using Content.Server.Administration.Logs;
using Content.Server.Popups;
using Content.Shared.ADT.Grab;
using Content.Shared.Database;
using Content.Shared.IdentityManagement;
using Content.Shared.Movement.Pulling.Components;
using Content.Shared.Movement.Pulling.Systems;
using Content.Shared.Popups;
using Content.Shared.Speech;
using Content.Shared.Tag;
using Content.Shared.Throwing;
using Robust.Server.Audio;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Map;
using Robust.Shared.Player;

namespace Content.Server.ADT.Pulling.Systems;

/// <summary>
/// Allows one entity to pull another behind them via a physics distance joint.
/// </summary>
public sealed partial class ServerPullingSystem : PullingSystem
{
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly AudioSystem _audio = default!;
[Dependency] private readonly ThrowingSystem _throwing = default!;
[Dependency] private readonly TransformSystem _transform = default!;
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
[Dependency] private readonly TagSystem _tag = default!;

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

SubscribeLocalEvent<PullableComponent, SpeakAttemptEvent>(OnPullableSpeakAttempt);
}

private void OnPullableSpeakAttempt(EntityUid uid, PullableComponent comp, SpeakAttemptEvent args)
{
if (!TryComp<PullerComponent>(comp.Puller, out var puller) || puller.Stage < GrabStage.Choke)
return;
if (_tag.HasTag(uid, "ADTIgnoreChokeSpeechBlocking"))
return;

// Ran only on server so we dont care about prediction
_popup.PopupEntity(Loc.GetString("grab-speech-attempt-choke"), uid, uid, PopupType.SmallCaution);
args.Cancel();
}

public override bool TryIncreaseGrabStage(Entity<PullerComponent> puller, Entity<PullableComponent> pullable)
{
if (!base.TryIncreaseGrabStage(puller, pullable))
return false;

puller.Comp.Stage++;
// Switch the popup type based on the new grab stage
var popupType = puller.Comp.Stage switch
{
GrabStage.Soft => PopupType.Small,
GrabStage.Hard => PopupType.SmallCaution,
GrabStage.Choke => PopupType.MediumCaution,
_ => PopupType.Small,
};

// Do grab stage change effects
_popup.PopupPredicted(
Loc.GetString($"grab-increase-{puller.Comp.Stage.ToString().ToLower()}-popup-self",
("target", Identity.Entity(pullable, EntityManager))),
Loc.GetString($"grab-increase-{puller.Comp.Stage.ToString().ToLower()}-popup-others",
("target", Identity.Entity(pullable, EntityManager)),
("puller", Identity.Entity(pullable, EntityManager))),
pullable, puller, popupType);
_audio.PlayPredicted(new SoundPathSpecifier("/Audio/Effects/thudswoosh.ogg"), pullable, puller);
Dirty(puller);

_adminLogger.Add(LogType.Grab, LogImpact.Low,
$"{ToPrettyString(puller):user} increased grab stage to {puller.Comp.Stage} while grabbing {ToPrettyString(pullable):target}");

return true;
}

public override bool TryLowerGrabStage(Entity<PullerComponent> puller, Entity<PullableComponent> pullable, EntityUid user)
{
if (!base.TryLowerGrabStage(puller, pullable, user))
return false;
puller.Comp.Stage--;

// Do grab stage change effects
if (user != pullable.Owner)
{
_popup.PopupPredicted(
Loc.GetString($"grab-lower-{puller.Comp.Stage.ToString().ToLower()}-popup-self",
("target", Identity.Entity(pullable, EntityManager))),
Loc.GetString($"grab-lower-{puller.Comp.Stage.ToString().ToLower()}-popup-others",
("target", Identity.Entity(pullable, EntityManager)),
("puller", Identity.Entity(pullable, EntityManager))),
pullable, puller, PopupType.Small);
_audio.PlayPredicted(new SoundPathSpecifier("/Audio/Effects/thudswoosh.ogg"), pullable, puller);
}

Dirty(puller);

return true;
}

public override void Throw(Entity<PullerComponent> puller, Entity<PullableComponent> pullable, EntityCoordinates coords)
{
base.Throw(puller, pullable, coords);

var xform = Transform(pullable);
var startCoords = _transform.ToMapCoordinates(xform.Coordinates);
var targCoords = _transform.ToMapCoordinates(coords);
Vector2 vector = Vector2.Clamp(targCoords.Position - startCoords.Position, new(-2.5f), new(2.5f));
Vector2 selfVec = Vector2.Clamp(new(-vector.X, -vector.Y), new(-0.25f), new(0.25f));


_audio.PlayPvs(new SoundPathSpecifier("/Audio/Effects/thudswoosh.ogg"), pullable);
EnsureComp<GrabThrownComponent>(pullable);
_throwing.TryThrow(pullable, vector, 8, animated: false, playSound: false, doSpin: false);
_throwing.TryThrow(puller, selfVec, 5, animated: false, playSound: false, doSpin: false);

_adminLogger.Add(LogType.Grab, LogImpact.Low,
$"{ToPrettyString(puller):user} thrown {ToPrettyString(pullable):target} by grab");
}
}
3 changes: 2 additions & 1 deletion Content.Server/Body/Systems/RespiratorSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Content.Shared.Movement.Pulling.Components;

namespace Content.Server.Body.Systems;

Expand Down Expand Up @@ -84,7 +85,7 @@ public override void Update(float frameTime)
// ADT tweak end
UpdateSaturation(uid, multiplier * (float) respirator.UpdateInterval.TotalSeconds, respirator); // ADT: use multiplier instead of negating

if (!_mobState.IsIncapacitated(uid)) // cannot breathe in crit.
if (!_mobState.IsIncapacitated(uid) && !(TryComp<PullableComponent>(uid, out var pullable) && TryComp<PullerComponent>(pullable.Puller, out var puller) && puller.Stage == GrabStage.Choke)) // cannot breathe in crit. // ADT grab tweak
{
switch (respirator.Status)
{
Expand Down
26 changes: 25 additions & 1 deletion Content.Server/Hands/Systems/HandsSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Content.Server.Stack;
using Content.Server.Stunnable;
using Content.Shared.ActionBlocker;
using Content.Shared.ADT.Hands;
using Content.Shared.Body.Part;
using Content.Shared.CombatMode;
using Content.Shared.Damage.Systems;
Expand Down Expand Up @@ -137,10 +138,18 @@ private void HandlePullStarted(EntityUid uid, HandsComponent component, PullStar
if (TryComp<PullerComponent>(args.PullerUid, out var pullerComp) && !pullerComp.NeedsHands)
return;

if (!_virtualItemSystem.TrySpawnVirtualItemInHand(args.PulledUid, uid))
if (!_virtualItemSystem.TrySpawnVirtualItemInHand(args.PulledUid, uid, out var virtualItem)) // ADT Grab tweaked
{
DebugTools.Assert("Unable to find available hand when starting pulling??");
}

// ADT Grab start
if (pullerComp != null)
{
pullerComp.VirtualItems.Add(GetNetEntity(virtualItem.Value));
Dirty(args.PullerUid, pullerComp);
}
// ADT Grab end
}

private void HandlePullStopped(EntityUid uid, HandsComponent component, PullStoppedMessage args)
Expand Down Expand Up @@ -191,6 +200,21 @@ hands.ActiveHandEntity is not { } throwEnt ||
return false;
hands.NextThrowTime = _timing.CurTime + hands.ThrowCooldown;

// ADT Grab start
if (TryComp<VirtualItemComponent>(throwEnt, out var virtualItem))
{
var userEv = new BeforeVirtualItemThrownEvent(virtualItem.BlockingEntity, player, coordinates);
RaiseLocalEvent(player, userEv);

var targEv = new BeforeVirtualItemThrownEvent(virtualItem.BlockingEntity, player, coordinates);
RaiseLocalEvent(virtualItem.BlockingEntity, targEv);

if (userEv.Cancelled || targEv.Cancelled)
return false;
}
// ADT Grab end


if (EntityManager.TryGetComponent(throwEnt, out StackComponent? stack) && stack.Count > 1 && stack.ThrowIndividually)
{
var splitStack = _stackSystem.Split(throwEnt, 1, EntityManager.GetComponent<TransformComponent>(player).Coordinates, stack);
Expand Down
24 changes: 15 additions & 9 deletions Content.Server/Weapons/Melee/MeleeWeaponSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,19 +135,25 @@ protected override bool DoDisarm(EntityUid user, DisarmAttackEvent ev, EntityUid
if (attemptEvent.Cancelled)
return false;

var chance = CalculateDisarmChance(user, target, inTargetHand, combatMode);

if (_random.Prob(chance))
{
// Yknow something tells me this comment is hilariously out of date...
// Don't play a sound as the swing is already predicted.
// Also don't play popups because most disarms will miss.
// ADT Disarm tweak start
if (component.DisarmAttemptsCount < component.ShovesToDisarm)
return false;
}

component.DisarmAttemptsCount = 0;
// var chance = CalculateDisarmChance(user, target, inTargetHand, combatMode);

// if (_random.Prob(chance))
// {
// // Yknow something tells me this comment is hilariously out of date...
// // Don't play a sound as the swing is already predicted.
// // Also don't play popups because most disarms will miss.
// return false;
// }
// ADT Disarm tweak end

AdminLogger.Add(LogType.DisarmedAction, $"{ToPrettyString(user):user} used disarm on {ToPrettyString(target):target}");

var eventArgs = new DisarmedEvent { Target = target, Source = user, PushProbability = 1 - chance };
var eventArgs = new DisarmedEvent { Target = target, Source = user, PushProbability = 0.22f }; // ADT Disarm tweak
RaiseLocalEvent(target, eventArgs);

if (!eventArgs.Handled)
Expand Down
7 changes: 7 additions & 0 deletions Content.Shared.Database/LogType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -444,4 +444,11 @@ public enum LogType
/// A player interacted with a PDA or its cartridge component
/// </summary>
PdaInteract = 96,

// ADT Start
/// <summary>
/// A player grabbed another player
/// </summary>
Grab = 97,
/// ADT End
}
12 changes: 12 additions & 0 deletions Content.Shared/ADT/Grab/Components/GrabThrownComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Robust.Shared.GameStates;

namespace Content.Shared.ADT.Grab;

[RegisterComponent, NetworkedComponent]
public sealed partial class GrabThrownComponent : Component
{
public int MaxCollides = 2;
public int CollideCounter = 0;

public List<EntityUid> HitEntities = new();
}
11 changes: 11 additions & 0 deletions Content.Shared/ADT/Grab/Components/ModifyGrabStageTimeComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using Content.Shared.Movement.Pulling.Components;
using Robust.Shared.GameStates;

namespace Content.Shared.ADT.Grab;

[RegisterComponent, NetworkedComponent]
public sealed partial class ModifyGrabStageTimeComponent : Component
{
[DataField(required: true)]
public Dictionary<GrabStage, float> Modifiers = new();
}
8 changes: 8 additions & 0 deletions Content.Shared/ADT/Grab/Events/GrabEscapeDoAfterEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using Content.Shared.DoAfter;
using Content.Shared.Movement.Pulling.Components;
using Robust.Shared.Serialization;

namespace Content.Shared.ADT.Grab;

[Serializable, NetSerializable]
public sealed partial class GrabEscapeDoAfterEvent : SimpleDoAfterEvent;
6 changes: 6 additions & 0 deletions Content.Shared/ADT/Grab/Events/GrabStageChangedEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using Content.Shared.Movement.Pulling.Components;

namespace Content.Shared.ADT.Grab;

[ByRefEvent]
public record struct GrabStageChangedEvent(Entity<PullerComponent> Puller, Entity<PullableComponent> Pulling, GrabStage OldStage, GrabStage NewStage);
10 changes: 10 additions & 0 deletions Content.Shared/ADT/Grab/Events/ModifyGrabStageTimeEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Content.Shared.Inventory;
using Content.Shared.Movement.Pulling.Components;

namespace Content.Shared.ADT.Grab;

[ByRefEvent]
public record struct ModifyGrabStageTimeEvent(GrabStage Stage, float Modifier = 1f) : IInventoryRelayEvent
{
public SlotFlags TargetSlots => SlotFlags.WITHOUT_POCKET;
}
16 changes: 16 additions & 0 deletions Content.Shared/ADT/Grab/Events/SetGrabStageDoAfterEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Content.Shared.DoAfter;
using Content.Shared.Movement.Pulling.Components;
using Robust.Shared.Serialization;

namespace Content.Shared.ADT.Grab;

[Serializable, NetSerializable]
public sealed partial class SetGrabStageDoAfterEvent : SimpleDoAfterEvent
{
public int Direction;

public SetGrabStageDoAfterEvent(int direction)
{
Direction = direction;
}
}
Loading

0 comments on commit e040fa1

Please sign in to comment.