Changeling reagent stings (#43805)

* Changeling-Stings

* update yml

* attempt failed dead

* sprite and ChangelingNeurodepressantSting

* Update changeling_catalog.yml

* merge

* update

* review

* review 2

* clean up, remove non-blind stings

* fix stuff

* lethal, cryogenic, hallucination + armblade  nerf

* validate deez

* a

* review

---------

Co-authored-by: ScarKy0 <scarky0@onet.eu>
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
This commit is contained in:
Pok
2026-06-04 22:29:21 +03:00
committed by GitHub
parent 9cfddca15c
commit 61f98c14d3
19 changed files with 400 additions and 11 deletions
@@ -0,0 +1,23 @@
using Content.Shared.EntityEffects;
using Robust.Shared.GameStates;
namespace Content.Shared.Actions.Components;
/// <summary>
/// An action component that applies a list of entity effects to the target when performed.
/// Requires <see cref="EntityTargetActionComponent"/> and <see cref="ActionComponent"/>.
/// </summary>
[RegisterComponent, NetworkedComponent, Access(typeof(EntityEffectActionSystem))]
public sealed partial class EntityEffectActionComponent : Component
{
/// <summary>
/// List of entity effects to apply to the target when this action is performed.
/// </summary>
[DataField(required: true)]
public List<EntityEffect> Effects = [];
}
/// <summary>
/// Event raised when an entity effect action is performed.
/// </summary>
public sealed partial class EntityEffectActionEvent : EntityTargetActionEvent;
@@ -0,0 +1,35 @@
using Content.Shared.Popups;
using Robust.Shared.GameStates;
namespace Content.Shared.Actions.Components;
/// <summary>
/// Displays a popup message when the action is successfully performed.
/// </summary>
[RegisterComponent, NetworkedComponent, Access(typeof(PopupOnActionSystem))]
public sealed partial class PopupOnActionComponent : Component
{
/// <summary>
/// The locale ID of the message to the performer of the action.
/// </summary>
[DataField]
public LocId? SelfMessage;
/// <summary>
/// The message to show to those around the performer of the action.
/// </summary>
[DataField]
public LocId? OthersMessage;
/// <summary>
/// The message to show to the action target.
/// </summary>
[DataField]
public LocId? TargetMessage;
/// <summary>
/// The visual style of the popup.
/// </summary>
[DataField]
public PopupType PopupType = PopupType.Small;
}
@@ -0,0 +1,27 @@
using Content.Shared.Actions.Components;
using Content.Shared.EntityEffects;
namespace Content.Shared.Actions;
/// <summary>
/// Handles applying entity effects when an entity effect action is performed.
/// </summary>
public sealed partial class EntityEffectActionSystem : EntitySystem
{
[Dependency] private SharedEntityEffectsSystem _effects = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<EntityEffectActionComponent, EntityEffectActionEvent>(OnEntityEffectAction);
}
private void OnEntityEffectAction(Entity<EntityEffectActionComponent> ent, ref EntityEffectActionEvent args)
{
foreach (var effect in ent.Comp.Effects)
{
if (_effects.TryApplyEffect(args.Target, effect, user: args.Performer))
args.Handled = true;
}
}
}
@@ -0,0 +1,62 @@
using Content.Shared.Actions.Components;
using Content.Shared.Actions.Events;
using Content.Shared.IdentityManagement;
using Content.Shared.Popups;
using Robust.Shared.Player;
namespace Content.Shared.Actions;
/// <summary>
/// Handles displaying popup messages when actions are successfully performed.
/// </summary>
public sealed partial class PopupOnActionSystem : EntitySystem
{
[Dependency] private SharedPopupSystem _popup = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<PopupOnActionComponent, ActionPerformedEvent>(OnActionPerformed);
}
private void OnActionPerformed(Entity<PopupOnActionComponent> ent, ref ActionPerformedEvent args)
{
EntityUid? target = null;
if (TryComp<EntityTargetActionComponent>(ent, out var entityTarget) && entityTarget.Event is { } ev)
target = ev.Target;
var userName = Identity.Name(args.Performer, EntityManager);
var targetName = target != null ? Identity.Name(target.Value, EntityManager) : string.Empty;
var selfMessage = ent.Comp.SelfMessage != null
? Loc.GetString(ent.Comp.SelfMessage, ("target", targetName), ("user", userName))
: null;
var othersMessage = ent.Comp.OthersMessage != null
? Loc.GetString(ent.Comp.OthersMessage, ("target", targetName), ("user", userName))
: null;
var targetMessage = ent.Comp.TargetMessage != null
? Loc.GetString(ent.Comp.TargetMessage, ("target", targetName), ("user", userName))
: null;
// Popup to show to the performer.
// If there is a target the popup is located on the target, if there is no target it is located on the user.
_popup.PopupClient(selfMessage, target ?? args.Performer, args.Performer, ent.Comp.PopupType);
// Popup to show to the target.
// Located on the target.
if (target != null)
_popup.PopupEntity(targetMessage, target.Value, target.Value, ent.Comp.PopupType);
// Popup for everyone else.
// Located on the performer.
var filter = Filter.PvsExcept(args.Performer, entityManager: EntityManager);
if (target != null)
filter = filter.RemovePlayerByAttachedEntity(target.Value);
_popup.PopupEntity(othersMessage, args.Performer, filter, true, ent.Comp.PopupType);
}
}
@@ -80,7 +80,6 @@ public sealed partial class ChangelingAbilitySystem : EntitySystem
if (!_changelingDevour.CanDevour(ent.Owner, args.Target, checkDead: false, checkProtected: false))
return;
_popup.PopupClient(Loc.GetString("changeling-sting-success", ("target", Identity.Entity(args.Target, EntityManager))), args.Target, ent.Owner, PopupType.Medium);
_changelingIdentity.GrantIdentity(ent, args.Target);
args.Handled = true;
@@ -11,6 +11,7 @@ public sealed partial class ReactiveSystem : EntitySystem
{
[Dependency] private IPrototypeManager _proto = default!;
// TODO: Someone add documentation, I beg you
public void DoEntityReaction(EntityUid uid, Solution solution, ReactionMethod method)
{
foreach (var reagent in solution.Contents.ToArray())
@@ -0,0 +1,51 @@
using Content.Shared.Body.Components;
using Content.Shared.Body.Systems;
using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.FixedPoint;
using Robust.Shared.Prototypes;
namespace Content.Shared.EntityEffects.Effects.Body;
/// <summary>
/// Adds a reagent directly to the target's bloodstream.
/// Quantity is modified by scale.
/// </summary>
/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
public sealed partial class AddReagentToBloodstreamEntityEffectSystem : EntityEffectSystem<BloodstreamComponent, AddReagentToBloodstream>
{
[Dependency] private SharedBloodstreamSystem _bloodstream = default!;
[Dependency] private ReactiveSystem _reactive = default!;
protected override void Effect(Entity<BloodstreamComponent> entity, ref EntityEffectEvent<AddReagentToBloodstream> args)
{
var solution = new Content.Shared.Chemistry.Components.Solution();
solution.AddReagent(args.Effect.Reagent, args.Effect.Quantity * args.Scale);
_bloodstream.TryAddToBloodstream(entity.AsNullable(), solution);
_reactive.DoEntityReaction(entity, solution, ReactionMethod.Injection); // TODO: This should be part of TryAddToBloodstream
}
}
/// <inheritdoc cref="EntityEffect"/>
public sealed partial class AddReagentToBloodstream : EntityEffectBase<AddReagentToBloodstream>
{
/// <summary>
/// Prototype of the reagent we're adding.
/// </summary>
[DataField(required: true)]
public ProtoId<ReagentPrototype> Reagent;
/// <summary>
/// Amount of reagent to add to the bloodstream.
/// </summary>
[DataField]
public FixedPoint2 Quantity = 1.0;
public override string? EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
{
return prototype.Resolve(Reagent, out var proto)
? Loc.GetString("entity-effect-guidebook-add-reagent-to-bloodstream", ("chance", Probability), ("reagent", proto.LocalizedName), ("quantity", Quantity))
: null;
}
}
@@ -530,3 +530,9 @@ entity-effect-guidebook-plant-mutate-chemicals =
[1] Mutates
*[other] mutate
} a plant to produce {$name}
entity-effect-guidebook-add-reagent-to-bloodstream =
{ $chance ->
[1] Injects
*[other] inject
} {$quantity} of {$reagent} directly into the bloodstream
@@ -12,3 +12,15 @@ changeling-catalog-biodegrade-desc = You learn to utilize acid glands within you
changeling-catalog-sting-dna-name = Extract DNA
changeling-catalog-sting-dna-desc = Learn to utilize your stinger to absorb the identity of unsuspecting victims. Does not grant DNA to your store or objective progress.
changeling-catalog-blind-sting-name = Blind Sting
changeling-catalog-blind-sting-desc = Causes temporary blindness in the target. Good to escape or initiate a fight. Can be used on self.
changeling-catalog-cryogenic-sting-name = Cryogenic Sting
changeling-catalog-cryogenic-sting-desc = Causes movement slowdown in the target. Perfect for keeping prey from running away. Can be used on self.
changeling-catalog-lethal-sting-name = Lethal Sting
changeling-catalog-lethal-sting-desc = Causes very slow damage to the target. Useless in combat, but ensures they will require medical attention. Can be used on self.
changeling-catalog-hallucinogenic-sting-name = Hallucinogenic Sting
changeling-catalog-hallucinogenic-sting-desc = Causes the target to get high and have difficulty speaking. Causes confusion among the crew. Can be used on self.
+98 -1
View File
@@ -102,6 +102,103 @@
canTargetSelf: false
whitelist:
components:
- MobState
- HumanoidProfile
event: !type:ChangelingStingDnaEvent
- type: PopupOnAction
popupType: Medium
selfMessage: changeling-sting-success
- type: entity
abstract: true
parent: BaseAction
id: BaseActionChangelingSting
components:
- type: TargetAction
interactOnMiss: false
- type: EntityTargetAction
whitelist:
components:
- Bloodstream
event: !type:EntityEffectActionEvent
- type: PopupOnAction
popupType: Medium
selfMessage: changeling-sting-success
- type: entity
parent: BaseActionChangelingSting
id: ActionChangelingStingBlind
name: Blind Sting
description: Silently sting a target and worsen their sight for a short duration.
components:
- type: Action
useDelay: 60 # TODO: Chemical cost
raiseOnAction: true
itemIconStyle: BigAction
icon:
sprite: Interface/Actions/changeling2.rsi
state: blind_sting
- type: EntityEffectAction
effects:
- !type:AddReagentToBloodstream
reagent: NorepinephricAcid
quantity: 23
- type: entity
parent: BaseActionChangelingSting
id: ActionChangelingStingCryogenic
name: Cryogenic Sting
description: Silently sting a target and slow down their movement drastically.
components:
- type: Action
useDelay: 60 # TODO: Chemical cost
raiseOnAction: true
itemIconStyle: BigAction
icon:
sprite: Interface/Actions/changeling2.rsi
state: cryogenic_sting
- type: EntityEffectAction
effects:
- !type:AddReagentToBloodstream
reagent: Fresium
quantity: 10 # About 20 seconds of slowdown.
- type: entity
parent: BaseActionChangelingSting
id: ActionChangelingStingLead
name: Lethal Sting
description: Silently sting a target and inject them with a lethal dose of a slow acting poison.
components:
- type: Action
useDelay: 180 # TODO: Chemical cost
raiseOnAction: true
itemIconStyle: BigAction
icon:
sprite: Interface/Actions/changeling2.rsi
state: lethal_sting
- type: EntityEffectAction
effects:
- !type:AddReagentToBloodstream
reagent: Lead
quantity: 5 # Exactly 100 poison damage over 3 minutes.
- type: entity
parent: BaseActionChangelingSting
id: ActionChangelingStingHallucinogenic
name: Hallucinogenic Sting
description: Silently sting a target and inject them with chemicals that slow down their higher brain functions.
components:
- type: Action
useDelay: 90 # TODO: Chemical cost
raiseOnAction: true
itemIconStyle: BigAction
icon:
sprite: Interface/Actions/changeling2.rsi
state: hallucination_sting
- type: EntityEffectAction
effects:
- !type:AddReagentToBloodstream
reagent: SpaceDrugs
quantity: 10 # About 100s of being high
- !type:AddReagentToBloodstream
reagent: Cryptobiolin
quantity: 10 # About 20s of your speech being scrambled
@@ -70,3 +70,63 @@
- !type:ListingLimitedStockCondition
stock: 1
productAction: ActionChangelingStingDna
- type: listing
id: ChangelingStingBlind
name: changeling-catalog-blind-sting-name
description: changeling-catalog-blind-sting-desc
icon: { sprite: /Textures/Interface/Actions/changeling2.rsi, state: blind_sting }
applyToMob: true
cost:
ChangelingDNA: 15
categories:
- ChangelingStoreCombat
conditions:
- !type:ListingLimitedStockCondition
stock: 1
productAction: ActionChangelingStingBlind
- type: listing
id: ChangelingStingCryogenic
name: changeling-catalog-cryogenic-sting-name
description: changeling-catalog-cryogenic-sting-desc
icon: { sprite: /Textures/Interface/Actions/changeling2.rsi, state: cryogenic_sting }
applyToMob: true
cost:
ChangelingDNA: 15
categories:
- ChangelingStoreCombat
conditions:
- !type:ListingLimitedStockCondition
stock: 1
productAction: ActionChangelingStingCryogenic
- type: listing
id: ChangelingStingLead
name: changeling-catalog-lethal-sting-name
description: changeling-catalog-lethal-sting-desc
icon: { sprite: /Textures/Interface/Actions/changeling2.rsi, state: lethal_sting }
applyToMob: true
cost:
ChangelingDNA: 10
categories:
- ChangelingStoreUtility
conditions:
- !type:ListingLimitedStockCondition
stock: 1
productAction: ActionChangelingStingLead
- type: listing
id: ChangelingStingHallucinogenic
name: changeling-catalog-hallucinogenic-sting-name
description: changeling-catalog-hallucinogenic-sting-desc
icon: { sprite: /Textures/Interface/Actions/changeling2.rsi, state: hallucination_sting }
applyToMob: true
cost:
ChangelingDNA: 10
categories:
- ChangelingStoreUtility
conditions:
- !type:ListingLimitedStockCondition
stock: 1
productAction: ActionChangelingStingHallucinogenic
@@ -18,9 +18,9 @@
wideAnimationRotation: 90
attackRate: 1
damage:
types: # Same as energy sword, but slash/pierce instead of slash/heat
Slash: 15
Piercing: 15
types:
Slash: 11
Piercing: 11
Structural: 20
- type: Item
size: Normal
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -1,7 +1,7 @@
{
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "Made by ketufaispikinut from a sprite taken from tgstation at commit https://github.com/tgstation/tgstation/commit/c838ba21dae97db345e0113f99596decd1d66039 (scientist suit), a sprite taken from tgstation at commit https://github.com/tgstation/tgstation/commit/c838ba21dae97db345e0113f99596decd1d66039 (hydro suit) and the changeling ability border/background sprites created by TiniestShark. Biodegrade, Mimicry and Extract Dna On/Off by ketufaispikinut, based on TiniestShark's textures. Shop icon by ketufaispikinut, based on TiniestShark's textures.",
"copyright": "Made by ketufaispikinut from a sprite taken from tgstation at commit https://github.com/tgstation/tgstation/commit/c838ba21dae97db345e0113f99596decd1d66039 (scientist suit), a sprite taken from tgstation at commit https://github.com/tgstation/tgstation/commit/c838ba21dae97db345e0113f99596decd1d66039 (hydro suit) and the changeling ability border/background sprites created by TiniestShark. Biodegrade, Mimicry and Extract Dna On/Off, Blind, Mute, Neurodepressant, Cryogenic, Lethal and Hallucination sting by ketufaispikinut, based on TiniestShark's textures. Shop icon by ketufaispikinut, based on TiniestShark's textures.",
"size": {
"x": 32,
"y": 32
@@ -18,12 +18,28 @@
},
{
"name": "biodegrade"
}
,
},
{
"name": "blind_sting"
},
{
"name": "mute_sting"
},
{
"name": "hallucination_sting"
},
{
"name": "neurodepressant_sting"
},
{
"name": "cryogenic_sting"
},
{
"name": "lethal_sting"
},
{
"name": "extract_dna_on"
}
,
},
{
"name": "extract_dna_off"
},
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB