Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Garrote #5

Draft
wants to merge 15 commits into
base: master
Choose a base branch
from
16 changes: 16 additions & 0 deletions Content.Server/Stories/Garrote/GarroteComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Content.Shared.Damage;

namespace Content.Server.Stories.Garrote;

[RegisterComponent]
public sealed partial class GarroteComponent : Component
{
[DataField("doAfterTime")]
public TimeSpan DoAfterTime = TimeSpan.FromSeconds(0.5f);

[DataField("damage")]
public DamageSpecifier Damage = default!;

[DataField("maxUseDistance")]
public float MaxUseDistance = 0.5f;
}
167 changes: 167 additions & 0 deletions Content.Server/Stories/Garrote/GarroteSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
using Content.Server.Body.Components;
using Content.Server.Body.Systems;
using Content.Server.Chat.Systems;
using Content.Server.Popups;
using Content.Shared.ActionBlocker;
using Content.Shared.Body.Components;
using Content.Shared.Chat.Prototypes;
using Content.Shared.Damage;
using Content.Shared.DoAfter;
using Content.Shared.Interaction;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Popups;
using Content.Shared.Speech.Muting;
using Content.Shared.StatusEffect;
using Content.Shared.Stunnable;
using Content.Shared.Wieldable.Components;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Content.Shared.Stories.Garrote;

namespace Content.Server.Stories.Garrote;

public sealed class GarroteSystem : EntitySystem
{
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
[Dependency] private readonly DamageableSystem _damageable = default!;
[Dependency] private readonly ChatSystem _chatSystem = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly RespiratorSystem _respirator = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly StatusEffectsSystem _statusEffect = default!;
[Dependency] private readonly SharedStunSystem _stun = default!;

public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<GarroteComponent, AfterInteractEvent>(OnGarroteAttempt);
SubscribeLocalEvent<GarroteComponent, GarroteDoAfterEvent>(OnGarroteDoAfter);
}

private void OnGarroteAttempt(EntityUid uid, GarroteComponent comp, ref AfterInteractEvent args)
{
if (args.User == args.Target
|| !HasComp<BodyComponent>(args.Target)
|| !HasComp<DamageableComponent>(args.Target)
|| !TryComp<MobStateComponent>(args.Target, out var mobstate))
return;

if (TryComp<WieldableComponent>(uid, out var wieldable) && !wieldable.Wielded)
{
var message = Loc.GetString("wieldable-component-requires", ("item", uid));
_popupSystem.PopupEntity(message, uid, args.User);
return;
}

if (!(mobstate.CurrentState == MobState.Alive && HasComp<RespiratorComponent>(args.Target)))
{
var message = Loc.GetString("garrote-component-doesnt-breath", ("target", args.Target));
_popupSystem.PopupEntity(message, args.Target.Value, args.User);
return;
}

if (!TryComp(args.User, out TransformComponent? userTransform))
return;

if (!TryComp(args.Target.Value, out TransformComponent? targetTransform))
return;

if (!args.CanReach || !IsRightTargetDistance(userTransform, targetTransform, comp.MaxUseDistance))
{
var message = Loc.GetString("garrote-component-too-far-away", ("target", args.Target));
_popupSystem.PopupEntity(message, args.Target.Value, args.User);
return;
}

if (GetEntityDirection(userTransform) != GetEntityDirection(targetTransform) && _actionBlocker.CanInteract(args.Target.Value, null))
{
var message = Loc.GetString("garrote-component-must-be-behind", ("target", args.Target));
_popupSystem.PopupEntity(message, args.Target.Value, args.User);
return;
}

var messagetarget = Loc.GetString("garrote-component-started-target", ("user", args.User));
_popupSystem.PopupEntity(messagetarget, args.User, args.Target.Value, PopupType.LargeCaution);

var messageothers = Loc.GetString("garrote-component-started-others", ("user", args.User), ("target", args.Target));
_popupSystem.PopupEntity(messageothers, args.User, Filter.PvsExcept(args.Target.Value), true, PopupType.MediumCaution);

var doAfterEventArgs = new DoAfterArgs(EntityManager, args.User, comp.DoAfterTime, new GarroteDoAfterEvent(), uid, target: args.Target)
{
BreakOnMove = true,
BreakOnDamage = true,
NeedHand = true,
DuplicateCondition = DuplicateConditions.SameTool,
DistanceThreshold = 0.1f
};

if (!_doAfter.TryStartDoAfter(doAfterEventArgs))
return;

ProtoId<EmotePrototype> emote = "Cough";
_chatSystem.TryEmoteWithChat(args.Target.Value, emote, ChatTransmitRange.HideChat, ignoreActionBlocker: true);

_stun.TryStun(args.Target.Value, 2*comp.DoAfterTime, true); // multiplying time by 2 to prevent mispredictons
_statusEffect.TryAddStatusEffect<MutedComponent>(args.Target.Value, "Muted", 2*comp.DoAfterTime, true);
}

private void OnGarroteDoAfter(EntityUid uid, GarroteComponent comp, GarroteDoAfterEvent args)
{
if (args.Target == null
|| !TryComp<DamageableComponent>(args.Target, out var damageable)
|| !TryComp<RespiratorComponent>(args.Target, out var respirator)
|| !TryComp<MobStateComponent>(args.Target, out var mobstate))
return;

if (args.Cancelled || mobstate.CurrentState != MobState.Alive)
return;

_damageable.TryChangeDamage(args.Target, comp.Damage, false, origin: args.User);

var saturationDelta = respirator.MinSaturation - respirator.Saturation;
_respirator.UpdateSaturation(args.Target.Value, saturationDelta, respirator);

_stun.TryStun(args.Target.Value, 2*comp.DoAfterTime, true);
_statusEffect.TryAddStatusEffect<MutedComponent>(args.Target.Value, "Muted", 2*comp.DoAfterTime, true);

args.Repeat = true;
}

/// <summary>
/// Checking whether the distance from the user to the target is set correctly.
/// </summary>
/// <remarks>
/// Does not check for the presence of TransformComponent.
/// </remarks>
private bool IsRightTargetDistance(TransformComponent user, TransformComponent target, float maxUseDistance)
{
if (Math.Abs(user.LocalPosition.X - target.LocalPosition.X) <= maxUseDistance
&& Math.Abs(user.LocalPosition.Y - target.LocalPosition.Y) <= maxUseDistance)
return true;
else
return false;
}

/// <remarks>
/// Does not check for the presence of TransformComponent.
/// </remarks>
private Direction GetEntityDirection(TransformComponent entityTransform)
{
double entityLocalRotation;

if (entityTransform.LocalRotation.Degrees < 0)
entityLocalRotation = 360 - Math.Abs(entityTransform.LocalRotation.Degrees);
else
entityLocalRotation = entityTransform.LocalRotation.Degrees;

if(entityLocalRotation > 43.5d && entityLocalRotation < 136.5d)
return Direction.East;
else if(entityLocalRotation >= 136.5d && entityLocalRotation <= 223.5d)
return Direction.North;
else if(entityLocalRotation > 223.5d && entityLocalRotation < 316.5d)
return Direction.West;
else
return Direction.South;
}
Comment on lines +146 to +166
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Мб захочешь предложить реализацию получше (но если так, то пж скажи в каком классе искать метод...)

}
10 changes: 10 additions & 0 deletions Content.Shared/Stories/Garrote/GarroteEvents.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Content.Shared.DoAfter;
using Robust.Shared.Serialization;

namespace Content.Shared.Stories.Garrote;

[Serializable, NetSerializable]

public sealed partial class GarroteDoAfterEvent : SimpleDoAfterEvent
{
}
5 changes: 5 additions & 0 deletions Resources/Locale/ru-RU/_stories/garrote/garrote-component.ftl
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
garrote-component-doesnt-breath = { $target } уже не дышит!
garrote-component-must-be-behind = Вы должны быть сзади { $target }.
garrote-component-too-far-away = Вы слишком далеко, чтобы использовать удавку!
garrote-component-started-target = { $user } начинает удушать вас!
garrote-component-started-others = { $user } начинает удушать { $target }!
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
- type: entity
parent: BaseItem
id: Garrote
name: удавка
description: Приспособление, используемое не иначе, как для скрытных убийств.
components:
- type: Sprite
sprite: Stories/Objects/Weapons/Special/garrote.rsi
state: icon
- type: Item
size: Small
- type: Garrote
damage:
types:
Asphyxiation: 5
- type: Wieldable
13 changes: 13 additions & 0 deletions Resources/Prototypes/_Stories/Catalog/uplink_catalog.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,16 @@
whitelist:
tags:
- NukeOpsUplink

- type: listing
id: UplinkGarrote
name: Удавка
description: Излюбленный профессионалами инструмент для скрытых убийств. Позволяет взять стоящую к вам спиной жертву в мёртвую хватку, неминуемо убивая её.
productEntity: Garrote
discountCategory: usualDiscounts
discountDownTo:
Telecrystal: 2
cost:
Telecrystal: 4
categories:
- UplinkWeaponry
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "Created by Shegare(discord:shegare).",
"size": {
"x": 32,
"y": 32
},
"states": [
{
"name": "icon"
},
{
"name": "inhand-left",
"directions": 4
},
{
"name": "inhand-right",
"directions": 4
},
{
"name": "wielded-inhand-left",
"directions": 4
},
{
"name": "wielded-inhand-right",
"directions": 4
}
]
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading