Реворк Интердайн дефибриллятора. (#3249)

Co-authored-by: worstplayerever <worstplayerever912@gmail.com>
Co-authored-by: banumbas <banumbas1@gmail.com>
Co-authored-by: KaiserMaus <kaiser.ratte@gmail.com>
Co-authored-by: Vigers Ray <60344369+VigersRay@users.noreply.github.com>
This commit is contained in:
worstplayerever 2025-12-25 19:52:57 +03:00 committed by GitHub
parent f122d9bdd4
commit 3e4bbda502
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 284 additions and 10 deletions

View file

@ -23,6 +23,12 @@ using Content.Shared.Timing;
using Content.Shared.Toggleable;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Player;
// Sunrise-Start
using Content.Shared.FixedPoint;
using Content.Shared.Body.Components;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Components;
// Sunrise-End
namespace Content.Server.Medical;
@ -46,6 +52,7 @@ public sealed class DefibrillatorSystem : EntitySystem
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedMindSystem _mind = default!;
[Dependency] private readonly UseDelaySystem _useDelay = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!; // Sunrise-Edit
/// <inheritdoc/>
public override void Initialize()
@ -70,7 +77,7 @@ public sealed class DefibrillatorSystem : EntitySystem
if (args.Target is not { } target)
return;
if (!CanZap(uid, target, args.User, component))
if (!CanZap(uid, target, args.User, component, component.AllowUseOnAlive)) // Sunrise-Edit
return;
args.Handled = true;
@ -111,6 +118,13 @@ public sealed class DefibrillatorSystem : EntitySystem
if (!_powerCell.HasActivatableCharge(uid, user: user))
return false;
// Sunrise-Start
var canZapEvent = new SunriseCanZapEvent(uid, target, user);
RaiseLocalEvent(uid, ref canZapEvent);
if (canZapEvent.Cancelled)
return false;
// Sunrise-End
if (!targetCanBeAlive && _mobState.IsAlive(target, mobState))
return false;
@ -135,7 +149,7 @@ public sealed class DefibrillatorSystem : EntitySystem
if (!Resolve(uid, ref component))
return false;
if (!CanZap(uid, target, user, component))
if (!CanZap(uid, target, user, component, component.AllowUseOnAlive)) // Sunrise-Edit
return false;
_audio.PlayPvs(component.ChargeSound, uid);
@ -164,7 +178,7 @@ public sealed class DefibrillatorSystem : EntitySystem
target = selfEvent.DefibTarget;
// Ensure thet new target is still valid.
if (selfEvent.Cancelled || !CanZap(uid, target, user, component, true))
if (selfEvent.Cancelled || !CanZap(uid, target, user, component, component.AllowUseOnAlive)) // Sunrise-Edit
return;
var targetEvent = new TargetBeforeDefibrillatorZapsEvent(user, uid, target);
@ -172,7 +186,7 @@ public sealed class DefibrillatorSystem : EntitySystem
target = targetEvent.DefibTarget;
if (targetEvent.Cancelled || !CanZap(uid, target, user, component, true))
if (targetEvent.Cancelled || !CanZap(uid, target, user, component, component.AllowUseOnAlive)) // Sunrise-Edit
return;
if (!TryComp<MobStateComponent>(target, out var mob) ||
@ -229,6 +243,18 @@ public sealed class DefibrillatorSystem : EntitySystem
}
}
// Sunrise-Start
// Inject reagents if any are specified
if (component.Reagents.Count > 0 && TryComp<BloodstreamComponent>(target, out var bloodstream))
{
if (_solutionContainer.TryGetSolution(target, bloodstream.ChemicalSolutionName, out var solution))
{
foreach (var (reagent, amount) in component.Reagents)
_solutionContainer.TryAddReagent(solution.Value, reagent, FixedPoint2.New(amount), out _);
}
}
// Sunrise-End
var sound = dead || session == null
? component.FailureSound
: component.SuccessSound;

View file

@ -0,0 +1,36 @@
using Content.Server.Popups;
using Content.Shared._Sunrise.Biocode;
using Content.Shared.Medical;
namespace Content.Server._Sunrise.Biocode.Systems;
/// <summary>
/// System that handles biocode checks for defibrillators.
/// </summary>
public sealed class BiocodeDefibrillatorSystem : EntitySystem
{
[Dependency] private readonly BiocodeSystem _biocode = default!;
[Dependency] private readonly PopupSystem _popup = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<BiocodeComponent, SunriseCanZapEvent>(OnCanZap);
}
private void OnCanZap(EntityUid uid, BiocodeComponent component, ref SunriseCanZapEvent args)
{
if (args.User == null)
return;
if (_biocode.CanUse(args.User.Value, component.Factions))
return;
// User is not authorized, cancel the zap
if (!string.IsNullOrEmpty(component.AlertText))
_popup.PopupEntity(component.AlertText, uid, args.User.Value);
args.Cancelled = true;
}
}

View file

@ -0,0 +1,71 @@
using Content.Server.Power.EntitySystems;
using Content.Server.PowerCell;
using Content.Shared.Item.ItemToggle;
using Content.Shared.Item.ItemToggle.Components;
using Content.Shared.Power.Components;
using Content.Shared.PowerCell.Components;
using Content.Shared.Weapons.Melee.Events;
using Content.Shared._Sunrise.Weapons.Melee.Components;
namespace Content.Server._Sunrise.Weapons.Melee.Systems;
public sealed class PowerDrainOnMeleeHitSystem : EntitySystem
{
[Dependency] private readonly ItemToggleSystem _itemToggle = default!;
[Dependency] private readonly PowerCellSystem _powerCell = default!;
[Dependency] private readonly BatterySystem _battery = default!;
public override void Initialize()
{
SubscribeLocalEvent<PowerDrainOnMeleeHitComponent, MeleeHitEvent>(OnMeleeHit);
}
private void OnMeleeHit(EntityUid uid, PowerDrainOnMeleeHitComponent comp, ref MeleeHitEvent args)
{
if (comp.ChargePerHit <= 0)
return;
if (!args.IsHit)
return;
if (comp.RequireActualHit && (args.HitEntities == null || args.HitEntities.Count == 0))
return;
// Check if item is toggled on (if it has ItemToggleComponent)
if (TryComp<ItemToggleComponent>(uid, out var toggle) && !_itemToggle.IsActivated((uid, toggle)))
return;
// Prefer slotted power cell if present
if (HasComp<PowerCellSlotComponent>(uid))
{
if (!_powerCell.HasCharge(uid, comp.ChargePerHit, null, args.User))
{
args.Handled = true;
return;
}
if (!_powerCell.TryUseCharge(uid, comp.ChargePerHit, null, args.User))
{
args.Handled = true;
return;
}
return;
}
// Fall back to direct BatteryComponent on the same entity
if (TryComp<BatteryComponent>(uid, out var directBattery))
{
if (directBattery.CurrentCharge < comp.ChargePerHit)
{
args.Handled = true;
return;
}
if (!_battery.TryUseCharge(uid, comp.ChargePerHit, directBattery))
{
args.Handled = true;
return;
}
}
}
}

View file

@ -77,6 +77,20 @@ public sealed partial class DefibrillatorComponent : Component
[ViewVariables(VVAccess.ReadWrite), DataField("readySound")]
public SoundSpecifier? ReadySound = new SoundPathSpecifier("/Audio/Items/Defib/defib_ready.ogg");
// Sunrise-Start
/// <summary>
/// Whether the defibrillator can be used on alive targets
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)]
public bool AllowUseOnAlive = false;
/// <summary>
/// The reagents to inject when defibrillation is completed
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)]
public Dictionary<string, float> Reagents = new();
// Sunrise-End
}
[Serializable, NetSerializable]

View file

@ -37,3 +37,15 @@ public sealed class TargetBeforeDefibrillatorZapsEvent : BeforeDefibrillatorZaps
{
public TargetBeforeDefibrillatorZapsEvent(EntityUid entityUsingDefib, EntityUid defib, EntityUid defibtarget) : base(entityUsingDefib, defib, defibtarget) { }
}
// Sunrise-Start
/// <summary>
/// This event is raised to check if the defibrillator can be used.
/// Systems can cancel this event to prevent defibrillation.
/// </summary>
[ByRefEvent]
public record struct SunriseCanZapEvent(EntityUid Defibrillator, EntityUid Target, EntityUid? User)
{
public bool Cancelled = false;
}
// Sunrise-End

View file

@ -0,0 +1,23 @@
using Robust.Shared.GameStates;
namespace Content.Shared._Sunrise.Weapons.Melee.Components;
/// <summary>
/// When attached to a melee weapon, drains power on successful melee hit.
/// Drains from a slotted power cell if present, otherwise from a direct BatteryComponent.
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed partial class PowerDrainOnMeleeHitComponent : Component
{
/// <summary>
/// Amount of charge to drain per successful hit (in joules).
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField]
public float ChargePerHit = 0f;
/// <summary>
/// If true, only drain when there is at least one entity hit.
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField]
public bool RequireActualHit = true;
}

View file

@ -41,7 +41,7 @@ ent-ClothingBackpackDuffelSyndicateHardsuitExtrasBundle = набор допол
ent-ClothingBackpackDuffelZombieBundle = зомби набор Синдиката
.desc = Универсальный набор для создания зомби на станции.
ent-ClothingBackpackDuffelSyndicateMedicalBundleFilled = набор медикаментов
.desc = Все, что нужно для возвращения в строй ваших товарищей: главным образом, боевая аптечка, дефибриллятор и три боевых медипена.
.desc = Все, что нужно для возвращения в строй ваших товарищей: главным образом, боевая аптечка и три боевых медипена.
ent-ClothingBackpackDuffelSyndicateDecoyKitFilled = набор обманок
.desc = Содержит отвлекающие устройства, как звуковые, так и визуальные. Скоро появятся и обонятельные.
ent-ClothingBackpackDuffelAcolyteBundle = набор брони послушника

View file

@ -3,3 +3,6 @@ ent-SyndicateTeleporter = телепортер синдиката
ent-SyndicateTeleporterBiocode = { ent-SyndicateTeleporter }
.desc = { ent-SyndicateTeleporter.desc }
ent-DefibrillatorSyndicateBiocode = { ent-DefibrillatorSyndicate }
.desc = { ent-DefibrillatorSyndicate.desc }

View file

@ -11,4 +11,4 @@ ent-DefibrillatorOneHandedUnpowered = { ent-BaseDefibrillator }
ent-DefibrillatorCompact = компактный дефибриллятор
.desc = Теперь в забавном размере!
ent-DefibrillatorSyndicate = дефибриллятор Interdyne
.desc = Двойное оружие самообороны против склонных к военным преступлениям тайдеров.
.desc = Особый дефибриллятор фирмы Interdyne. Для настоящих медиков Синдиката!

View file

@ -371,3 +371,5 @@ uplink-fake-mindshield-name = Имитатор защиты разума
uplink-fake-mindshield-desc = Переключаемый имплант, воспроизводящий сигнатуры настоящего щита. Обманывает сканеры командования, создавая ложное присутствие защиты. (Имплантер NT не включён в поставку.)
uplink-handcuffs-name = Наручники
uplink-handcuffs-desc = Используется для удержания жертв.
uplink-interdyne-defibrillator-name = Дефибриллятор Interdyne
uplink-interdyne-defibrillator-desc = Превосходный дефибриллятор, предназначенный для помощи и самообороны. Для настоящих медиков Синдиката.

View file

@ -387,7 +387,7 @@
components:
- type: StorageFill
contents:
- id: DefibrillatorSyndicate
#- id: DefibrillatorSyndicate
- id: MedkitCombatFilled
amount: 4
- id: Tourniquet

View file

@ -899,9 +899,9 @@
productEntity: ClothingBackpackDuffelSyndicateMedicalBundleFilled
discountCategory: rareDiscounts
discountDownTo:
Telecrystal: 16
Telecrystal: 12
cost:
Telecrystal: 24
Telecrystal: 16
categories:
- UplinkChemicals
conditions:

View file

@ -112,6 +112,24 @@
- state: ready
map: ["enum.PowerDeviceVisualLayers.Powered"]
shader: unshaded
# Sunrise-Start
- type: Defibrillator
allowUseOnAlive: true
reagents:
Omnizine: 10
Epinephrine: 5
- type: ItemSlots
slots:
cell_slot:
name: power-cell-slot-component-slot-name-default
startingItem: PowerCellDefibrillatorSyndicate
disableEject: true
locked: true
- type: PowerCellDraw
useRate: 85
- type: PowerDrainOnMeleeHit
chargePerHit: 30
# Sunrise-End
- type: MeleeWeapon
damage:
types:

View file

@ -2252,7 +2252,7 @@
sprite: /Textures/_Starlight/Objects/Weapons/Melee/cybermantisblade.rsi,
state: cybermantisblade,
}
productEntity: MantisBladeArmsKit #ClothingBackpackDufelSyndicateFilledMantisBladeArms #Скучно и не практично в сумке ради 4 слота
productEntity: MantisBladeArmsKit
discountCategory: rareDiscounts
discountDownTo:
Telecrystal: 8
@ -2443,6 +2443,29 @@
categories:
- UplinkDeception
conditions:
- !type:StoreWhitelistCondition
whitelist:
- Science
tags:
- NukeOpsUplink
- LoneOpsUplink
- AssaultOpsUplink
- type: listing
id: UplinkDefibrillatorSyndicate
name: uplink-interdyne-defibrillator-name
description: uplink-interdyne-defibrillator-desc
productEntity: DefibrillatorSyndicateBiocode
discountCategory: rareDiscounts
discountDownTo:
Telecrystal: 4
cost:
Telecrystal: 8
categories:
- UplinkChemicals
conditions:
- !type:ListingLimitedStockCondition
stock: 1
- !type:StoreWhitelistCondition
whitelist:
tags:

View file

@ -8,3 +8,17 @@
- Syndicate
- Thief
alertText: Данный предмет биокодирован. Вы не можете его использовать.
- type: entity
parent: DefibrillatorSyndicate
id: DefibrillatorSyndicateBiocode
suffix: BIOCODE
components:
- type: Biocode
factions:
- Syndicate
alertText: Данный предмет биокодирован. Вы не можете его использовать.
- type: FactionWeaponBlocker
factions:
- Syndicate
alertText: Данное оружие биокодировано. Вы не можете его использовать.

View file

@ -59,3 +59,35 @@
graph: MakeshiftPowerCage
node: makeshiftcage
- type: entity
name: powercell defibrillator syndicate
id: PowerCellDefibrillatorSyndicate
suffix: Full
parent: SyndicateDefibrillatorPowercell
components:
- type: Battery
maxCharge: 300
startingCharge: 300
- type: BatterySelfRecharger
autoRechargeRate: 4.5
autoRecharge: true
autoRechargePause: true
autoRechargePauseTime: 25
- type: entity
name: syndicate defibrillator power cell
description: A rechargeable standardized power cell. This one looks like a rare and powerful Syndicate combat variant.
id: SyndicateDefibrillatorPowercell
suffix: Full
parent: BasePowerCell
components:
- type: Sprite
layers:
- map: [ "enum.PowerCellVisualLayers.Base" ]
state: syndicate
- map: [ "enum.PowerCellVisualLayers.Unshaded" ]
state: o2
shader: unshaded
- type: Battery
maxCharge: 1800
startingCharge: 1800