From 3e4bbda5026165c9a059ca6356cf5f8ea687c582 Mon Sep 17 00:00:00 2001 From: worstplayerever Date: Thu, 25 Dec 2025 19:52:57 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A0=D0=B5=D0=B2=D0=BE=D1=80=D0=BA=20=D0=98?= =?UTF-8?q?=D0=BD=D1=82=D0=B5=D1=80=D0=B4=D0=B0=D0=B9=D0=BD=20=D0=B4=D0=B5?= =?UTF-8?q?=D1=84=D0=B8=D0=B1=D1=80=D0=B8=D0=BB=D0=BB=D1=8F=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D0=B0.=20(#3249)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: worstplayerever Co-authored-by: banumbas Co-authored-by: KaiserMaus Co-authored-by: Vigers Ray <60344369+VigersRay@users.noreply.github.com> --- Content.Server/Medical/DefibrillatorSystem.cs | 34 +++++++-- .../Systems/BiocodeDefibrillatorSystem.cs | 36 ++++++++++ .../PowerDrainOnMeleeHitComponent.cs | 0 .../Systems/PowerDrainOnMeleeHitSystem.cs | 71 +++++++++++++++++++ .../Medical/DefibrillatorComponent.cs | 14 ++++ Content.Shared/Medical/DefibrillatorEvents.cs | 12 ++++ .../PowerDrainOnMeleeHitComponent.cs | 23 ++++++ .../catalog/fills/backpacks/duffelbag.ftl | 2 +- .../syndicate_teleporter.ftl | 3 + .../objects/specific/medical/defib.ftl | 2 +- .../ru-RU/_strings/store/uplink-catalog.ftl | 2 + .../Catalog/Fills/Backpacks/duffelbag.yml | 2 +- .../Prototypes/Catalog/uplink_catalog.yml | 4 +- .../Objects/Specific/Medical/defib.yml | 18 +++++ .../_Sunrise/Catalog/uplink_catalog.yml | 25 ++++++- .../Devices/Syndicate_Gadgets/biocode.yml | 14 ++++ .../Entities/Objects/Power/powercells.yml | 32 +++++++++ 17 files changed, 284 insertions(+), 10 deletions(-) create mode 100644 Content.Server/_Sunrise/Biocode/Systems/BiocodeDefibrillatorSystem.cs create mode 100644 Content.Server/_Sunrise/Weapons/Melee/Components/PowerDrainOnMeleeHitComponent.cs create mode 100644 Content.Server/_Sunrise/Weapons/Melee/Systems/PowerDrainOnMeleeHitSystem.cs create mode 100644 Content.Shared/_Sunrise/Weapons/Melee/Components/PowerDrainOnMeleeHitComponent.cs diff --git a/Content.Server/Medical/DefibrillatorSystem.cs b/Content.Server/Medical/DefibrillatorSystem.cs index 94d62641ce..7331d8dcc7 100644 --- a/Content.Server/Medical/DefibrillatorSystem.cs +++ b/Content.Server/Medical/DefibrillatorSystem.cs @@ -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 /// 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(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(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; diff --git a/Content.Server/_Sunrise/Biocode/Systems/BiocodeDefibrillatorSystem.cs b/Content.Server/_Sunrise/Biocode/Systems/BiocodeDefibrillatorSystem.cs new file mode 100644 index 0000000000..9f2f21ec0f --- /dev/null +++ b/Content.Server/_Sunrise/Biocode/Systems/BiocodeDefibrillatorSystem.cs @@ -0,0 +1,36 @@ +using Content.Server.Popups; +using Content.Shared._Sunrise.Biocode; +using Content.Shared.Medical; + +namespace Content.Server._Sunrise.Biocode.Systems; + +/// +/// System that handles biocode checks for defibrillators. +/// +public sealed class BiocodeDefibrillatorSystem : EntitySystem +{ + [Dependency] private readonly BiocodeSystem _biocode = default!; + [Dependency] private readonly PopupSystem _popup = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(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; + } +} + diff --git a/Content.Server/_Sunrise/Weapons/Melee/Components/PowerDrainOnMeleeHitComponent.cs b/Content.Server/_Sunrise/Weapons/Melee/Components/PowerDrainOnMeleeHitComponent.cs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Content.Server/_Sunrise/Weapons/Melee/Systems/PowerDrainOnMeleeHitSystem.cs b/Content.Server/_Sunrise/Weapons/Melee/Systems/PowerDrainOnMeleeHitSystem.cs new file mode 100644 index 0000000000..966277f0a3 --- /dev/null +++ b/Content.Server/_Sunrise/Weapons/Melee/Systems/PowerDrainOnMeleeHitSystem.cs @@ -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(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(uid, out var toggle) && !_itemToggle.IsActivated((uid, toggle))) + return; + + // Prefer slotted power cell if present + if (HasComp(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(uid, out var directBattery)) + { + if (directBattery.CurrentCharge < comp.ChargePerHit) + { + args.Handled = true; + return; + } + + if (!_battery.TryUseCharge(uid, comp.ChargePerHit, directBattery)) + { + args.Handled = true; + return; + } + } + } +} diff --git a/Content.Shared/Medical/DefibrillatorComponent.cs b/Content.Shared/Medical/DefibrillatorComponent.cs index f54348d771..94f9587359 100644 --- a/Content.Shared/Medical/DefibrillatorComponent.cs +++ b/Content.Shared/Medical/DefibrillatorComponent.cs @@ -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 + /// + /// Whether the defibrillator can be used on alive targets + /// + [DataField, ViewVariables(VVAccess.ReadWrite)] + public bool AllowUseOnAlive = false; + + /// + /// The reagents to inject when defibrillation is completed + /// + [DataField, ViewVariables(VVAccess.ReadWrite)] + public Dictionary Reagents = new(); + // Sunrise-End } [Serializable, NetSerializable] diff --git a/Content.Shared/Medical/DefibrillatorEvents.cs b/Content.Shared/Medical/DefibrillatorEvents.cs index 54a21a40d4..1f0d531689 100644 --- a/Content.Shared/Medical/DefibrillatorEvents.cs +++ b/Content.Shared/Medical/DefibrillatorEvents.cs @@ -37,3 +37,15 @@ public sealed class TargetBeforeDefibrillatorZapsEvent : BeforeDefibrillatorZaps { public TargetBeforeDefibrillatorZapsEvent(EntityUid entityUsingDefib, EntityUid defib, EntityUid defibtarget) : base(entityUsingDefib, defib, defibtarget) { } } + +// Sunrise-Start +/// +/// This event is raised to check if the defibrillator can be used. +/// Systems can cancel this event to prevent defibrillation. +/// +[ByRefEvent] +public record struct SunriseCanZapEvent(EntityUid Defibrillator, EntityUid Target, EntityUid? User) +{ + public bool Cancelled = false; +} +// Sunrise-End diff --git a/Content.Shared/_Sunrise/Weapons/Melee/Components/PowerDrainOnMeleeHitComponent.cs b/Content.Shared/_Sunrise/Weapons/Melee/Components/PowerDrainOnMeleeHitComponent.cs new file mode 100644 index 0000000000..b37061d640 --- /dev/null +++ b/Content.Shared/_Sunrise/Weapons/Melee/Components/PowerDrainOnMeleeHitComponent.cs @@ -0,0 +1,23 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared._Sunrise.Weapons.Melee.Components; + +/// +/// 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. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class PowerDrainOnMeleeHitComponent : Component +{ + /// + /// Amount of charge to drain per successful hit (in joules). + /// + [ViewVariables(VVAccess.ReadWrite), DataField] + public float ChargePerHit = 0f; + + /// + /// If true, only drain when there is at least one entity hit. + /// + [ViewVariables(VVAccess.ReadWrite), DataField] + public bool RequireActualHit = true; +} diff --git a/Resources/Locale/ru-RU/_prototypes/catalog/fills/backpacks/duffelbag.ftl b/Resources/Locale/ru-RU/_prototypes/catalog/fills/backpacks/duffelbag.ftl index d1f390a398..d540b9a6cc 100644 --- a/Resources/Locale/ru-RU/_prototypes/catalog/fills/backpacks/duffelbag.ftl +++ b/Resources/Locale/ru-RU/_prototypes/catalog/fills/backpacks/duffelbag.ftl @@ -41,7 +41,7 @@ ent-ClothingBackpackDuffelSyndicateHardsuitExtrasBundle = набор допол ent-ClothingBackpackDuffelZombieBundle = зомби набор Синдиката .desc = Универсальный набор для создания зомби на станции. ent-ClothingBackpackDuffelSyndicateMedicalBundleFilled = набор медикаментов - .desc = Все, что нужно для возвращения в строй ваших товарищей: главным образом, боевая аптечка, дефибриллятор и три боевых медипена. + .desc = Все, что нужно для возвращения в строй ваших товарищей: главным образом, боевая аптечка и три боевых медипена. ent-ClothingBackpackDuffelSyndicateDecoyKitFilled = набор обманок .desc = Содержит отвлекающие устройства, как звуковые, так и визуальные. Скоро появятся и обонятельные. ent-ClothingBackpackDuffelAcolyteBundle = набор брони послушника diff --git a/Resources/Locale/ru-RU/_prototypes/entities/objects/devices/syndicate_gadgets/syndicate_teleporter.ftl b/Resources/Locale/ru-RU/_prototypes/entities/objects/devices/syndicate_gadgets/syndicate_teleporter.ftl index 76919e76e9..55db47d9e1 100644 --- a/Resources/Locale/ru-RU/_prototypes/entities/objects/devices/syndicate_gadgets/syndicate_teleporter.ftl +++ b/Resources/Locale/ru-RU/_prototypes/entities/objects/devices/syndicate_gadgets/syndicate_teleporter.ftl @@ -3,3 +3,6 @@ ent-SyndicateTeleporter = телепортер синдиката ent-SyndicateTeleporterBiocode = { ent-SyndicateTeleporter } .desc = { ent-SyndicateTeleporter.desc } + +ent-DefibrillatorSyndicateBiocode = { ent-DefibrillatorSyndicate } + .desc = { ent-DefibrillatorSyndicate.desc } diff --git a/Resources/Locale/ru-RU/_prototypes/entities/objects/specific/medical/defib.ftl b/Resources/Locale/ru-RU/_prototypes/entities/objects/specific/medical/defib.ftl index 3439d47f52..b5a3197e17 100644 --- a/Resources/Locale/ru-RU/_prototypes/entities/objects/specific/medical/defib.ftl +++ b/Resources/Locale/ru-RU/_prototypes/entities/objects/specific/medical/defib.ftl @@ -11,4 +11,4 @@ ent-DefibrillatorOneHandedUnpowered = { ent-BaseDefibrillator } ent-DefibrillatorCompact = компактный дефибриллятор .desc = Теперь в забавном размере! ent-DefibrillatorSyndicate = дефибриллятор Interdyne - .desc = Двойное оружие самообороны против склонных к военным преступлениям тайдеров. + .desc = Особый дефибриллятор фирмы Interdyne. Для настоящих медиков Синдиката! diff --git a/Resources/Locale/ru-RU/_strings/store/uplink-catalog.ftl b/Resources/Locale/ru-RU/_strings/store/uplink-catalog.ftl index 1b0fc742e1..61ac70936c 100644 --- a/Resources/Locale/ru-RU/_strings/store/uplink-catalog.ftl +++ b/Resources/Locale/ru-RU/_strings/store/uplink-catalog.ftl @@ -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 = Превосходный дефибриллятор, предназначенный для помощи и самообороны. Для настоящих медиков Синдиката. diff --git a/Resources/Prototypes/Catalog/Fills/Backpacks/duffelbag.yml b/Resources/Prototypes/Catalog/Fills/Backpacks/duffelbag.yml index b3c19bdb48..d147380b69 100644 --- a/Resources/Prototypes/Catalog/Fills/Backpacks/duffelbag.yml +++ b/Resources/Prototypes/Catalog/Fills/Backpacks/duffelbag.yml @@ -387,7 +387,7 @@ components: - type: StorageFill contents: - - id: DefibrillatorSyndicate + #- id: DefibrillatorSyndicate - id: MedkitCombatFilled amount: 4 - id: Tourniquet diff --git a/Resources/Prototypes/Catalog/uplink_catalog.yml b/Resources/Prototypes/Catalog/uplink_catalog.yml index 897461b6ed..3d04fd783f 100644 --- a/Resources/Prototypes/Catalog/uplink_catalog.yml +++ b/Resources/Prototypes/Catalog/uplink_catalog.yml @@ -899,9 +899,9 @@ productEntity: ClothingBackpackDuffelSyndicateMedicalBundleFilled discountCategory: rareDiscounts discountDownTo: - Telecrystal: 16 + Telecrystal: 12 cost: - Telecrystal: 24 + Telecrystal: 16 categories: - UplinkChemicals conditions: diff --git a/Resources/Prototypes/Entities/Objects/Specific/Medical/defib.yml b/Resources/Prototypes/Entities/Objects/Specific/Medical/defib.yml index 894454b05c..5e7f817d95 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Medical/defib.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Medical/defib.yml @@ -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: diff --git a/Resources/Prototypes/_Sunrise/Catalog/uplink_catalog.yml b/Resources/Prototypes/_Sunrise/Catalog/uplink_catalog.yml index cbb430b86a..fcf062e583 100644 --- a/Resources/Prototypes/_Sunrise/Catalog/uplink_catalog.yml +++ b/Resources/Prototypes/_Sunrise/Catalog/uplink_catalog.yml @@ -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: diff --git a/Resources/Prototypes/_Sunrise/Entities/Objects/Devices/Syndicate_Gadgets/biocode.yml b/Resources/Prototypes/_Sunrise/Entities/Objects/Devices/Syndicate_Gadgets/biocode.yml index c41756ed33..f4f4e5150b 100644 --- a/Resources/Prototypes/_Sunrise/Entities/Objects/Devices/Syndicate_Gadgets/biocode.yml +++ b/Resources/Prototypes/_Sunrise/Entities/Objects/Devices/Syndicate_Gadgets/biocode.yml @@ -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: Данное оружие биокодировано. Вы не можете его использовать. diff --git a/Resources/Prototypes/_Sunrise/Entities/Objects/Power/powercells.yml b/Resources/Prototypes/_Sunrise/Entities/Objects/Power/powercells.yml index d616c83dd7..0f659c187b 100644 --- a/Resources/Prototypes/_Sunrise/Entities/Objects/Power/powercells.yml +++ b/Resources/Prototypes/_Sunrise/Entities/Objects/Power/powercells.yml @@ -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