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

Взрывной ошейник #508

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
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
71 changes: 71 additions & 0 deletions Content.Server/_Sunrise/ExpCollars/ExpCollarComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using Content.Shared.Damage;
using Robust.Shared.Audio;

namespace Content.Server._Sunrise.ExpCollars;

/// <summary>
/// Компонент для взрывного ошейника
/// </summary>
[RegisterComponent, Access(typeof(ExpCollarsSystem))]
public sealed partial class ExpCollarComponent : Component
{
/// <summary>
/// Является ли красным
/// </summary>
[DataField(readOnly: true)]
public bool IsHost;

/// <summary>
/// Привязанные ошейники
/// </summary>
[DataField(readOnly: true)]
public List<EntityUid> Linked = new();

/// <summary>
/// Взведен ли механизм
/// </summary>
[DataField(readOnly: true)]
public bool Armed;

/// <summary>
/// Звук
/// </summary>
[DataField(readOnly: true)]
public SoundSpecifier BeepSound;

/// <summary>
/// Сколько урона наносится при взрыве носителю
/// </summary>
[DataField(readOnly: true)]
public DamageSpecifier Damage;

/// <summary>
/// "Девственность" взрывного механизма
/// </summary>
[DataField(readOnly: true)]
public bool Virgin = true;

/// <summary>
/// Айди сущности, которая считается текущим носителем
/// </summary>
[DataField(readOnly: true)]
public EntityUid? Wearer;

/// <summary>
/// Разница между временем сколько снимает ошейник носитель и сосед если он не взведен
/// </summary>
[DataField(readOnly: true)]
public TimeSpan InitialStripDelay = TimeSpan.FromSeconds(0);

/// <summary>
/// Разница между временем сколько снимает ошейник носитель и сосед если он взведен
/// </summary>
[DataField(readOnly: true)]
public TimeSpan ArmedStripDelay = TimeSpan.FromSeconds(30);

/// <summary>
/// Идет ли асинхронная функция кулдауна на ошейнике
/// </summary>
[DataField(readOnly: true)]
public bool ActiveCooldown;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Content.Server._Sunrise.ExpCollars;

[RegisterComponent]
public sealed partial class ExpCollarUserComponent : Component
{
[DataField(readOnly: true)]
public EntityUid? Tool;
}
223 changes: 223 additions & 0 deletions Content.Server/_Sunrise/ExpCollars/ExpCollarsSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
using System.Linq;
using System.Threading.Tasks;
using Content.Server.Explosion.EntitySystems;
using Content.Server.Popups;
using Content.Shared.Clothing;
using Content.Shared.Clothing.Components;
using Content.Shared.Clothing.EntitySystems;
using Content.Shared.Damage;
using Content.Shared.Examine;
using Content.Shared.Interaction;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Systems;
using Content.Shared.Popups;
using Content.Shared.Tag;
using Robust.Server.Audio;

namespace Content.Server._Sunrise.ExpCollars;

/// <summary>
/// Система для взрывного ошейника.
/// </summary>
public sealed class ExpCollarsSystem : EntitySystem
{
[Dependency] private readonly AudioSystem _audio = default!;
[Dependency] private readonly DamageableSystem _damageable = default!;
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
[Dependency] private readonly TriggerSystem _trigger = default!;
[Dependency] private readonly ClothingSystem _clothing = default!;
[Dependency] private readonly TagSystem _tag = default!;

/// <inheritdoc/>
public override void Initialize()
{
SubscribeLocalEvent<ExpCollarComponent, AfterInteractEvent>(OnInteraction);
SubscribeLocalEvent<ExpCollarUserComponent, MobStateChangedEvent>(OnMobStateChanged);
SubscribeLocalEvent<ExpCollarComponent, ClothingGotEquippedEvent>(OnEquipped);
SubscribeLocalEvent<ExpCollarComponent, ClothingGotUnequippedEvent>(OnUnequipped);
SubscribeLocalEvent<ExpCollarComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<ExpCollarComponent, ComponentShutdown>(OnShutdown);
SubscribeLocalEvent<ExpCollarComponent, ExaminedEvent>(OnExamined);
}

private void OnInteraction(EntityUid uid, ExpCollarComponent component, AfterInteractEvent args)
{
if (args.Handled || !args.CanReach || !component.IsHost || args.Target == null || !TryComp(args.Target, out ExpCollarComponent? targetComp) || targetComp.IsHost)
return;

if (targetComp.Linked.Count != 0 || !targetComp.Virgin)
{
_popup.PopupEntity(Loc.GetString("expcollar-connected"), args.Target.Value, PopupType.SmallCaution);
return;
}

_popup.PopupEntity(Loc.GetString("expcollar-connect"), args.Target.Value, PopupType.MediumCaution);
component.Linked.Add(args.Target.Value);
targetComp.Linked.Add(uid);
}

private async void OnMobStateChanged(EntityUid uid, ExpCollarUserComponent component, MobStateChangedEvent args)
{
if (component.Tool == null || !(args.OldMobState == MobState.Alive && args.NewMobState != MobState.Alive))
return;

if (!TryComp<ExpCollarComponent>(component.Tool, out var expCollarComponent))
return;

if (expCollarComponent.Armed == false || !expCollarComponent.IsHost)
return;

Destroy(component.Tool.Value);
foreach (var linkedCollar in expCollarComponent.Linked)
{
if (linkedCollar == null)
return;
_popup.PopupEntity(Loc.GetString("expcollar-kill"), linkedCollar, PopupType.LargeCaution);
Destroy(linkedCollar);
}
}

private void OnEquipped(EntityUid uid, ExpCollarComponent component, ClothingGotEquippedEvent args)
{
if (TryComp<ExpCollarUserComponent>(args.Wearer, out _))
return;

var comp = EnsureComp<ExpCollarUserComponent>(args.Wearer);
comp.Tool = uid;
component.Wearer = args.Wearer;

if (!_mobState.IsAlive(args.Wearer))
return;

if (component is { Armed: false, IsHost: false })
{
var a = component.Linked.FirstOrDefault();
if (!TryComp<ExpCollarComponent>(a, out var hostCollarComponent))
return;
if (hostCollarComponent.Armed)
{
component.Armed = true;
_popup.PopupEntity(Loc.GetString("expcollar-armed"), uid, PopupType.LargeCaution);
}
}

if (component.IsHost)
{
component.Armed = true;
_tag.AddTag(uid, "CannotSuicide");
_popup.PopupEntity(Loc.GetString("expcollar-armed"), args.Wearer, PopupType.LargeCaution);
foreach (var i in component.Linked)
{
if (!TryComp<ExpCollarComponent>(i, out var expCollarComponent))
continue;

if (expCollarComponent.Wearer == null)
continue;

expCollarComponent.Armed = true;
EnsureComp<SelfUnremovableClothingComponent>(i);
if (TryComp<ClothingComponent>(i, out var iClothingComponent))
_clothing.SetStripDelay((i, iClothingComponent), TimeSpan.FromSeconds(30));
_popup.PopupEntity(Loc.GetString("expcollar-armed"), i, PopupType.LargeCaution);
}
}

if (component.Armed)
{
EnsureComp<SelfUnremovableClothingComponent>(uid);
if (TryComp<ClothingComponent>(uid, out var clothingComponent))
_clothing.SetStripDelay((uid, clothingComponent), TimeSpan.FromSeconds(30));
}
}

private void OnUnequipped(EntityUid uid, ExpCollarComponent component, ClothingGotUnequippedEvent args)
{
if (!TryComp<ExpCollarUserComponent>(args.Wearer, out var expCollarUserComponent))
return;

component.Wearer = null;
component.Armed = false;
component.Virgin = false;
RemComp<ExpCollarUserComponent>(args.Wearer);
if (HasComp<SelfUnremovableClothingComponent>(uid))
RemComp<SelfUnremovableClothingComponent>(uid);
if (TryComp<ClothingComponent>(uid, out var clothingComponent))
_clothing.SetStripDelay((uid, clothingComponent), TimeSpan.FromSeconds(10));
}

private void OnComponentInit(EntityUid uid, ExpCollarComponent component, ComponentInit args)
{
if (!TryComp<ClothingComponent>(uid, out var clothingComponent))
return;

// А вот нехуй в лоадауты такую штуку пихать. Надевать ее надо.
if (clothingComponent.InSlot != null)
QueueDel(uid);

_clothing.SetStripDelay((uid, clothingComponent), component.InitialStripDelay);
}

private void OnShutdown(EntityUid uid, ExpCollarComponent component, ComponentShutdown args)
{
if (component.Wearer == null)
return;

if (!TryComp<ExpCollarUserComponent>(component.Wearer, out var expCollarUserComponent)) // На него насильно надели...
return;
RemComp<ExpCollarUserComponent>(component.Wearer.Value);
}

private async void Destroy(EntityUid uid)
{
if (!TryComp<ExpCollarComponent>(uid, out var collar))
return;
if (collar.Wearer == null)
{
// Больше нельзя использовать
RemComp<ExpCollarComponent>(uid);
return;
}

if (collar.Armed == false)
return;

if (uid == null)
return;
_popup.PopupEntity(Loc.GetString("expcollar-boom"), uid, PopupType.LargeCaution);
_audio.PlayPvs(collar.BeepSound, uid);
await Task.Delay(TimeSpan.FromSeconds(1));

for (var i = 10; i > 0; i--)
{
if (uid == null)
return;
_popup.PopupEntity(Loc.GetString("expcollar-popup", ("timer", i)), uid, PopupType.LargeCaution);
_audio.PlayPvs(collar.BeepSound, uid);
await Task.Delay(TimeSpan.FromSeconds(1));
}

if (HasComp<ExpCollarUserComponent>(collar.Wearer.Value))
{
_damageable.TryChangeDamage(collar.Wearer.Value, collar.Damage);
_trigger.Trigger(uid);
}
}

private void OnExamined(EntityUid uid, ExpCollarComponent component, ExaminedEvent args)
{
args.PushMarkup(component.Armed
? Loc.GetString("expcollar-examine-armed")
: Loc.GetString("expcoller-examine-disarmed"));
if (component.Wearer == null)
{
args.PushMarkup(component.Virgin
? Loc.GetString("expcollar-examine-virgin")
: Loc.GetString("expcollar-examine-unvirgin"));
if (component.Linked.Count != 0)
{
args.PushMarkup(Loc.GetString("expcollar-examine-linked"));
}
}
}
}
5 changes: 5 additions & 0 deletions Content.Shared/Clothing/EntitySystems/ClothingSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -274,4 +274,9 @@ public void SetLayerState(ClothingComponent clothing, string slot, string mapKey
}

#endregion

public void SetStripDelay(Entity<ClothingComponent> clothing, TimeSpan newDelay)
{
clothing.Comp.StripDelay = newDelay;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ent-ExpCollarsKit = Коробка с взрывными ошейниками
.desc = Ужасающе
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
ent-ExplosiveCollarBase = ошейник
.desc = Высокотехнологичный ошейник, основанный на инвертере квантового спина, использующийся синдикатом для взятия в заложники важных лиц.
ent-ExplosiveCollarRed = красный { ent-ExplosiveCollarBase }
.desc = { ent-ExplosiveCollarBase.desc }
ent-ExplosiveCollarWhite = белый { ent-ExplosiveCollarBase }
.desc = { ent-ExplosiveCollarBase.desc }
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ent-bouquet = Букет
.desc = Букет душистых цветов.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
expcollar-examine-armed = Боевой режим [color=red]включен[/color]
expcoller-examine-disarmed = Боевой режим [color=green]выключен[/color]
expcollar-examine-virgin = [color=yellow]Ошейник ни разу не использовался до этого[/color]
expcollar-examine-unvirgin = [color=red]Ошейник уже был использован до этого и потерял свою эффективность[/color]
expcollar-examine-linked = [color=lightblue]Из ошейника исходят блюспейс импульсы[/color]
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
uplink-exp-collars-kit-name = Набор с взрывными ошейниками
uplink-exp-collars-kit-desc = Набор включает два взрывных ошейника и свадебный букет. Если соединить ошейники, то при смерти носителя красного ошейника они оба взорвутся.
uplink-bouquet-name = Букет
uplink-bouquet-desc = Красивый набор цветов, соответствующий последним стандартам Синдиката.
8 changes: 8 additions & 0 deletions Resources/Locale/ru-RU/_strings/_sunrise/expcollar/popups.ftl
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
expcollar-connect = Ошейники связываются!
expcollar-connected = Ошейник уже привязан.
expcollar-bolts-down = Болты ошейника отключаются!
expcollar-kill = Зафиксирована смерть хоста!
expcollar-armed = Ошейник переводится в боевой режим!
expcollar-disarmed = Боевой режим отключается!
expcollar-boom = Пик!
expcollar-popup = { $timer }
17 changes: 17 additions & 0 deletions Resources/Prototypes/_Sunrise/Catalog/Fills/Boxes/syndicate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
- type: entity
parent: [BoxCardboard, BaseSyndicateContraband]
id: ExpCollarsKit
name: explosive collars kit
components:
- type: Storage
grid:
- 0,0,3,1
- type: StorageFill
contents:
- id: ExplosiveCollarRed
- id: ExplosiveCollarWhite
- id: Bouquet
- type: Sprite
layers:
- state: box_of_doom_big
- state: implant
Loading
Loading