diff --git a/Content.Client/Arachne/CocoonSystem.cs b/Content.Client/Cocoon/CocoonSystem.cs similarity index 98% rename from Content.Client/Arachne/CocoonSystem.cs rename to Content.Client/Cocoon/CocoonSystem.cs index 7645c5bc81..d3eb4a8205 100644 --- a/Content.Client/Arachne/CocoonSystem.cs +++ b/Content.Client/Cocoon/CocoonSystem.cs @@ -1,4 +1,4 @@ -using Content.Shared.Arachne; +using Content.Shared.Cocoon; using Content.Shared.Humanoid; using Robust.Client.GameObjects; using Robust.Shared.Containers; diff --git a/Content.Server/Arachne/ArachneSystem.cs b/Content.Server/Cocoon/CocoonerSystem.cs similarity index 51% rename from Content.Server/Arachne/ArachneSystem.cs rename to Content.Server/Cocoon/CocoonerSystem.cs index 27e8851247..a53ebbb5f5 100644 --- a/Content.Server/Arachne/ArachneSystem.cs +++ b/Content.Server/Cocoon/CocoonerSystem.cs @@ -1,7 +1,6 @@ -using Content.Shared.Arachne; +using Content.Shared.Cocoon; using Content.Shared.IdentityManagement; using Content.Shared.Verbs; -using Content.Shared.Buckle.Components; using Content.Shared.DoAfter; using Content.Shared.Stunnable; using Content.Shared.Eye.Blinding.Systems; @@ -10,29 +9,21 @@ using Content.Shared.Damage; using Content.Shared.Administration.Logs; using Content.Shared.Database; using Content.Shared.Humanoid; -using Content.Server.Buckle.Systems; using Content.Server.Popups; using Content.Server.DoAfter; -using Content.Server.Body.Components; -using Content.Server.Vampiric; using Content.Server.Speech.Components; using Robust.Shared.Containers; -using Robust.Shared.Utility; -using Robust.Server.Console; +using Content.Shared.Mobs.Components; -namespace Content.Server.Arachne +namespace Content.Server.Cocoon { - public sealed class ArachneSystem : EntitySystem + public sealed class CocooningSystem : EntitySystem { [Dependency] private readonly PopupSystem _popupSystem = default!; [Dependency] private readonly DoAfterSystem _doAfter = default!; - [Dependency] private readonly BuckleSystem _buckleSystem = default!; [Dependency] private readonly ItemSlotsSystem _itemSlots = default!; [Dependency] private readonly BlindableSystem _blindableSystem = default!; [Dependency] private readonly DamageableSystem _damageableSystem = default!; - - [Dependency] private readonly IServerConsoleHost _host = default!; - [Dependency] private readonly BloodSuckerSystem _bloodSuckerSystem = default!; [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; private const string BodySlot = "body_slot"; @@ -40,27 +31,16 @@ namespace Content.Server.Arachne public override void Initialize() { base.Initialize(); - SubscribeLocalEvent>(AddCocoonVerb); - + SubscribeLocalEvent>(AddCocoonVerb); SubscribeLocalEvent(OnCocEntInserted); SubscribeLocalEvent(OnCocEntRemoved); SubscribeLocalEvent(OnDamageChanged); - SubscribeLocalEvent>(AddSuccVerb); - SubscribeLocalEvent(OnCocoonDoAfter); + SubscribeLocalEvent(OnCocoonDoAfter); } - private void AddCocoonVerb(EntityUid uid, ArachneComponent component, GetVerbsEvent args) + private void AddCocoonVerb(EntityUid uid, CocoonerComponent component, GetVerbsEvent args) { - if (!args.CanAccess || !args.CanInteract) - return; - - if (args.Target == uid) - return; - - if (!TryComp(args.Target, out var bloodstream)) - return; - - if (bloodstream.BloodReagent != component.WebBloodReagent) + if (!args.CanAccess || !args.CanInteract || !HasComp(args.Target)) return; InnateVerb verb = new() @@ -77,31 +57,23 @@ namespace Content.Server.Arachne private void OnCocEntInserted(EntityUid uid, CocoonComponent component, EntInsertedIntoContainerMessage args) { - _blindableSystem.UpdateIsBlind(args.Entity); - EnsureComp(args.Entity); + component.Victim = args.Entity; if (TryComp(args.Entity, out var currentAccent)) - { - component.WasReplacementAccent = true; component.OldAccent = currentAccent.Accent; - currentAccent.Accent = "mumble"; - } else - { - component.WasReplacementAccent = false; - var replacement = EnsureComp(args.Entity); - replacement.Accent = "mumble"; - } + + EnsureComp(args.Entity).Accent = "mumble"; + EnsureComp(args.Entity); + + _blindableSystem.UpdateIsBlind(args.Entity); } private void OnCocEntRemoved(EntityUid uid, CocoonComponent component, EntRemovedFromContainerMessage args) { - if (component.WasReplacementAccent && TryComp(args.Entity, out var replacement)) - { - replacement.Accent = component.OldAccent; - } else - { + if (TryComp(args.Entity, out var replacement)) + replacement.Accent = component.OldAccent ?? replacement.Accent; + else RemComp(args.Entity); - } RemComp(args.Entity); _blindableSystem.UpdateIsBlind(args.Entity); @@ -109,79 +81,24 @@ namespace Content.Server.Arachne private void OnDamageChanged(EntityUid uid, CocoonComponent component, DamageChangedEvent args) { - if (!args.DamageIncreased) - return; - - if (args.DamageDelta == null) - return; - - var body = _itemSlots.GetItemOrNull(uid, BodySlot); - - if (body == null) + if (!args.DamageIncreased || args.DamageDelta == null || component.Victim == null) return; var damage = args.DamageDelta * component.DamagePassthrough; - _damageableSystem.TryChangeDamage(body, damage); + _damageableSystem.TryChangeDamage(component.Victim, damage); } - private void AddSuccVerb(EntityUid uid, CocoonComponent component, GetVerbsEvent args) - { - if (!args.CanAccess || !args.CanInteract) - return; - - if (!TryComp(args.User, out var sucker)) - return; - - if (!sucker.WebRequired) - return; - - var victim = _itemSlots.GetItemOrNull(uid, BodySlot); - - if (victim == null) - return; - - if (!TryComp(victim, out var stream)) - return; - - AlternativeVerb verb = new() - { - Act = () => - { - _bloodSuckerSystem.StartSuccDoAfter(args.User, victim.Value, sucker, stream, false); // start doafter - }, - Text = Loc.GetString("action-name-suck-blood"), - Icon = new SpriteSpecifier.Texture(new ("/Textures/Nyanotrasen/Icons/verbiconfangs.png")), - Priority = 2 - }; - args.Verbs.Add(verb); - } - - private void OnEntRemoved(EntityUid uid, WebComponent web, EntRemovedFromContainerMessage args) - { - if (!TryComp(uid, out var strap)) - return; - - if (HasComp(args.Entity)) - _buckleSystem.StrapSetEnabled(uid, false, strap); - } - - private void StartCocooning(EntityUid uid, ArachneComponent component, EntityUid target) + private void StartCocooning(EntityUid uid, CocoonerComponent component, EntityUid target) { _popupSystem.PopupEntity(Loc.GetString("cocoon-start-third-person", ("target", Identity.Entity(target, EntityManager)), ("spider", Identity.Entity(uid, EntityManager))), uid, Shared.Popups.PopupType.MediumCaution); - _popupSystem.PopupEntity(Loc.GetString("cocoon-start-second-person", ("target", Identity.Entity(target, EntityManager))), uid, uid, Shared.Popups.PopupType.Medium); - var delay = component.CocoonDelay; if (HasComp(target)) delay *= component.CocoonKnockdownMultiplier; - // Is it good practice to use empty data just to disambiguate doafters - // Who knows, there's no docs! - var ev = new ArachneCocoonDoAfterEvent(); - - var args = new DoAfterArgs(EntityManager, uid, delay, ev, uid, target: target) + var args = new DoAfterArgs(EntityManager, uid, delay, new CocoonDoAfterEvent(), uid, target: target) { BreakOnUserMove = true, BreakOnTargetMove = true, @@ -190,7 +107,7 @@ namespace Content.Server.Arachne _doAfter.TryStartDoAfter(args); } - private void OnCocoonDoAfter(EntityUid uid, ArachneComponent component, ArachneCocoonDoAfterEvent args) + private void OnCocoonDoAfter(EntityUid uid, CocoonerComponent component, CocoonDoAfterEvent args) { if (args.Handled || args.Cancelled || args.Args.Target == null) return; diff --git a/Content.Server/Vampire/BloodSuckerSystem.cs b/Content.Server/Vampire/BloodSuckerSystem.cs index 81d2854318..41afe0d666 100644 --- a/Content.Server/Vampire/BloodSuckerSystem.cs +++ b/Content.Server/Vampire/BloodSuckerSystem.cs @@ -6,6 +6,7 @@ using Content.Shared.Interaction; using Content.Shared.Inventory; using Content.Shared.Administration.Logs; using Content.Shared.Vampiric; +using Content.Shared.Cocoon; using Content.Server.Atmos.Components; using Content.Server.Body.Components; using Content.Server.Body.Systems; @@ -45,23 +46,28 @@ namespace Content.Server.Vampiric private void AddSuccVerb(EntityUid uid, BloodSuckerComponent component, GetVerbsEvent args) { - if (args.User == args.Target) + + var victim = args.Target; + var ignoreClothes = false; + + if (TryComp(args.Target, out var cocoon)) + { + victim = cocoon.Victim ?? args.Target; + ignoreClothes = cocoon.Victim != null; + } else if (component.WebRequired) return; - if (component.WebRequired) - return; // handled elsewhere - if (!TryComp(args.Target, out var bloodstream)) - return; - if (!args.CanAccess) + + if (!TryComp(victim, out var bloodstream) || args.User == victim || !args.CanAccess) return; InnateVerb verb = new() { Act = () => { - StartSuccDoAfter(uid, args.Target, component, bloodstream); // start doafter + StartSuccDoAfter(uid, victim, component, bloodstream, !ignoreClothes); // start doafter }, Text = Loc.GetString("action-name-suck-blood"), - Icon = new SpriteSpecifier.Texture(new ("/Textures/Nyanotrasen/Icons/verbiconfangs.png")), + Icon = new SpriteSpecifier.Texture(new("/Textures/Nyanotrasen/Icons/verbiconfangs.png")), Priority = 2 }; args.Verbs.Add(verb); @@ -80,10 +86,8 @@ namespace Content.Server.Vampiric if (_prototypeManager.TryIndex("Brute", out var brute) && args.Damageable.Damage.TryGetDamageInGroup(brute, out var bruteTotal) && _prototypeManager.TryIndex("Airloss", out var airloss) && args.Damageable.Damage.TryGetDamageInGroup(airloss, out var airlossTotal)) - { if (bruteTotal == 0 && airlossTotal == 0) RemComp(uid); - } } private void OnDoAfter(EntityUid uid, BloodSuckerComponent component, BloodSuckDoAfterEvent args) @@ -96,18 +100,13 @@ namespace Content.Server.Vampiric public void StartSuccDoAfter(EntityUid bloodsucker, EntityUid victim, BloodSuckerComponent? bloodSuckerComponent = null, BloodstreamComponent? stream = null, bool doChecks = true) { - if (!Resolve(bloodsucker, ref bloodSuckerComponent)) - return; - - if (!Resolve(victim, ref stream)) + if (!Resolve(bloodsucker, ref bloodSuckerComponent) || !Resolve(victim, ref stream)) return; if (doChecks) { if (!_interactionSystem.InRangeUnobstructed(bloodsucker, victim)) - { return; - } if (_inventorySystem.TryGetSlotEntity(victim, "head", out var headUid) && HasComp(headUid)) { @@ -125,19 +124,15 @@ namespace Content.Server.Vampiric } if (stream.BloodReagent != "Blood") - { - _popups.PopupEntity(Loc.GetString("bloodsucker-fail-not-blood", ("target", victim)), victim, bloodsucker, Shared.Popups.PopupType.Medium); - return; - } - - if (_solutionSystem.PercentFull(stream.Owner) != 0) + _popups.PopupEntity(Loc.GetString("bloodsucker-not-blood", ("target", victim)), victim, bloodsucker, Shared.Popups.PopupType.Medium); + else if (_solutionSystem.PercentFull(victim) != 0) _popups.PopupEntity(Loc.GetString("bloodsucker-fail-no-blood", ("target", victim)), victim, bloodsucker, Shared.Popups.PopupType.Medium); + else + _popups.PopupEntity(Loc.GetString("bloodsucker-doafter-start", ("target", victim)), victim, bloodsucker, Shared.Popups.PopupType.Medium); _popups.PopupEntity(Loc.GetString("bloodsucker-doafter-start-victim", ("sucker", bloodsucker)), victim, victim, Shared.Popups.PopupType.LargeCaution); - _popups.PopupEntity(Loc.GetString("bloodsucker-doafter-start", ("target", victim)), victim, bloodsucker, Shared.Popups.PopupType.Medium); - var ev = new BloodSuckDoAfterEvent(); - var args = new DoAfterArgs(EntityManager, bloodsucker, bloodSuckerComponent.Delay, ev, bloodsucker, target: victim) + var args = new DoAfterArgs(EntityManager, bloodsucker, bloodSuckerComponent.Delay, new BloodSuckDoAfterEvent(), bloodsucker, target: victim) { BreakOnTargetMove = true, BreakOnUserMove = false, @@ -206,8 +201,5 @@ namespace Content.Server.Vampiric //} return true; } - - private record struct BloodSuckData() - {} } } diff --git a/Content.Shared/Arachne/ArachneComponent.cs b/Content.Shared/Arachne/ArachneComponent.cs deleted file mode 100644 index 04c369cc45..0000000000 --- a/Content.Shared/Arachne/ArachneComponent.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Robust.Shared.GameStates; - -namespace Content.Shared.Arachne -{ - [RegisterComponent, NetworkedComponent] - public sealed partial class ArachneComponent : Component - { - [DataField("cocoonDelay")] - public float CocoonDelay = 12f; - - [DataField("cocoonKnockdownMultiplier")] - public float CocoonKnockdownMultiplier = 0.5f; - - /// - /// Blood reagent required to web up a mob. - /// - - [DataField("webBloodReagent")] - public string WebBloodReagent = "Blood"; - } -} diff --git a/Content.Shared/Arachne/Events.cs b/Content.Shared/Arachne/Events.cs deleted file mode 100644 index 02001286ac..0000000000 --- a/Content.Shared/Arachne/Events.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Robust.Shared.Map; -using Robust.Shared.Serialization; -using Content.Shared.DoAfter; - -namespace Content.Shared.Arachne -{ - [Serializable, NetSerializable] - public sealed partial class ArachneCocoonDoAfterEvent : SimpleDoAfterEvent - { - } -} diff --git a/Content.Shared/Arachne/WebComponent.cs b/Content.Shared/Arachne/WebComponent.cs deleted file mode 100644 index c8284f3943..0000000000 --- a/Content.Shared/Arachne/WebComponent.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Robust.Shared.GameStates; - -namespace Content.Shared.Arachne -{ - [RegisterComponent, NetworkedComponent] - public sealed partial class WebComponent : Component - {} -} diff --git a/Content.Shared/Arachne/CocoonComponent.cs b/Content.Shared/Cocoon/CocoonComponent.cs similarity index 61% rename from Content.Shared/Arachne/CocoonComponent.cs rename to Content.Shared/Cocoon/CocoonComponent.cs index e22e393ffd..66ba6e6dd3 100644 --- a/Content.Shared/Arachne/CocoonComponent.cs +++ b/Content.Shared/Cocoon/CocoonComponent.cs @@ -1,13 +1,14 @@ -namespace Content.Shared.Arachne +namespace Content.Shared.Cocoon { [RegisterComponent] public sealed partial class CocoonComponent : Component { - public bool WasReplacementAccent = false; + public string? OldAccent; - public string OldAccent = ""; + public EntityUid? Victim; [DataField("damagePassthrough")] public float DamagePassthrough = 0.5f; + } } diff --git a/Content.Shared/Cocoon/CocoonDoAfterEvent.cs b/Content.Shared/Cocoon/CocoonDoAfterEvent.cs new file mode 100644 index 0000000000..55986d2894 --- /dev/null +++ b/Content.Shared/Cocoon/CocoonDoAfterEvent.cs @@ -0,0 +1,10 @@ +using Robust.Shared.Serialization; +using Content.Shared.DoAfter; + +namespace Content.Shared.Cocoon +{ + [Serializable, NetSerializable] + public sealed partial class CocoonDoAfterEvent : SimpleDoAfterEvent + { + } +} diff --git a/Content.Shared/Cocoon/CocoonerComponent.cs b/Content.Shared/Cocoon/CocoonerComponent.cs new file mode 100644 index 0000000000..17cce97309 --- /dev/null +++ b/Content.Shared/Cocoon/CocoonerComponent.cs @@ -0,0 +1,14 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Cocoon +{ + [RegisterComponent, NetworkedComponent] + public sealed partial class CocoonerComponent : Component + { + [DataField("cocoonDelay")] + public float CocoonDelay = 12f; + + [DataField("cocoonKnockdownMultiplier")] + public float CocoonKnockdownMultiplier = 0.5f; + } +} diff --git a/Resources/Locale/en-US/abilities/bloodsucker.ftl b/Resources/Locale/en-US/abilities/bloodsucker.ftl index d956eaff84..c8aa0ed854 100644 --- a/Resources/Locale/en-US/abilities/bloodsucker.ftl +++ b/Resources/Locale/en-US/abilities/bloodsucker.ftl @@ -4,9 +4,9 @@ action-description-suck-blood = Suck the blood of the victim in your hand. bloodsucker-fail-helmet = You'd need to remove {THE($helmet)}. bloodsucker-fail-mask = You'd need to remove your mask! -bloodsucker-fail-not-blood = { CAPITALIZE(SUBJECT($target)) } doesn't have delicious, nourishing mortal blood. -bloodsucker-fail-no-blood = { CAPITALIZE(SUBJECT($target)) } has no blood in { POSS-ADJ($target) } body. -bloodsucker-fail-no-blood-bloodsucked = { CAPITALIZE(SUBJECT($target)) } has been sucked dry. +bloodsucker-not-blood = {$target} doesn't have delicious, nourishing blood. +bloodsucker-fail-no-blood = {$target} has no blood in { POSS-ADJ($target) } body. +bloodsucker-fail-no-blood-bloodsucked = {$target} has been sucked dry. bloodsucker-blood-sucked = You suck some blood from {$target}. bloodsucker-doafter-start = You try to suck blood from {$target}. diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index d6c274751d..8dd348f47e 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -2357,6 +2357,9 @@ - type: RandomBark barkType: hissing barkMultiplier: 0.3 + - type: BloodSucker + webRequired: true + - type: Cocooner - type: entity name: tarantula diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/space.yml b/Resources/Prototypes/Entities/Mobs/NPCs/space.yml index 919441dff5..4ea766c314 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/space.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/space.yml @@ -277,6 +277,9 @@ Unsexed: UnisexArachnid - type: TypingIndicator proto: spider + - type: BloodSucker + webRequired: true + - type: Cocooner - type: entity id: MobSpiderSpaceSalvage diff --git a/Resources/Prototypes/Entities/Mobs/Species/arachne.yml b/Resources/Prototypes/Entities/Mobs/Species/arachne.yml index e704f15725..24ebafd91d 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/arachne.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/arachne.yml @@ -111,7 +111,7 @@ bloodRegenerationThirst: 4 # 1 unit of demon's blood satiates 4 thirst - type: BloodSucker webRequired: true - - type: Arachne + - type: Cocooner - type: DamageVisuals targetLayers: - "enum.HumanoidVisualLayers.Chest" diff --git a/Resources/Prototypes/Entities/Mobs/Species/arachnid.yml b/Resources/Prototypes/Entities/Mobs/Species/arachnid.yml index be66c8093e..b5c450ba1d 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/arachnid.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/arachnid.yml @@ -44,6 +44,9 @@ methods: [Touch] effects: - !type:WashCreamPieReaction + - type: BloodSucker + webRequired: true + - type: Cocooner # Damage (Self) - type: Bloodstream bloodReagent: CopperBlood diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/NPCs/mutants.yml b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/NPCs/mutants.yml index ee75dd3c8e..b3de9e0191 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/NPCs/mutants.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/NPCs/mutants.yml @@ -109,7 +109,7 @@ # path: /Audio/Animals/snake_hiss.ogg # - type: Puller # needsHands: false -# - type: Arachne +# - type: Cocooner # cocoonDelay: 8 # - type: SolutionContainerManager # solutions: