From 61fde0d02e9d03017d333f602a11b863496d6948 Mon Sep 17 00:00:00 2001 From: fenndragon Date: Wed, 11 Dec 2024 21:39:25 -0700 Subject: [PATCH 01/25] Update xenopet.yml --- .../Prototypes/Entities/Mobs/NPCs/xenopet.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/xenopet.yml b/Resources/Prototypes/Entities/Mobs/NPCs/xenopet.yml index 98acc0fa4cd..5aae51b9bd4 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/xenopet.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/xenopet.yml @@ -382,3 +382,21 @@ - MobXenoNeutralDrone - MobXenoNeutralRavager chance: 1 + +- type: entity + name: Rain Lizard + parent: MobXenoNeutralRouny + id: Mobrainlizard + description: A darkscaled lizard with a colorful head + components: + - type: NpcFactionMember + factions: + - PetsNT + - type: Tool + speed: 6 + qualities: + - Prying + - type: Sprite + drawdepth: Mobs + sprite: Mobs/Aliens/FXS/rouny.rsi + scale: 0.6, 0.6 From e403ce579d5b891cb1a386ba68f136f5ab9318e9 Mon Sep 17 00:00:00 2001 From: fenndragon Date: Thu, 12 Dec 2024 13:00:44 -0700 Subject: [PATCH 02/25] Create folder --- Content.Shared/SegmentedEntity/folder | 1 + 1 file changed, 1 insertion(+) create mode 100644 Content.Shared/SegmentedEntity/folder diff --git a/Content.Shared/SegmentedEntity/folder b/Content.Shared/SegmentedEntity/folder new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/Content.Shared/SegmentedEntity/folder @@ -0,0 +1 @@ + From c9ef2868aadceb5af17a73b51e4cfdca89e58d94 Mon Sep 17 00:00:00 2001 From: fenndragon Date: Thu, 12 Dec 2024 13:01:30 -0700 Subject: [PATCH 03/25] Add files via upload --- .../SegmentedEntity/SegmentSpawnedEvent.cs | 10 + .../SegmentedEntityComponent.cs | 108 +++++++ .../SegmentedEntitySegmentComponent.cs | 35 ++ .../SegmentedEntity/SegmentedEntitySystem.cs | 306 ++++++++++++++++++ 4 files changed, 459 insertions(+) create mode 100644 Content.Shared/SegmentedEntity/SegmentSpawnedEvent.cs create mode 100644 Content.Shared/SegmentedEntity/SegmentedEntityComponent.cs create mode 100644 Content.Shared/SegmentedEntity/SegmentedEntitySegmentComponent.cs create mode 100644 Content.Shared/SegmentedEntity/SegmentedEntitySystem.cs diff --git a/Content.Shared/SegmentedEntity/SegmentSpawnedEvent.cs b/Content.Shared/SegmentedEntity/SegmentSpawnedEvent.cs new file mode 100644 index 00000000000..6b0e0407a32 --- /dev/null +++ b/Content.Shared/SegmentedEntity/SegmentSpawnedEvent.cs @@ -0,0 +1,10 @@ +namespace Content.Shared.SegmentedEntity; +public sealed class SegmentSpawnedEvent : EntityEventArgs +{ + public EntityUid Lamia = default!; + + public SegmentSpawnedEvent(EntityUid lamia) + { + Lamia = lamia; + } +} diff --git a/Content.Shared/SegmentedEntity/SegmentedEntityComponent.cs b/Content.Shared/SegmentedEntity/SegmentedEntityComponent.cs new file mode 100644 index 00000000000..83f2450539f --- /dev/null +++ b/Content.Shared/SegmentedEntity/SegmentedEntityComponent.cs @@ -0,0 +1,108 @@ +/* +* Delta-V - This file is licensed under AGPLv3 +* Copyright (c) 2024 Delta-V Contributors +* See AGPLv3.txt for details. +*/ + +using Robust.Shared.GameStates; +using Robust.Shared.Serialization; +using Robust.Shared.Utility; + +namespace Content.Shared.SegmentedEntity +{ + /// + /// Controls initialization of any Multi-segmented entity + /// + [RegisterComponent, NetworkedComponent] + [AutoGenerateComponentState] + public sealed partial class SegmentedEntityComponent : Component + { + /// + /// A list of each UID attached to the Lamia, in order of spawn + /// + [DataField("segments")] + [AutoNetworkedField] + public List Segments = new(); + + /// + /// A clamped variable that represents the number of segments to be spawned + /// + [DataField("numberOfSegments")] + public int NumberOfSegments = 18; + + /// + /// How wide the initial segment should be. + /// + [DataField("initialRadius")] + public float InitialRadius = 0.3f; + + /// + /// Texture of the segment. + /// + [DataField("texturePath", required: true)] + public string TexturePath; + + /// + /// If UseTaperSystem is true, this constant represents the rate at which a segmented entity will taper towards the tip. Tapering is on a logarithmic scale, and will asymptotically approach 0. + /// + [DataField("offsetConstant")] + public float OffsetConstant = 1.03f; + + /// + /// Represents the prototype used to parent all segments + /// + [DataField("initialSegmentId")] + public string InitialSegmentId = "LamiaInitialSegment"; + + /// + /// Represents the segment prototype to be spawned + /// + [DataField("segmentId")] + public string SegmentId = "LamiaSegment"; + + /// + /// How much to slim each successive segment. + /// + [DataField("slimFactor")] + public float SlimFactor = 0.93f; + + /// + /// Set to 1f for constant width + /// + [DataField("useTaperSystem")] + public bool UseTaperSystem = true; + + /// + /// The standard distance between the centerpoint of each segment. + /// + [DataField("staticOffset")] + public float StaticOffset = 0.15f; + + /// + /// The standard sprite scale of each segment. + /// + [DataField("staticScale")] + public float StaticScale = 1f; + + /// + /// Used to more finely tune how much damage should be transfered from tail to body. + /// + [DataField("damageModifierOffset")] + public float DamageModifierOffset = 0.4f; + + /// + /// A clamped variable that represents how far from the tip should tapering begin. + /// + [DataField("taperOffset")] + public int TaperOffset = 18; + + /// + /// Coefficient used to finely tune how much explosion damage should be transfered to the body. This is calculated multiplicatively with the derived damage modifier set. + /// + [DataField("explosiveModifierOffset")] + public float ExplosiveModifierOffset = 0.1f; + + [DataField("bulletPassover")] + public bool BulletPassover = true; + } +} diff --git a/Content.Shared/SegmentedEntity/SegmentedEntitySegmentComponent.cs b/Content.Shared/SegmentedEntity/SegmentedEntitySegmentComponent.cs new file mode 100644 index 00000000000..b29e282e9e3 --- /dev/null +++ b/Content.Shared/SegmentedEntity/SegmentedEntitySegmentComponent.cs @@ -0,0 +1,35 @@ +/* +* Delta-V - This file is licensed under AGPLv3 +* Copyright (c) 2024 Delta-V Contributors +* See AGPLv3.txt for details. +*/ + +using Robust.Shared.GameStates; + +namespace Content.Shared.SegmentedEntity +{ + /// + /// Lamia segment + /// + [RegisterComponent] + [NetworkedComponent] + public sealed partial class SegmentedEntitySegmentComponent : Component + { + [DataField("AttachedToUid")] + public EntityUid AttachedToUid = default!; + public float DamageModifyFactor = default!; + public float OffsetSwitching = default!; + public float ScaleFactor = default!; + [DataField("DamageModifierCoefficient")] + public float DamageModifierCoefficient = default!; + public float ExplosiveModifyFactor = default!; + public float OffsetConstant = default!; + [DataField("Lamia")] + public EntityUid Lamia = default!; + public int MaxSegments = default!; + public int SegmentNumber = default!; + public float DamageModifierConstant = default!; + [DataField("segmentId")] + public string? segmentId; + } +} diff --git a/Content.Shared/SegmentedEntity/SegmentedEntitySystem.cs b/Content.Shared/SegmentedEntity/SegmentedEntitySystem.cs new file mode 100644 index 00000000000..5ab786c429e --- /dev/null +++ b/Content.Shared/SegmentedEntity/SegmentedEntitySystem.cs @@ -0,0 +1,306 @@ +using Robust.Shared.Physics; +using Content.Shared.Damage; +using Content.Shared.Explosion; +using Content.Shared.Humanoid; +using Content.Shared.Clothing.Components; +using Content.Shared.Inventory.Events; +using Content.Shared.Tag; +using Content.Shared.Storage.Components; +using Content.Shared.Weapons.Ranged.Events; +using Content.Shared.Teleportation.Components; +using Robust.Shared.Map; +using Robust.Shared.Physics.Systems; +using Robust.Shared.Physics.Components; +using System.Numerics; +using Robust.Shared.Network; +using Robust.Shared.GameStates; + +namespace Content.Shared.SegmentedEntity +{ + public sealed partial class LamiaSystem : EntitySystem + { + [Dependency] private readonly TagSystem _tagSystem = default!; + [Dependency] private readonly DamageableSystem _damageableSystem = default!; + [Dependency] private readonly SharedJointSystem _jointSystem = default!; + [Dependency] private readonly INetManager _net = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + + Queue<(SegmentedEntitySegmentComponent segment, EntityUid lamia)> _segments = new(); + + [ValidatePrototypeId] + private const string LamiaHardsuitTag = "AllowLamiaHardsuit"; + public override void Initialize() + { + base.Initialize(); + //Parent subscriptions + SubscribeLocalEvent(OnShootHitscan); + SubscribeLocalEvent(OnLamiaStorageInsertAttempt); + SubscribeLocalEvent(OnDidEquipEvent); + SubscribeLocalEvent(OnDidUnequipEvent); + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnJointRemoved); + SubscribeLocalEvent(OnParentChanged); + SubscribeLocalEvent(OnStoreSnekAttempt); + + //Child subscriptions + SubscribeLocalEvent(OnSegmentStorageInsertAttempt); + SubscribeLocalEvent(OnSnekBoom); + SubscribeLocalEvent(HandleDamageTransfer); + SubscribeLocalEvent(HandleSegmentDamage); + } + public override void Update(float frameTime) + { + //I HATE THIS, SO MUCH. I AM FORCED TO DEAL WITH THIS MONSTROSITY. PLEASE. SEND HELP. + base.Update(frameTime); + foreach (var segment in _segments) + { + var segmentUid = segment.segment.Owner; + var attachedUid = segment.segment.AttachedToUid; + if (!Exists(segmentUid) || !Exists(attachedUid) + || MetaData(segmentUid).EntityLifeStage > EntityLifeStage.MapInitialized + || MetaData(attachedUid).EntityLifeStage > EntityLifeStage.MapInitialized + || Transform(segmentUid).MapID == MapId.Nullspace + || Transform(attachedUid).MapID == MapId.Nullspace) + continue; + + EnsureComp(segmentUid); + EnsureComp(attachedUid); // Hello I hate tests + + // This is currently HERE and not somewhere more sane like OnInit because HumanoidAppearanceComponent is for whatever + // ungodly reason not initialized when ComponentStartup is called. Kill me. + var humanoidFactor = TryComp(segment.segment.Lamia, out var humanoid) ? (humanoid.Height + humanoid.Width) / 2 : 1; + + var ev = new SegmentSpawnedEvent(segment.lamia); + RaiseLocalEvent(segmentUid, ev, false); + + if (segment.segment.SegmentNumber == 1) + { + _transform.SetCoordinates(segmentUid, Transform(attachedUid).Coordinates); + var revoluteJoint = _jointSystem.CreateWeldJoint(attachedUid, segmentUid, id: "Segment" + segment.segment.SegmentNumber + segment.segment.Lamia); + revoluteJoint.CollideConnected = false; + } + if (segment.segment.SegmentNumber <= segment.segment.MaxSegments) + _transform.SetCoordinates(segmentUid, Transform(attachedUid).Coordinates.Offset(new Vector2(0, segment.segment.OffsetSwitching * humanoidFactor))); + else + _transform.SetCoordinates(segmentUid, Transform(attachedUid).Coordinates.Offset(new Vector2(0, segment.segment.OffsetSwitching * humanoidFactor))); + + var joint = _jointSystem.CreateDistanceJoint(attachedUid, segmentUid, id: ("Segment" + segment.segment.SegmentNumber + segment.segment.Lamia)); + joint.CollideConnected = false; + joint.Stiffness = 0.2f; + } + _segments.Clear(); + } + private void OnInit(EntityUid uid, SegmentedEntityComponent component, ComponentInit args) + { + EnsureComp(uid); //Temporary, remove when Portal handling is added + Math.Clamp(component.NumberOfSegments, 2, 18); + Math.Clamp(component.TaperOffset, 1, component.NumberOfSegments - 1); + SpawnSegments(uid, component); + } + + private void OnShutdown(EntityUid uid, SegmentedEntityComponent component, ComponentShutdown args) + { + if (_net.IsClient) + return; + + foreach (var segment in component.Segments) + { + QueueDel(GetEntity(segment)); + } + + component.Segments.Clear(); + } + + /// + /// TODO: Full Self-Test function that intelligently checks the status of where everything is, and calls whatever + /// functions are appropriate + /// + /// + /// + public void SegmentSelfTest(EntityUid uid, SegmentedEntityComponent component) + { + + } + + /// + /// TODO: Function that ensures clothing visuals, to be called anytime the tail is reset + /// + /// + /// + private void EnsureSnekSock(EntityUid uid, SegmentedEntityComponent segment) + { + + } + public void OnStoreSnekAttempt(EntityUid uid, SegmentedEntityComponent comp, ref StoreMobInItemContainerAttemptEvent args) + { + args.Cancelled = true; + } + + private void OnJointRemoved(EntityUid uid, SegmentedEntityComponent component, JointRemovedEvent args) + { + if (!component.Segments.Contains(GetNetEntity(args.OtherEntity))) + return; + + DeleteSegments(component); + } + + private void DeleteSegments(SegmentedEntityComponent component) + { + if (_net.IsClient) + return; //Client is not allowed to predict QueueDel, it'll throw an error(but won't crash in Release build) + + foreach (var segment in component.Segments) + QueueDel(GetEntity(segment)); + + component.Segments.Clear(); + } + + /// + /// Public call for a SegmentedEntity to reset their tail completely. + /// + /// + /// + public void RespawnSegments(EntityUid uid, SegmentedEntityComponent component) + { + DeleteSegments(component); + SpawnSegments(uid, component); + } + + private void SpawnSegments(EntityUid uid, SegmentedEntityComponent component) + { + if (_net.IsClient) + return; //Client is not allowed to spawn entities. It won't throw an error, but it'll make fake client entities. + + int i = 1; + var addTo = uid; + while (i <= component.NumberOfSegments + 1) + { + var segment = AddSegment(addTo, uid, component, i); + addTo = segment; + i++; + } + + Dirty(uid, component); + } + + private EntityUid AddSegment(EntityUid segmentuid, EntityUid parentuid, SegmentedEntityComponent segmentedComponent, int segmentNumber) + { + float taperConstant = segmentedComponent.NumberOfSegments - segmentedComponent.TaperOffset; + EntityUid segment; + if (segmentNumber == 1) + segment = EntityManager.SpawnEntity(segmentedComponent.InitialSegmentId, Transform(segmentuid).Coordinates); + else + segment = EntityManager.SpawnEntity(segmentedComponent.SegmentId, Transform(segmentuid).Coordinates); + + var segmentComponent = EnsureComp(segment); + + segmentComponent.Lamia = parentuid; + segmentComponent.AttachedToUid = segmentuid; + segmentComponent.DamageModifierConstant = segmentedComponent.NumberOfSegments * segmentedComponent.DamageModifierOffset; + float damageModifyCoefficient = segmentComponent.DamageModifierConstant / segmentedComponent.NumberOfSegments; + segmentComponent.DamageModifyFactor = segmentComponent.DamageModifierConstant * damageModifyCoefficient; + segmentComponent.ExplosiveModifyFactor = 1 / segmentComponent.DamageModifyFactor / (segmentedComponent.NumberOfSegments * segmentedComponent.ExplosiveModifierOffset); + segmentComponent.SegmentNumber = segmentNumber; + segmentComponent.Owner = segment; + + if (segmentedComponent.UseTaperSystem == true) + { + if (segmentNumber >= taperConstant) + { + segmentComponent.OffsetSwitching = segmentedComponent.StaticOffset + * MathF.Pow(segmentedComponent.OffsetConstant, segmentNumber - taperConstant); + + segmentComponent.ScaleFactor = segmentedComponent.StaticScale + * MathF.Pow(1f / segmentedComponent.OffsetConstant, segmentNumber - taperConstant); + } + if (segmentNumber < taperConstant) + { + segmentComponent.OffsetSwitching = segmentedComponent.StaticOffset; + segmentComponent.ScaleFactor = segmentedComponent.StaticScale; + } + } + else + { + segmentComponent.OffsetSwitching = segmentedComponent.StaticOffset; + segmentComponent.ScaleFactor = segmentedComponent.StaticScale; + } + + // We invert the Y axis offset on every odd numbered tail so that the segmented entity spawns in a neat pile + // Rather than stretching across 5 to 10 vertical tiles, and potentially getting trapped in a wall + if (segmentNumber % 2 != 0) + { + segmentComponent.OffsetSwitching *= -1; + } + + EnsureComp(segment); //Not temporary, segments must never be allowed to go through portals for physics limitation reasons + _segments.Enqueue((segmentComponent, parentuid)); + segmentedComponent.Segments.Add(GetNetEntity(segment)); + return segment; + } + + private void HandleSegmentDamage(EntityUid uid, SegmentedEntitySegmentComponent component, DamageModifyEvent args) + { + if (args.Origin == component.Lamia) + args.Damage *= 0; + args.Damage = args.Damage / component.DamageModifyFactor; + } + private void HandleDamageTransfer(EntityUid uid, SegmentedEntitySegmentComponent component, DamageChangedEvent args) + { + if (args.DamageDelta == null) return; + _damageableSystem.TryChangeDamage(component.Lamia, args.DamageDelta); + } + + private void OnLamiaStorageInsertAttempt(EntityUid uid, SegmentedEntityComponent comp, ref InsertIntoEntityStorageAttemptEvent args) + { + args.Cancelled = true; + } + + private void OnSegmentStorageInsertAttempt(EntityUid uid, SegmentedEntitySegmentComponent comp, ref InsertIntoEntityStorageAttemptEvent args) + { + args.Cancelled = true; + } + + private void OnDidEquipEvent(EntityUid equipee, SegmentedEntityComponent component, DidEquipEvent args) + { + if (!TryComp(args.Equipment, out var clothing)) return; + if (args.Slot == "outerClothing" && _tagSystem.HasTag(args.Equipment, LamiaHardsuitTag)) + { + // TODO: Switch segment sprite + } + } + + private void OnSnekBoom(EntityUid uid, SegmentedEntitySegmentComponent component, ref GetExplosionResistanceEvent args) + { + args.DamageCoefficient = component.ExplosiveModifyFactor; + } + + private void OnDidUnequipEvent(EntityUid equipee, SegmentedEntityComponent component, DidUnequipEvent args) + { + if (args.Slot == "outerClothing" && _tagSystem.HasTag(args.Equipment, LamiaHardsuitTag)) + { + // TODO: Revert to default segment sprite + } + } + + private void OnShootHitscan(EntityUid uid, SegmentedEntityComponent component, ref HitScanAfterRayCastEvent args) + { + if (args.RayCastResults == null) return; + + var entityList = new List(); + foreach (var entity in args.RayCastResults) + { + if (!component.Segments.Contains(GetNetEntity(entity.HitEntity))) + entityList.Add(entity); + } + args.RayCastResults = entityList; + } + + private void OnParentChanged(EntityUid uid, SegmentedEntityComponent component, ref EntParentChangedMessage args) + { + //If the change was NOT to a different map + //if (args.OldMapId == args.Transform.MapUid) + // RespawnSegments(uid, component); + } + } +} From 5a08f7f094ac98311d9fbca5edf264081b83441c Mon Sep 17 00:00:00 2001 From: fenndragon Date: Thu, 12 Dec 2024 13:09:00 -0700 Subject: [PATCH 04/25] Add files via upload --- .../Ranged/Events/HitScanAfterRayCastEvent.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 Content.Shared/Weapons/Ranged/Events/HitScanAfterRayCastEvent.cs diff --git a/Content.Shared/Weapons/Ranged/Events/HitScanAfterRayCastEvent.cs b/Content.Shared/Weapons/Ranged/Events/HitScanAfterRayCastEvent.cs new file mode 100644 index 00000000000..99bfd1eabc0 --- /dev/null +++ b/Content.Shared/Weapons/Ranged/Events/HitScanAfterRayCastEvent.cs @@ -0,0 +1,17 @@ +using Robust.Shared.Physics; + +namespace Content.Shared.Weapons.Ranged.Events; + +/// +/// Raised after an entity fires a hitscan weapon, but before the list is truncated to the first target. Necessary for Entities that need to prevent friendly fire +/// +[ByRefEvent] +public struct HitScanAfterRayCastEvent +{ + public List? RayCastResults; + + public HitScanAfterRayCastEvent(List? rayCastResults) + { + RayCastResults = rayCastResults; + } +} From c9326f87c2683b062b392f541df17c3887bcf77c Mon Sep 17 00:00:00 2001 From: fenndragon Date: Thu, 12 Dec 2024 13:13:16 -0700 Subject: [PATCH 05/25] Add files via upload --- .../Teleportation/Components/PortalExemptComponent.cs | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 Content.Shared/Teleportation/Components/PortalExemptComponent.cs diff --git a/Content.Shared/Teleportation/Components/PortalExemptComponent.cs b/Content.Shared/Teleportation/Components/PortalExemptComponent.cs new file mode 100644 index 00000000000..28043808e09 --- /dev/null +++ b/Content.Shared/Teleportation/Components/PortalExemptComponent.cs @@ -0,0 +1,8 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Teleportation.Components; + +[RegisterComponent, NetworkedComponent] +public sealed partial class PortalExemptComponent : Component +{ +} From 0b9b729366ff5279dd47708aad08184b6052b3b0 Mon Sep 17 00:00:00 2001 From: fenndragon Date: Thu, 12 Dec 2024 14:13:48 -0700 Subject: [PATCH 06/25] Update xenopet.yml --- Resources/Prototypes/Entities/Mobs/NPCs/xenopet.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/xenopet.yml b/Resources/Prototypes/Entities/Mobs/NPCs/xenopet.yml index 5aae51b9bd4..7e7476cdbb4 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/xenopet.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/xenopet.yml @@ -400,3 +400,7 @@ drawdepth: Mobs sprite: Mobs/Aliens/FXS/rouny.rsi scale: 0.6, 0.6 + - type: SegmentedEntity + numberOfSegments: 2 + texturePath: /Textures/Floof/Mobs/Animals/rainlizard.rsi/segment.png + - type: PortalExempt From 6ea356146385bbdf7a90a250f729520a7cea6fef Mon Sep 17 00:00:00 2001 From: fenndragon Date: Thu, 12 Dec 2024 14:15:57 -0700 Subject: [PATCH 07/25] Update xenopet.yml --- Resources/Prototypes/Entities/Mobs/NPCs/xenopet.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/xenopet.yml b/Resources/Prototypes/Entities/Mobs/NPCs/xenopet.yml index 7e7476cdbb4..c0f84084e13 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/xenopet.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/xenopet.yml @@ -398,7 +398,7 @@ - Prying - type: Sprite drawdepth: Mobs - sprite: Mobs/Aliens/FXS/rouny.rsi + sprite: /Textures/Floof/Mobs/Animals/rainlizard.rsi scale: 0.6, 0.6 - type: SegmentedEntity numberOfSegments: 2 From 33717d3c3e67e96cb0894ae0c24d325d7d2efc34 Mon Sep 17 00:00:00 2001 From: fenndragon Date: Thu, 12 Dec 2024 14:21:33 -0700 Subject: [PATCH 08/25] sprites --- .../Animals/rainlizard.rsi/lizardheadopen.png | Bin 0 -> 4377 bytes .../Mobs/Animals/rainlizard.rsi/meta.json | 25 ++++++++++++++++++ .../Mobs/Animals/rainlizard.rsi/running.png | Bin 0 -> 3565 bytes .../Mobs/Animals/rainlizard.rsi/segment.png | Bin 0 -> 726 bytes .../Floof/Mobs/Animals/rainlizard.rsi/tip.png | Bin 0 -> 640 bytes 5 files changed, 25 insertions(+) create mode 100644 Resources/Textures/Floof/Mobs/Animals/rainlizard.rsi/lizardheadopen.png create mode 100644 Resources/Textures/Floof/Mobs/Animals/rainlizard.rsi/meta.json create mode 100644 Resources/Textures/Floof/Mobs/Animals/rainlizard.rsi/running.png create mode 100644 Resources/Textures/Floof/Mobs/Animals/rainlizard.rsi/segment.png create mode 100644 Resources/Textures/Floof/Mobs/Animals/rainlizard.rsi/tip.png diff --git a/Resources/Textures/Floof/Mobs/Animals/rainlizard.rsi/lizardheadopen.png b/Resources/Textures/Floof/Mobs/Animals/rainlizard.rsi/lizardheadopen.png new file mode 100644 index 0000000000000000000000000000000000000000..dfcc1719b807bff1e732211983f74c9ff2bd8908 GIT binary patch literal 4377 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 y-Z;*-xYAX9X8WNB|8RBvx=!KdMT000em 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 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Floof/Mobs/Animals/rainlizard.rsi/segment.png b/Resources/Textures/Floof/Mobs/Animals/rainlizard.rsi/segment.png new file mode 100644 index 0000000000000000000000000000000000000000..4f1bdce63d4d7b9891990ab8da77af7093a28660 GIT binary patch literal 726 zcmV;{0xA88P)2R|084ld5RI=Bjg;0K7agOj3*l=#1-&?3fz<9@um_qclp_?uOx zn!#B>)hr{EN(#CBst|fb7(qlaiZO|q`kW}H;W@tU;p6LFf@gW3`*ZXu1(N|jfq0hb zhDE$VJiTe@ocD=itSl+S=fvX%U6A;Z>$1yloJ$V-d1lPWX6A`w#6qcyER!A z{35wza#g^{v49#>NRA);4}N!R7AGg%q(~Czd9m$}VIZ^%v>LYkeQevU6Cm&mTxlJD ztqIJ0lHTZO@gtyr8@RacX!0I#xdRM5>5?HiQh=tvSOnhB=$rCD^cLt{b9-y=TA@Z*OeDr{R16007EKL_t(oh3(ft z62%Y<2H@WfEOH0#e;S9d=ncG;woLGJqR*R&O&6m1TEs9ZC5)Q)J^?_C$1oNEh%5jg zgkVOoW(1f`K8yGYFq{02P=Esd1CX~G7rX*+&u5%-X98~TN&@PdLS(N$5aR(Ni~P(3 zr2wrQ1)w)j3Q+eIQ}8Ar_ZN$h6G{NMuLp2VL2g{&)8wsp)C;o^it0HQvQzFwZtu>b%707*qo IM6N<$f@tJFumAu6 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Floof/Mobs/Animals/rainlizard.rsi/tip.png b/Resources/Textures/Floof/Mobs/Animals/rainlizard.rsi/tip.png new file mode 100644 index 0000000000000000000000000000000000000000..a137ca498612b448b8d7d58a6ea80c081735baab GIT binary patch literal 640 zcmV-`0)PF9P)gO8j3^Xc6PVaX;SOd)&PP{LLy; z&7N^U)hr{EN(#CBst|ld5K#;ugowmUeNGh9@El+F@bUF7!Lz*2{W&5^!DN6>Af9Eq zVG(Z-Pj6Z}=Y8TBD@zLTIq{f57bJeH9A zokXv!XlO+LV%1qDyYIjf>wS6atk zYXUQ$q>R{0QjZ1}?5Un!E>G?f`>Nx@1U>6rkxZ7J>IO`ldV(y#@N#+}_&zIDG)J z)K&ThI5-4GOO(Ct@$OJ}Z~vZY_xA%4>~eslgXQ@E000SaNLh0L01FZT01FZU(%pXi z0000RbVXQnQ*UN;cVTj607GSLb9r+hQ*?D?X>TA@Z*OeDr{R16004AJL_t(oh3(g| z3BWK6MA7dJQg{gMKaGcQ;SH23kRk+2h6Kai0{=BCRNS3ER|F71fE9q5IY4Wzd=4-( z?|lGJ>ybtXkOJJ}*Rlu!!homv3POOr0B`XuLV$e$Gw}>UfIR?_coZRk0Mh_~$Q8ic azi Date: Thu, 12 Dec 2024 14:24:19 -0700 Subject: [PATCH 09/25] Update xenopet.yml --- Resources/Prototypes/Entities/Mobs/NPCs/xenopet.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/xenopet.yml b/Resources/Prototypes/Entities/Mobs/NPCs/xenopet.yml index c0f84084e13..420059bb75d 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/xenopet.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/xenopet.yml @@ -399,7 +399,12 @@ - type: Sprite drawdepth: Mobs sprite: /Textures/Floof/Mobs/Animals/rainlizard.rsi - scale: 0.6, 0.6 + scale: 1,1 + offset: 0,0 + layers: + - map: ["enum.DamageStateVisualLayers.Base"] + state: running + - type: SegmentedEntity numberOfSegments: 2 texturePath: /Textures/Floof/Mobs/Animals/rainlizard.rsi/segment.png From 820b53e9abccc664c9f574fdd735bf94a0619980 Mon Sep 17 00:00:00 2001 From: fenndragon Date: Thu, 12 Dec 2024 14:41:32 -0700 Subject: [PATCH 10/25] Create folder --- Resources/Prototypes/Entities/Objects/Specific/Species/folder | 1 + 1 file changed, 1 insertion(+) create mode 100644 Resources/Prototypes/Entities/Objects/Specific/Species/folder diff --git a/Resources/Prototypes/Entities/Objects/Specific/Species/folder b/Resources/Prototypes/Entities/Objects/Specific/Species/folder new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Specific/Species/folder @@ -0,0 +1 @@ + From 5281a5cedef933811347f15b870b6f0ac8f370c8 Mon Sep 17 00:00:00 2001 From: fenndragon Date: Thu, 12 Dec 2024 14:41:55 -0700 Subject: [PATCH 11/25] Add files via upload --- .../Objects/Specific/Species/lamia.yml | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 Resources/Prototypes/Entities/Objects/Specific/Species/lamia.yml diff --git a/Resources/Prototypes/Entities/Objects/Specific/Species/lamia.yml b/Resources/Prototypes/Entities/Objects/Specific/Species/lamia.yml new file mode 100644 index 00000000000..f5fad8c21cb --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Specific/Species/lamia.yml @@ -0,0 +1,49 @@ +# Delta-V - This file is licensed under AGPLv3 +# Copyright (c) 2024 Delta-V Contributors +# See AGPLv3.txt for details. + +- type: entity + id: LamiaInitialSegment + save: false + noSpawn: true + components: + - type: Damageable + - type: StandingState + - type: Clickable + - type: InteractionOutline + - type: PsionicInsulation #Not a brain, target the lamia instead + - type: Physics + bodyType: KinematicController + - type: Fixtures + fixtures: # TODO: This needs a second fixture just for mob collisions. + fix1: + shape: + !type:PhysShapeCircle + radius: 0.25 + density: 80 + restitution: 0.0 + mask: + - MobMask + layer: + - MobLayer + - type: Transform + anchored: false + - type: Tag + tags: + - HideContextMenu + - type: RequireProjectileTarget + active: True + +- type: entity + id: LamiaSegment + save: false + parent: LamiaInitialSegment + name: lamia segment + noSpawn: true + description: A tail segment, hopefully attached to a lamia. + components: + - type: Sprite + - type: Tag + tags: + - HideContextMenu + - DoorBumpOpener From fd84d36479683ee7c54271c7eba351b73f66de3f Mon Sep 17 00:00:00 2001 From: fenndragon Date: Thu, 12 Dec 2024 15:17:54 -0700 Subject: [PATCH 12/25] Create folder --- Content.Client/DeltaV/Lamiae/folder | 1 + 1 file changed, 1 insertion(+) create mode 100644 Content.Client/DeltaV/Lamiae/folder diff --git a/Content.Client/DeltaV/Lamiae/folder b/Content.Client/DeltaV/Lamiae/folder new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/Content.Client/DeltaV/Lamiae/folder @@ -0,0 +1 @@ + From 97d3602d29ba3d87879e38be73eaedc336065d5a Mon Sep 17 00:00:00 2001 From: fenndragon Date: Thu, 12 Dec 2024 15:18:13 -0700 Subject: [PATCH 13/25] Add files via upload --- Content.Client/DeltaV/Lamiae/SnakeOverlay.cs | 184 ++++++++++++++++++ .../DeltaV/Lamiae/SnakeOverlaySystem.cs | 32 +++ 2 files changed, 216 insertions(+) create mode 100644 Content.Client/DeltaV/Lamiae/SnakeOverlay.cs create mode 100644 Content.Client/DeltaV/Lamiae/SnakeOverlaySystem.cs diff --git a/Content.Client/DeltaV/Lamiae/SnakeOverlay.cs b/Content.Client/DeltaV/Lamiae/SnakeOverlay.cs new file mode 100644 index 00000000000..a8ed98c3369 --- /dev/null +++ b/Content.Client/DeltaV/Lamiae/SnakeOverlay.cs @@ -0,0 +1,184 @@ +/* +* This file is licensed under AGPLv3 +* Copyright (c) 2024 Rane +* See AGPLv3.txt for details. +*/ + +using Content.Shared.SegmentedEntity; +using Content.Shared.Humanoid; +using Content.Shared.Humanoid.Markings; +using Content.Client.Resources; +using Robust.Client.ResourceManagement; +using Robust.Client.Graphics; +using Robust.Shared.Enums; +using System.Numerics; +using System.Linq; + + +namespace Content.Client.DeltaV.Lamiae; + +/// +/// This draws lamia segments directly from polygons instead of sprites. This is a very novel approach as of the time this is being written (August 2024) but it wouldn't surprise me +/// if there's a better way to do this at some point. Currently we have a very heavy restriction on the tools we can make, forcing me to make several helpers that may be redundant later. +/// This will be overcommented because I know you haven't seen code like this before and you might want to copy it. +/// This is an expansion on some techniques I discovered in (https://github.com/Elijahrane/Delta-v/blob/49d76c437740eab79fc622ab50d628b926e6ddcb/Content.Client/DeltaV/Arcade/S3D/Renderer/S3DRenderer.cs) +/// +public sealed class SnakeOverlay : Overlay +{ + private readonly IResourceCache _resourceCache; + private readonly IEntityManager _entManager; + private readonly SharedTransformSystem _transform; + private readonly SharedHumanoidAppearanceSystem _humanoid = default!; + + // Look through these carefully. WorldSpace is useful for debugging. Note that this defaults to "screen space" which breaks when you try and get the world handle. + public override OverlaySpace Space => OverlaySpace.WorldSpaceEntities; + + // Overlays are strange and you need this pattern where you define readonly deps above, and then make a constructor with this pattern. Anything that creates this overlay will then + // have to provide all the deps. + public SnakeOverlay(IEntityManager entManager, IResourceCache resourceCache) + { + _resourceCache = resourceCache; + // we get ent manager from SnakeOverlaySystem turning this on and passing it + _entManager = entManager; + // with ent manager we can fetch our other entity systems + _transform = _entManager.EntitySysManager.GetEntitySystem(); + _humanoid = _entManager.EntitySysManager.GetEntitySystem(); + + // draw at drawdepth 3 + ZIndex = 3; + } + + // This step occurs each frame. For some overlays you may want to conisder limiting how often they update, but for player entities that move around fast we'll just do it every frame. + protected override void Draw(in OverlayDrawArgs args) + { + // load the handle, the "pen" we draw with + var handle = args.WorldHandle; + + // Get all lamiae the client knows of and their transform in a way we can enumerate over + var enumerator = _entManager.AllEntityQueryEnumerator(); + + // I go over the collection above, pulling out an EntityUid and the two components I need for each. + while (enumerator.MoveNext(out var uid, out var lamia, out var xform)) + { + // Skip ones that are off-map. "Map" in this context means interconnected stuff you can travel between by moving, rather than needing e.g. FTL to load a new map. + if (xform.MapID != args.MapId) + continue; + + // Skip ones where they are not loaded properly, uninitialized, or w/e + if (lamia.Segments.Count < lamia.NumberOfSegments) + { + _entManager.Dirty(uid, lamia); // pls give me an update... + continue; + } + + // By the way, there's a hack to mitigate overdraw somewhat. Check out whatever is going on with the variable called "bounds" in DoAfterOverlay. + // I won't do it here because (1) it's ugly and (2) theoretically these entities can be fucking huge and you'll see the tail end of them when they are way off screen. + // On a PVS level I think segmented entities should be all-or-nothing when it comes to PVS range, that is you either load all of their segments or none. + + // Color.White is drawing without modifying color. For clothed tails, we should use White. For skin, we should use the color of the marking. + // TODO: Better way to cache this + if (_entManager.TryGetComponent(uid, out var humanoid)) + { + if (humanoid.MarkingSet.TryGetCategory(MarkingCategories.Tail, out var tailMarkings)) + { + var col = tailMarkings.First().MarkingColors.First(); + DrawLamia(handle, lamia, col); + } + } + else + { + DrawLamia(handle, lamia, Color.White); + } + } + } + + // This is where we do the actual drawing. + private void DrawLamia(DrawingHandleWorld handle, SegmentedEntityComponent lamia, Color color) + { + // We're going to store all our verticies in here and then draw them + List verts = new List(); + + // Radius of the initial segment + float radius = lamia.InitialRadius; + + // We're storing the left and right verticies of the last segment so we can start drawing from there without gaps + Vector2? lastPtCW = null; + Vector2? lastPtCCW = null; + + var tex = _resourceCache.GetTexture(lamia.TexturePath); + + int i = 1; + // do each segment except the last one normally + while (i < lamia.Segments.Count - 1) + { + // get centerpoints of last segment and this one + var origin = _transform.GetWorldPosition(_entManager.GetEntity(lamia.Segments[i - 1])); + var destination = _transform.GetWorldPosition(_entManager.GetEntity(lamia.Segments[i])); + + // get direction between the two points and normalize it + var connectorVec = destination - origin; + connectorVec = connectorVec.Normalized(); + + //get one rotated 90 degrees clockwise + var offsetVecCW = new Vector2(connectorVec.Y, 0 - connectorVec.X); + + //and counterclockwise + var offsetVecCCW = new Vector2(0 - connectorVec.Y, connectorVec.X); + + /// tri 1: line across first segment and corner of second + if (lastPtCW == null) + { + verts.Add(new DrawVertexUV2D(origin + offsetVecCW * radius, Vector2.Zero)); + } + else + { + verts.Add(new DrawVertexUV2D((Vector2) lastPtCW, Vector2.Zero)); + } + + if (lastPtCCW == null) + { + verts.Add(new DrawVertexUV2D(origin + offsetVecCCW * radius, new Vector2(1, 0))); + } + else + { + verts.Add(new DrawVertexUV2D((Vector2) lastPtCCW, new Vector2(1, 0))); + } + + verts.Add(new DrawVertexUV2D(destination + offsetVecCW * radius, new Vector2(0, 1))); + + // tri 2: line across second segment and corner of first + if (lastPtCCW == null) + { + verts.Add(new DrawVertexUV2D(origin + offsetVecCCW * radius, new Vector2(1, 0))); + } + else + { + verts.Add(new DrawVertexUV2D((Vector2) lastPtCCW, new Vector2(1, 0))); + } + + lastPtCW = destination + offsetVecCW * radius; + verts.Add(new DrawVertexUV2D((Vector2) lastPtCW, new Vector2(0, 1))); + lastPtCCW = destination + offsetVecCCW * radius; + verts.Add(new DrawVertexUV2D((Vector2) lastPtCCW, new Vector2(1, 1))); + + // slim down a bit for next segment + radius *= lamia.SlimFactor; + + i++; + } + + // draw tail (1 tri) + if (lastPtCW != null && lastPtCCW != null) + { + verts.Add(new DrawVertexUV2D((Vector2) lastPtCW, new Vector2(0, 0))); + verts.Add(new DrawVertexUV2D((Vector2) lastPtCCW, new Vector2(1, 0))); + + var destination = _transform.GetWorldPosition(_entManager.GetEntity(lamia.Segments.Last())); + + verts.Add(new DrawVertexUV2D(destination, new Vector2(0.5f, 1f))); + } + + // Draw all of the triangles we just pit in at once + handle.DrawPrimitives(DrawPrimitiveTopology.TriangleList, texture: tex, verts.ToArray().AsSpan(), color); + } +} \ No newline at end of file diff --git a/Content.Client/DeltaV/Lamiae/SnakeOverlaySystem.cs b/Content.Client/DeltaV/Lamiae/SnakeOverlaySystem.cs new file mode 100644 index 00000000000..6be1a18dc14 --- /dev/null +++ b/Content.Client/DeltaV/Lamiae/SnakeOverlaySystem.cs @@ -0,0 +1,32 @@ +/* +* This file is licensed under AGPLv3 +* Copyright (c) 2024 Rane +* See AGPLv3.txt for details. +*/ + +using Robust.Client.Graphics; +using Robust.Client.ResourceManagement; + +namespace Content.Client.DeltaV.Lamiae; + +/// +/// This system turns on our always-on overlay. I have no opinion on this design pattern or the existence of this file. +/// It also fetches the deps it needs. +/// +public sealed class SnakeOverlaySystem : EntitySystem +{ + [Dependency] private readonly IOverlayManager _overlay = default!; + [Dependency] private readonly IResourceCache _resourceCache = default!; + + public override void Initialize() + { + base.Initialize(); + _overlay.AddOverlay(new SnakeOverlay(EntityManager, _resourceCache)); + } + + public override void Shutdown() + { + base.Shutdown(); + _overlay.RemoveOverlay(); + } +} \ No newline at end of file From 018e1a9abacc86f8df1ffada3da5771e74a6fe8f Mon Sep 17 00:00:00 2001 From: fenndragon Date: Fri, 13 Dec 2024 18:15:53 -0700 Subject: [PATCH 14/25] Update xenopet.yml --- Resources/Prototypes/Entities/Mobs/NPCs/xenopet.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/xenopet.yml b/Resources/Prototypes/Entities/Mobs/NPCs/xenopet.yml index 420059bb75d..7ff8c796304 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/xenopet.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/xenopet.yml @@ -404,7 +404,6 @@ layers: - map: ["enum.DamageStateVisualLayers.Base"] state: running - - type: SegmentedEntity numberOfSegments: 2 texturePath: /Textures/Floof/Mobs/Animals/rainlizard.rsi/segment.png From f5bd542b30880e159644c92eb975ffe0e323256f Mon Sep 17 00:00:00 2001 From: fenndragon Date: Sun, 15 Dec 2024 18:37:43 -0700 Subject: [PATCH 15/25] Delete Content.Client/DeltaV/Lamiae/folder --- Content.Client/DeltaV/Lamiae/folder | 1 - 1 file changed, 1 deletion(-) delete mode 100644 Content.Client/DeltaV/Lamiae/folder diff --git a/Content.Client/DeltaV/Lamiae/folder b/Content.Client/DeltaV/Lamiae/folder deleted file mode 100644 index 8b137891791..00000000000 --- a/Content.Client/DeltaV/Lamiae/folder +++ /dev/null @@ -1 +0,0 @@ - From 712cf23ef634082870302aeeefa612157fdb97cb Mon Sep 17 00:00:00 2001 From: fenndragon Date: Sun, 15 Dec 2024 18:44:34 -0700 Subject: [PATCH 16/25] Delete Content.Shared/SegmentedEntity/folder --- Content.Shared/SegmentedEntity/folder | 1 - 1 file changed, 1 deletion(-) delete mode 100644 Content.Shared/SegmentedEntity/folder diff --git a/Content.Shared/SegmentedEntity/folder b/Content.Shared/SegmentedEntity/folder deleted file mode 100644 index 8b137891791..00000000000 --- a/Content.Shared/SegmentedEntity/folder +++ /dev/null @@ -1 +0,0 @@ - From 7d68994ea82aea3f8b6d82883cbb45fb10b74e65 Mon Sep 17 00:00:00 2001 From: fenndragon Date: Sun, 15 Dec 2024 22:21:40 -0700 Subject: [PATCH 17/25] fixes and tweaks --- .../Prototypes/Entities/Mobs/NPCs/xeno.yml | 5 +++-- .../Prototypes/Entities/Mobs/NPCs/xenopet.yml | 9 +++++++-- .../Mobs/Animals/rainlizard.rsi/marking.png | Bin 0 -> 616 bytes .../Floof/Mobs/Animals/rainlizard.rsi/meta.json | 3 +++ .../Mobs/Animals/rainlizard.rsi/segment.png | Bin 726 -> 762 bytes .../Floof/Mobs/Animals/rainlizard.rsi/tip.png | Bin 640 -> 708 bytes 6 files changed, 13 insertions(+), 4 deletions(-) create mode 100644 Resources/Textures/Floof/Mobs/Animals/rainlizard.rsi/marking.png diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml index f72616d45c6..703fe6189c0 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml @@ -79,8 +79,8 @@ bloodReagent: FluorosulfuricAcid bloodMaxVolume: 650 - type: MeleeWeapon - altDisarm: false - angle: 0 + altDisarm: true + angle: 30 soundHit: collection: AlienClaw animation: WeaponArcBite @@ -134,6 +134,7 @@ - type: InnatePsionicPowers powersToAdd: - TelepathyPower + - type: MouseRotator - type: LanguageKnowledge speaks: - Xeno diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/xenopet.yml b/Resources/Prototypes/Entities/Mobs/NPCs/xenopet.yml index 7ff8c796304..6c288ffd99a 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/xenopet.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/xenopet.yml @@ -399,12 +399,17 @@ - type: Sprite drawdepth: Mobs sprite: /Textures/Floof/Mobs/Animals/rainlizard.rsi - scale: 1,1 + scale: 0.6,0.6 offset: 0,0 layers: - map: ["enum.DamageStateVisualLayers.Base"] state: running + color: "#30ce00" - type: SegmentedEntity - numberOfSegments: 2 + numberOfSegments: 3 + staticOffset: 0.3 + taperOffset: 5 texturePath: /Textures/Floof/Mobs/Animals/rainlizard.rsi/segment.png + - type: RotationVisuals + horizontalRotation: 45 - type: PortalExempt diff --git a/Resources/Textures/Floof/Mobs/Animals/rainlizard.rsi/marking.png b/Resources/Textures/Floof/Mobs/Animals/rainlizard.rsi/marking.png new file mode 100644 index 0000000000000000000000000000000000000000..3c4cf57f4c0beaf8646fb3322d20897f04f11867 GIT binary patch literal 616 zcmV-u0+;=XP)2R|084ld5RI=Bjg;0K7agOj3*l=#1-&?3fz<9@um_qclp_?uOx zn!#B>)hr{EN(#CBst|fb7(qlaiZO|q`kW}H;W@tU;p6LFf@gW3`*ZXu1(N|jfq0hb zhDE$VJiTe@ocD=itSl+S=fvX%U6A;Z>$1yloJ$V-d1lPWX6A`w#6qcyER!A z{35wza#g^{v49#>NRA);4}N!R7AGg%q(~Czd9m$}VIZ^%v>LYkeQevU6Cm&mTxlJD ztqIJ0lHTZO@gtyr8@RacX!0I#xdRM5>5?HiQh=tvSOnhB=$rCD^cLt{b9-y=TA@Z*OeDr{R16003M`L_t(oh3(bL z3IHGoMA7m8KiXDpT8%xZ;JOUa11Uyk>ShE000000007`uKuQYZ?HFKeIMsX=uq6Og z0F{5XbE^Ouh26X|pci)l_C|a)0{{R#2e^KWwg4K2sBOVx5S(iO0000M&B1^a1hp7q@{ zGm&xt2dND}#@x^CfH5ni96(R4wWtY@^6&ssFVzGH*S!*)2|#}_uL*f=S_1H1bH^}D zXbnJ(d3_JW04M?Ym4$1Z7ku8Y?AqRUvH&@8!<6}+4g(z31^|d}cPn!sath9X2!Hq9 zG$aTv0{W-UJeLf)_;4y7$K$*JKo%m*t9lj8?tdDGu;>lEm9|XqbfV9j ziA@)x`C7yv1SCAO+JhG3NV}ej!=LC{sWM=8W+3* zaL;F)b7ul>?@9vdnnGl+KM>;qB8&XY1Em1190i~^Pzq4@6;ojFCLs40i;xpa0JyIQ za7{sOT;S8>t#{N5vkO?!_6Dp*aD|C2#VZi4s<o(f6T&{u(^ QI{*Lx07*qoM6N<$f|0gY)&Kwi delta 146 zcmV;D0B!%o1%L&x_W^%&NklQ0tLK2#^BYN05kCnLV!I0k$4m#fB@3~fXEfV+`n)Fotq2LQ25m;000UA07*qoM6N<$f@l#s A?EnA( From b8597899872da33d509b99f1c74e5edcbcf6ee34 Mon Sep 17 00:00:00 2001 From: fenndragon Date: Sun, 15 Dec 2024 22:46:39 -0700 Subject: [PATCH 18/25] setup --- Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml | 1 + Resources/Prototypes/Entities/Mobs/NPCs/xenopet.yml | 12 ++++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml index 703fe6189c0..2dc5f62a106 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml @@ -87,6 +87,7 @@ damage: groups: Brute: 6 + - type: Rotatable - type: DamageStateVisuals rotate: true states: diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/xenopet.yml b/Resources/Prototypes/Entities/Mobs/NPCs/xenopet.yml index 6c288ffd99a..2f07bdb27da 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/xenopet.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/xenopet.yml @@ -405,11 +405,15 @@ - map: ["enum.DamageStateVisualLayers.Base"] state: running color: "#30ce00" + - type: PointLight + radius: 2 + energy: 1 + color: "#30ce00" - type: SegmentedEntity numberOfSegments: 3 - staticOffset: 0.3 - taperOffset: 5 + initialRadius: 0.25 + staticScale: 0.9 + staticOffset: 0.2 + taperOffset: 30 texturePath: /Textures/Floof/Mobs/Animals/rainlizard.rsi/segment.png - - type: RotationVisuals - horizontalRotation: 45 - type: PortalExempt From d4a9e07f2ffa99ecaadca5f18d91833102ffc6c2 Mon Sep 17 00:00:00 2001 From: fenndragon Date: Sun, 15 Dec 2024 23:40:21 -0700 Subject: [PATCH 19/25] morelizards --- .../Entities/Mobs/NPCs/Rainlizard.yml | 299 ++++++++++++++++++ .../Prototypes/Entities/Mobs/NPCs/xenopet.yml | 35 +- .../Mobs/Animals/rainlizard.rsi/meta.json | 9 + .../Animals/rainlizard.rsi/segmentblue.png | Bin 0 -> 763 bytes .../Animals/rainlizard.rsi/segmentpink.png | Bin 0 -> 768 bytes .../Animals/rainlizard.rsi/segmentred.png | Bin 0 -> 902 bytes .../Floof/Mobs/Animals/rainlizard.rsi/tip.png | Bin 708 -> 632 bytes 7 files changed, 309 insertions(+), 34 deletions(-) create mode 100644 Resources/Prototypes/Entities/Mobs/NPCs/Rainlizard.yml create mode 100644 Resources/Textures/Floof/Mobs/Animals/rainlizard.rsi/segmentblue.png create mode 100644 Resources/Textures/Floof/Mobs/Animals/rainlizard.rsi/segmentpink.png create mode 100644 Resources/Textures/Floof/Mobs/Animals/rainlizard.rsi/segmentred.png diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/Rainlizard.yml b/Resources/Prototypes/Entities/Mobs/NPCs/Rainlizard.yml new file mode 100644 index 00000000000..0aba7bf036c --- /dev/null +++ b/Resources/Prototypes/Entities/Mobs/NPCs/Rainlizard.yml @@ -0,0 +1,299 @@ +- type: entity + name: Rain Lizard + parent: MobXenoNeutralRouny + id: Mobrainlizard + description: A darkscaled lizard with a colorful head + abstract: true + components: + - type: NpcFactionMember + factions: + - PetsNT + - type: Tool + speed: 6 + qualities: + - Prying + - type: Sprite + drawdepth: Mobs + sprite: /Textures/Floof/Mobs/Animals/rainlizard.rsi + scale: 0.6,0.6 + offset: 0,0 + layers: + - map: ["enum.DamageStateVisualLayers.Base"] + state: running + color: "#30ce00" + - type: PointLight + radius: 2 + energy: 1 + color: "#30ce00" + - type: SegmentedEntity + numberOfSegments: 3 + initialRadius: 0.25 + staticScale: 0.9 + staticOffset: 0.2 + taperOffset: 30 + texturePath: /Textures/Floof/Mobs/Animals/rainlizard.rsi/segment.png + - type: PortalExempt + +- type: entity + name: Green Rain Lizard + parent: Mobrainlizard + id: Mobgreenlizard + description: A darkscaled lizard with a colorful head + components: + - type: Sprite + drawdepth: Mobs + sprite: /Textures/Floof/Mobs/Animals/rainlizard.rsi + scale: 0.6,0.6 + offset: 0,0 + layers: + - map: ["enum.DamageStateVisualLayers.Base"] + state: running + color: "#30ce00" + - type: PointLight + radius: 2 + energy: 1 + color: "#30ce00" + - type: SegmentedEntity + numberOfSegments: 3 + initialRadius: 0.25 + staticScale: 0.9 + staticOffset: 0.2 + taperOffset: 30 + texturePath: /Textures/Floof/Mobs/Animals/rainlizard.rsi/segment.png + +- type: entity + name: Blue Rain Lizard + parent: Mobrainlizard + id: Mobbluelizard + description: A darkscaled lizard with a colorful head + components: + - type: Sprite + drawdepth: Mobs + sprite: /Textures/Floof/Mobs/Animals/rainlizard.rsi + scale: 0.4,0.4 + offset: 0,0 + layers: + - map: ["enum.DamageStateVisualLayers.Base"] + state: running + color: "#2986cc" + - type: PointLight + radius: 2 + energy: 1 + color: "#2986cc" + - type: SegmentedEntity + numberOfSegments: 3 + initialRadius: 0.2 + staticScale: 0.6 + staticOffset: 0.15 + taperOffset: 15 + texturePath: /Textures/Floof/Mobs/Animals/rainlizard.rsi/segmentblue.png + +- type: entity + name: Pink Rain Lizard + parent: Mobrainlizard + id: Mobpinklizard + description: A darkscaled lizard with a colorful head + components: + - type: Sprite + drawdepth: Mobs + sprite: /Textures/Floof/Mobs/Animals/rainlizard.rsi + scale: 0.5,0.5 + offset: 0,0 + layers: + - map: ["enum.DamageStateVisualLayers.Base"] + state: running + color: "#cc29bf" + - type: PointLight + radius: 2 + energy: 1 + color: "#cc29bf" + - type: SegmentedEntity + numberOfSegments: 3 + initialRadius: 0.23 + staticScale: 0.7 + staticOffset: 0.19 + taperOffset: 25 + texturePath: /Textures/Floof/Mobs/Animals/rainlizard.rsi/segmentpink.png + +- type: entity + name: Red Rain Lizard + parent: Mobrainlizard + id: Mobredlizard + description: A darkscaled lizard with a colorful head + components: + - type: Sprite + drawdepth: Mobs + sprite: /Textures/Floof/Mobs/Animals/rainlizard.rsi + scale: 0.8,0.8 + offset: 0,0 + layers: + - map: ["enum.DamageStateVisualLayers.Base"] + state: running + color: "#ff0000" + - type: PointLight + radius: 2 + energy: 1 + color: "#ff0000" + - type: MeleeWeapon + altDisarm: false + soundHit: + path: /Audio/Weapons/Xeno/alien_claw_flesh3.ogg + damage: + types: + Piercing: 15 + Slash: 15 + - type: Devourer + foodPreference: Humanoid + shouldStoreDevoured: true + chemical: Ichor + healRate: 15.0 + - type: MobThresholds + thresholds: + 0: Alive + 450: Critical + 500: Dead + - type: SegmentedEntity + numberOfSegments: 3 + initialRadius: 0.25 + staticScale: 0.9 + staticOffset: 0.2 + taperOffset: 40 + texturePath: /Textures/Floof/Mobs/Animals/rainlizard.rsi/segmentred.png + +- type: entity + name: Rabid Green Rain Lizard + parent: Mobrainlizard + id: MobRgreenlizard + description: A darkscaled lizard with a colorful head + components: + - type: NpcFactionMember + factions: + - Dragon + - type: Sprite + drawdepth: Mobs + sprite: /Textures/Floof/Mobs/Animals/rainlizard.rsi + scale: 0.6,0.6 + offset: 0,0 + layers: + - map: ["enum.DamageStateVisualLayers.Base"] + state: lizardheadopen + color: "#30ce00" + - type: PointLight + radius: 2 + energy: 1 + color: "#30ce00" + - type: SegmentedEntity + numberOfSegments: 3 + initialRadius: 0.25 + staticScale: 0.9 + staticOffset: 0.2 + taperOffset: 30 + texturePath: /Textures/Floof/Mobs/Animals/rainlizard.rsi/segment.png + +- type: entity + name: Rabid blue Rain Lizard + parent: Mobrainlizard + id: MobRbluelizard + description: A darkscaled lizard with a colorful head + components: + - type: NpcFactionMember + factions: + - Dragon + - type: Sprite + drawdepth: Mobs + sprite: /Textures/Floof/Mobs/Animals/rainlizard.rsi + scale: 0.4,0.4 + offset: 0,0 + layers: + - map: ["enum.DamageStateVisualLayers.Base"] + state: lizardheadopen + color: "#2986cc" + - type: PointLight + radius: 2 + energy: 1 + color: "#2986cc" + - type: SegmentedEntity + numberOfSegments: 3 + initialRadius: 0.25 + staticScale: 0.9 + staticOffset: 0.2 + taperOffset: 30 + texturePath: /Textures/Floof/Mobs/Animals/rainlizard.rsi/segmentblue.png + +- type: entity + name: Rabid pink Rain Lizard + parent: Mobrainlizard + id: MobRpinklizard + description: A darkscaled lizard with a colorful head + components: + - type: NpcFactionMember + factions: + - Dragon + - type: Sprite + drawdepth: Mobs + sprite: /Textures/Floof/Mobs/Animals/rainlizard.rsi + scale: 0.5,0.5 + offset: 0,0 + layers: + - map: ["enum.DamageStateVisualLayers.Base"] + state: lizardheadopen + color: "#cc29bf" + - type: PointLight + radius: 2 + energy: 1 + color: "#cc29bf" + - type: SegmentedEntity + numberOfSegments: 3 + initialRadius: 0.23 + staticScale: 0.7 + staticOffset: 0.19 + taperOffset: 25 + texturePath: /Textures/Floof/Mobs/Animals/rainlizard.rsi/segmentpink.png + +- type: entity + name: Rabid Red Rain Lizard + parent: Mobrainlizard + id: MobRredlizard + description: A darkscaled lizard with a colorful head + components: + - type: NpcFactionMember + factions: + - Dragon + - type: Sprite + drawdepth: Mobs + sprite: /Textures/Floof/Mobs/Animals/rainlizard.rsi + scale: 0.8,0.8 + offset: 0,0 + layers: + - map: ["enum.DamageStateVisualLayers.Base"] + state: lizardheadopen + color: "#ff0000" + - type: PointLight + radius: 2 + energy: 1 + color: "#ff0000" + - type: MeleeWeapon + altDisarm: false + soundHit: + path: /Audio/Weapons/Xeno/alien_claw_flesh3.ogg + damage: + types: + Piercing: 15 + Slash: 15 + - type: Devourer + foodPreference: Humanoid + shouldStoreDevoured: true + chemical: Ichor + healRate: 15.0 + - type: MobThresholds + thresholds: + 0: Alive + 450: Critical + 500: Dead + - type: SegmentedEntity + numberOfSegments: 3 + initialRadius: 0.25 + staticScale: 0.9 + staticOffset: 0.2 + taperOffset: 40 + texturePath: /Textures/Floof/Mobs/Animals/rainlizard.rsi/segmentred.png diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/xenopet.yml b/Resources/Prototypes/Entities/Mobs/NPCs/xenopet.yml index 2f07bdb27da..611157edc7c 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/xenopet.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/xenopet.yml @@ -383,37 +383,4 @@ - MobXenoNeutralRavager chance: 1 -- type: entity - name: Rain Lizard - parent: MobXenoNeutralRouny - id: Mobrainlizard - description: A darkscaled lizard with a colorful head - components: - - type: NpcFactionMember - factions: - - PetsNT - - type: Tool - speed: 6 - qualities: - - Prying - - type: Sprite - drawdepth: Mobs - sprite: /Textures/Floof/Mobs/Animals/rainlizard.rsi - scale: 0.6,0.6 - offset: 0,0 - layers: - - map: ["enum.DamageStateVisualLayers.Base"] - state: running - color: "#30ce00" - - type: PointLight - radius: 2 - energy: 1 - color: "#30ce00" - - type: SegmentedEntity - numberOfSegments: 3 - initialRadius: 0.25 - staticScale: 0.9 - staticOffset: 0.2 - taperOffset: 30 - texturePath: /Textures/Floof/Mobs/Animals/rainlizard.rsi/segment.png - - type: PortalExempt + diff --git a/Resources/Textures/Floof/Mobs/Animals/rainlizard.rsi/meta.json b/Resources/Textures/Floof/Mobs/Animals/rainlizard.rsi/meta.json index 56b934f30a0..8e41acc8cce 100644 --- a/Resources/Textures/Floof/Mobs/Animals/rainlizard.rsi/meta.json +++ b/Resources/Textures/Floof/Mobs/Animals/rainlizard.rsi/meta.json @@ -12,6 +12,15 @@ }, { "name": "tip" + }, + { + "name": "segmentblue" + }, + { + "name": "segmentpink" + }, + { + "name": "segmentred" }, { "name": "marking" diff --git a/Resources/Textures/Floof/Mobs/Animals/rainlizard.rsi/segmentblue.png b/Resources/Textures/Floof/Mobs/Animals/rainlizard.rsi/segmentblue.png new file mode 100644 index 0000000000000000000000000000000000000000..fd0049a4d18ec52ed246d910476d8c041d198b27 GIT binary patch literal 763 zcmV2R|084ld5RI=Bjg;0K7agOj3*l=#1-&?3fz<9@um_qclp_?uOx zn!#B>)hr{EN(#CBst|fb7(qlaiZO|q`kW}H;W@tU;p6LFf@gW3`*ZXu1(N|jfq0hb zhDE$VJiTe@ocD=itSl+S=fvX%U6A;Z>$1yloJ$V-d1lPWX6A`w#6qcyER!A z{35wza#g^{v49#>NRA);4}N!R7AGg%q(~Czd9m$}VIZ^%v>LYkeQevU6Cm&mTxlJD ztqIJ0lHTZO@gtyr8@RacX!0I#xdRM5>5?HiQh=tvSOnhB=$rCD^cLt{b9-y=TA@Z*OeDr{R16008evL_t(oh3%Hj z4TK;Ng&!SoAhrO#Y&q7k<=6`~Y&h5fdm48@5jKK93GZUUjJ_|-;2^yBNXZ!Eotu;K z5-BBNtre-b=l~s{1Ka|@7~=t~5?cXqKcTf=Oq7z~AhiL=nENd|;M@$QB^nK*|6B002ovPDHLkV1io5O*a4l literal 0 HcmV?d00001 diff --git a/Resources/Textures/Floof/Mobs/Animals/rainlizard.rsi/segmentpink.png b/Resources/Textures/Floof/Mobs/Animals/rainlizard.rsi/segmentpink.png new file mode 100644 index 0000000000000000000000000000000000000000..9024b26ec1c45640680a66aba7e94d346ee8c053 GIT binary patch literal 768 zcmV+b1ONPqP)2R|084ld5RI=Bjg;0K7agOj3*l=#1-&?3fz<9@um_qclp_?uOx zn!#B>)hr{EN(#CBst|fb7(qlaiZO|q`kW}H;W@tU;p6LFf@gW3`*ZXu1(N|jfq0hb zhDE$VJiTe@ocD=itSl+S=fvX%U6A;Z>$1yloJ$V-d1lPWX6A`w#6qcyER!A z{35wza#g^{v49#>NRA);4}N!R7AGg%q(~Czd9m$}VIZ^%v>LYkeQevU6Cm&mTxlJD ztqIJ0lHTZO@gtyr8@RacX!0I#xdRM5>5?HiQh=tvSOnhB=$rCD^cLt{b9-y=TA@Z*OeDr{R16008t!L_t(oh3%H# z5rZHM#y>Xjf$IveNmuBIU7?#WLyreHz^B-NM6rUcp5Kcn3FLQ4_@hDykYue5-uI%t zL|S)n&MC>R4hitOJ#l|O#Q)|1Yjgor1DLBbSL#SAe z0jM#5-vc=SasWPM*#eMTXAyu@dIYbXTMY0m#L}Dz0I)sXr4o0+9uVnqAKiuo!BwjX yx%lODoCnEY05p4$90p(xLK;6;4_pAJ1Q-A<3);ST@z2R|084ld5RI=Bjg;0K7agOj3*l=#1-&?3fz<9@um_qclp_?uOx zn!#B>)hr{EN(#CBst|fb7(qlaiZO|q`kW}H;W@tU;p6LFf@gW3`*ZXu1(N|jfq0hb zhDE$VJiTe@ocD=itSl+S=fvX%U6A;Z>$1yloJ$V-d1lPWX6A`w#6qcyER!A z{35wza#g^{v49#>NRA);4}N!R7AGg%q(~Czd9m$}VIZ^%v>LYkeQevU6Cm&mTxlJD ztqIJ0lHTZO@gtyr8@RacX!0I#xdRM5>5?HiQh=tvSOnhB=$rCD^cLt{b9-y=TA@Z*OeDr{R1600DYQL_t(oh257? zio_rchX354u)-ce?|&M5go1^+LH1#>M4MRcxJ=o+%z&EzGbWl6GsChF0;lvc$9v4V z1u;gl#_90@ab4lQzHf=AeM9j6p8=$OGlA-{X#fBY$Gre)-+q0(3y{*QDQa0>b3X}* zICzgK0P6}cs}RJ@2q7?ll9Y%6+)h9V0-ykpdgQ%7YTda71qc!Mo_A|r7SF;`?}$K3 zFF3a#|Hc@}x`^_w>V(rEYG1WR0bniC%BvQ$=2)9K0OS!AgX;<_35NlMuUe-_LU}ed zKppZHfSj)Z$Xq+hP=-ibG*XIkt_6@2y_o~D3hn~rDll_E1CZo10Ic(G0%*=gt+n=f z002&5dI#c498jvjg6Ju`t9wBc2ddXFLlllCT*bA?NAFfsI=bWOUGc{OMr;beXIe6p z5wOAzoRjbL77POraeaSl&#g4;xt8yW))#g)rJCcO{|CTTQ)*h(6ZYhS8Yx5k65tV^ cW`A9VKZOZ!%}o+XTmS$707*qoM6N<$g0W+kMgRZ+ literal 0 HcmV?d00001 diff --git a/Resources/Textures/Floof/Mobs/Animals/rainlizard.rsi/tip.png b/Resources/Textures/Floof/Mobs/Animals/rainlizard.rsi/tip.png index 09eb235fa49cacfaa37f397f96dc69e1ef9a7722..8a93677c0681ba75d1456df0fe29908b271319ec 100644 GIT binary patch delta 138 zcmV;50CoSw1^5K8_W^%wNkl{JkVI@nwVn0tlD^m{|gVyUYIoW|k2r06=vsBM2a%1h|*CR1pNM2Y4!%t{dh1(8zd!&11Q2im_yz^gPu0Q400000NkvXXu0jG}f@6>}RR910 delta 214 zcmV;{04e|Y1jGff_W^&nNklo(f6T&{u(^ QI{*Lx07*qoM6N<$g7q<2O#lD@ From 09361f772410cb9988b8e6a97776435d51c6833a Mon Sep 17 00:00:00 2001 From: fenndragon Date: Mon, 16 Dec 2024 12:09:16 -0700 Subject: [PATCH 20/25] fix --- .../Floof/Mobs/Animals/rainlizard.rsi/dead.png | Bin 0 -> 3352 bytes .../Floof/Mobs/Animals/rainlizard.rsi/meta.json | 4 ++++ 2 files changed, 4 insertions(+) create mode 100644 Resources/Textures/Floof/Mobs/Animals/rainlizard.rsi/dead.png diff --git a/Resources/Textures/Floof/Mobs/Animals/rainlizard.rsi/dead.png b/Resources/Textures/Floof/Mobs/Animals/rainlizard.rsi/dead.png new file mode 100644 index 0000000000000000000000000000000000000000..da1aef7de3067d2f3a6194b0a3bf3b02af7f8bba GIT binary patch literal 3352 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 i0&cYpw-i5m0{#!Wy=>dB2=-L~0000 Date: Mon, 16 Dec 2024 12:26:17 -0700 Subject: [PATCH 21/25] fix --- .../Floof/Mobs/Animals/rainlizard.rsi/crit.png | Bin 0 -> 3348 bytes .../Floof/Mobs/Animals/rainlizard.rsi/meta.json | 4 ++++ 2 files changed, 4 insertions(+) create mode 100644 Resources/Textures/Floof/Mobs/Animals/rainlizard.rsi/crit.png diff --git a/Resources/Textures/Floof/Mobs/Animals/rainlizard.rsi/crit.png b/Resources/Textures/Floof/Mobs/Animals/rainlizard.rsi/crit.png new file mode 100644 index 0000000000000000000000000000000000000000..13e69a4701ab9cb35c58ac4de2f48e3d20b1836b GIT binary patch literal 3348 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 eEtMZV0sjY+fk@j(vX%4z0000 Date: Tue, 17 Dec 2024 13:22:58 -0700 Subject: [PATCH 22/25] additions --- .../DeltaV/Lamiae/ClientLamiaVisuals.cs | 42 +++++++++++++++++++ .../Lamiae/LamiaSegmentVisualsComponent.cs | 11 +++++ .../Entities/Mobs/NPCs/Rainlizard.yml | 18 +++++++- .../Objects/Specific/Species/lamia.yml | 6 +-- 4 files changed, 72 insertions(+), 5 deletions(-) create mode 100644 Content.Client/DeltaV/Lamiae/ClientLamiaVisuals.cs create mode 100644 Content.Client/DeltaV/Lamiae/LamiaSegmentVisualsComponent.cs diff --git a/Content.Client/DeltaV/Lamiae/ClientLamiaVisuals.cs b/Content.Client/DeltaV/Lamiae/ClientLamiaVisuals.cs new file mode 100644 index 00000000000..046f6a7a3c3 --- /dev/null +++ b/Content.Client/DeltaV/Lamiae/ClientLamiaVisuals.cs @@ -0,0 +1,42 @@ +/* +* Delta-V - This file is licensed under AGPLv3 +* Copyright (c) 2024 Delta-V Contributors +* See AGPLv3.txt for details. +*/ + +using Robust.Client.GameObjects; +using System.Numerics; +using Content.Shared.SegmentedEntity; + +namespace Content.Client.DeltaV.Lamiae; + +public sealed class ClientLamiaVisualSystem : VisualizerSystem +{ + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnAppearanceChange); + } + private void OnAppearanceChange(EntityUid uid, SegmentedEntitySegmentComponent component, ref AppearanceChangeEvent args) + { + if (args.Sprite == null) return; + + if (AppearanceSystem.TryGetData(uid, ScaleVisuals.Scale, out var scale) && TryComp(uid, out var sprite)) + { + sprite.Scale = new Vector2(scale, scale); + } + + if (AppearanceSystem.TryGetData(uid, SegmentedEntitySegmentVisualLayers.Armor, out var worn) + && AppearanceSystem.TryGetData(uid, SegmentedEntitySegmentVisualLayers.ArmorRsi, out var path)) + { + var valid = !string.IsNullOrWhiteSpace(path); + if (valid) + { + args.Sprite.LayerSetRSI(SegmentedEntitySegmentVisualLayers.Armor, path); + } + args.Sprite.LayerSetVisible(SegmentedEntitySegmentVisualLayers.Armor, worn); + } + } +} diff --git a/Content.Client/DeltaV/Lamiae/LamiaSegmentVisualsComponent.cs b/Content.Client/DeltaV/Lamiae/LamiaSegmentVisualsComponent.cs new file mode 100644 index 00000000000..e2c1139f234 --- /dev/null +++ b/Content.Client/DeltaV/Lamiae/LamiaSegmentVisualsComponent.cs @@ -0,0 +1,11 @@ +/* +* Delta-V - This file is licensed under AGPLv3 +* Copyright (c) 2024 Delta-V Contributors +* See AGPLv3.txt for details. +*/ + +namespace Content.Client.DeltaV.Lamiae; + +[RegisterComponent] +public sealed partial class SegmentedEntitySegmentVisualsComponent : Component +{} diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/Rainlizard.yml b/Resources/Prototypes/Entities/Mobs/NPCs/Rainlizard.yml index 0aba7bf036c..dddd1542b20 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/Rainlizard.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/Rainlizard.yml @@ -12,11 +12,23 @@ speed: 6 qualities: - Prying + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeCircle + radius: 0.2 + density: 0.0007 + mask: + - FlyingMobMask + layer: + - FlyingMobLayer - type: Sprite - drawdepth: Mobs + drawdepth: SmallMobs + noRot: false sprite: /Textures/Floof/Mobs/Animals/rainlizard.rsi scale: 0.6,0.6 - offset: 0,0 + offset: 0,0.4 layers: - map: ["enum.DamageStateVisualLayers.Base"] state: running @@ -26,6 +38,8 @@ energy: 1 color: "#30ce00" - type: SegmentedEntity + initialSegmentId: LamiaInitialSegment + segmentId: LamiaSegment numberOfSegments: 3 initialRadius: 0.25 staticScale: 0.9 diff --git a/Resources/Prototypes/Entities/Objects/Specific/Species/lamia.yml b/Resources/Prototypes/Entities/Objects/Specific/Species/lamia.yml index f5fad8c21cb..ba15340ff86 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Species/lamia.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Species/lamia.yml @@ -20,12 +20,12 @@ shape: !type:PhysShapeCircle radius: 0.25 - density: 80 + density: 1 restitution: 0.0 mask: - - MobMask + - FlyingMobMask layer: - - MobLayer + - FlyingMobLayer - type: Transform anchored: false - type: Tag From 2a2d7c552985f5d82c90e1aac48eac99bdc03aac Mon Sep 17 00:00:00 2001 From: fenndragon Date: Fri, 20 Dec 2024 19:25:11 -0700 Subject: [PATCH 23/25] fix --- .../DeltaV/Lamiae/ClientLamiaVisuals.cs | 42 ------------------- .../Lamiae/LamiaSegmentVisualsComponent.cs | 11 ----- 2 files changed, 53 deletions(-) delete mode 100644 Content.Client/DeltaV/Lamiae/ClientLamiaVisuals.cs delete mode 100644 Content.Client/DeltaV/Lamiae/LamiaSegmentVisualsComponent.cs diff --git a/Content.Client/DeltaV/Lamiae/ClientLamiaVisuals.cs b/Content.Client/DeltaV/Lamiae/ClientLamiaVisuals.cs deleted file mode 100644 index 046f6a7a3c3..00000000000 --- a/Content.Client/DeltaV/Lamiae/ClientLamiaVisuals.cs +++ /dev/null @@ -1,42 +0,0 @@ -/* -* Delta-V - This file is licensed under AGPLv3 -* Copyright (c) 2024 Delta-V Contributors -* See AGPLv3.txt for details. -*/ - -using Robust.Client.GameObjects; -using System.Numerics; -using Content.Shared.SegmentedEntity; - -namespace Content.Client.DeltaV.Lamiae; - -public sealed class ClientLamiaVisualSystem : VisualizerSystem -{ - - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnAppearanceChange); - } - private void OnAppearanceChange(EntityUid uid, SegmentedEntitySegmentComponent component, ref AppearanceChangeEvent args) - { - if (args.Sprite == null) return; - - if (AppearanceSystem.TryGetData(uid, ScaleVisuals.Scale, out var scale) && TryComp(uid, out var sprite)) - { - sprite.Scale = new Vector2(scale, scale); - } - - if (AppearanceSystem.TryGetData(uid, SegmentedEntitySegmentVisualLayers.Armor, out var worn) - && AppearanceSystem.TryGetData(uid, SegmentedEntitySegmentVisualLayers.ArmorRsi, out var path)) - { - var valid = !string.IsNullOrWhiteSpace(path); - if (valid) - { - args.Sprite.LayerSetRSI(SegmentedEntitySegmentVisualLayers.Armor, path); - } - args.Sprite.LayerSetVisible(SegmentedEntitySegmentVisualLayers.Armor, worn); - } - } -} diff --git a/Content.Client/DeltaV/Lamiae/LamiaSegmentVisualsComponent.cs b/Content.Client/DeltaV/Lamiae/LamiaSegmentVisualsComponent.cs deleted file mode 100644 index e2c1139f234..00000000000 --- a/Content.Client/DeltaV/Lamiae/LamiaSegmentVisualsComponent.cs +++ /dev/null @@ -1,11 +0,0 @@ -/* -* Delta-V - This file is licensed under AGPLv3 -* Copyright (c) 2024 Delta-V Contributors -* See AGPLv3.txt for details. -*/ - -namespace Content.Client.DeltaV.Lamiae; - -[RegisterComponent] -public sealed partial class SegmentedEntitySegmentVisualsComponent : Component -{} From 3cd9bcabd00411164361577756af88cdde15584a Mon Sep 17 00:00:00 2001 From: fenndragon Date: Mon, 30 Dec 2024 00:24:53 -0700 Subject: [PATCH 24/25] r --- Content.Client/DeltaV/Lamiae/SnakeOverlay.cs | 10 +- .../DeltaV/Lamiae/SnakeOverlaySystem.cs | 10 +- .../Systems/ShuttleSystem.FasterThanLight.cs | 37 +- Content.Server/Teleportation/PortalSystem.cs | 2 +- .../Weapons/Ranged/Systems/GunSystem.cs | 8 +- Content.Shared/Roles/StartingGearPrototype.cs | 1 + .../SegmentedEntity/SegmentSpawnedEvent.cs | 1 + .../SegmentedEntityComponent.cs | 197 ++++---- .../SegmentedEntitySegmentComponent.cs | 75 +-- .../SegmentedEntity/SegmentedEntitySystem.cs | 472 +++++++++--------- .../Systems/SharedPortalSystem.cs | 27 +- .../Ranged/Events/HitScanAfterRayCastEvent.cs | 2 +- .../Objects/Specific/Species/lamia.yml | 10 +- .../Entities/Mobs/NPCs/Rainlizard.yml | 2 - 14 files changed, 430 insertions(+), 424 deletions(-) rename Resources/Prototypes/{ => DeltaV}/Entities/Objects/Specific/Species/lamia.yml (89%) diff --git a/Content.Client/DeltaV/Lamiae/SnakeOverlay.cs b/Content.Client/DeltaV/Lamiae/SnakeOverlay.cs index a8ed98c3369..e7b7e9f1aee 100644 --- a/Content.Client/DeltaV/Lamiae/SnakeOverlay.cs +++ b/Content.Client/DeltaV/Lamiae/SnakeOverlay.cs @@ -1,9 +1,3 @@ -/* -* This file is licensed under AGPLv3 -* Copyright (c) 2024 Rane -* See AGPLv3.txt for details. -*/ - using Content.Shared.SegmentedEntity; using Content.Shared.Humanoid; using Content.Shared.Humanoid.Markings; @@ -15,7 +9,7 @@ using System.Linq; -namespace Content.Client.DeltaV.Lamiae; +namespace Content.Client.Lamiae; /// /// This draws lamia segments directly from polygons instead of sprites. This is a very novel approach as of the time this is being written (August 2024) but it wouldn't surprise me @@ -181,4 +175,4 @@ private void DrawLamia(DrawingHandleWorld handle, SegmentedEntityComponent lamia // Draw all of the triangles we just pit in at once handle.DrawPrimitives(DrawPrimitiveTopology.TriangleList, texture: tex, verts.ToArray().AsSpan(), color); } -} \ No newline at end of file +} diff --git a/Content.Client/DeltaV/Lamiae/SnakeOverlaySystem.cs b/Content.Client/DeltaV/Lamiae/SnakeOverlaySystem.cs index 6be1a18dc14..ed2d8b58025 100644 --- a/Content.Client/DeltaV/Lamiae/SnakeOverlaySystem.cs +++ b/Content.Client/DeltaV/Lamiae/SnakeOverlaySystem.cs @@ -1,13 +1,7 @@ -/* -* This file is licensed under AGPLv3 -* Copyright (c) 2024 Rane -* See AGPLv3.txt for details. -*/ - using Robust.Client.Graphics; using Robust.Client.ResourceManagement; -namespace Content.Client.DeltaV.Lamiae; +namespace Content.Client.Lamiae; /// /// This system turns on our always-on overlay. I have no opinion on this design pattern or the existence of this file. @@ -29,4 +23,4 @@ public override void Shutdown() base.Shutdown(); _overlay.RemoveOverlay(); } -} \ No newline at end of file +} diff --git a/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs b/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs index 11cc16e0cd0..3b5565c52e7 100644 --- a/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs +++ b/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs @@ -11,6 +11,7 @@ using Content.Shared.Ghost; using Content.Shared.Maps; using Content.Shared.Parallax; +using Content.Shared.SegmentedEntity; using Content.Shared.Shuttles.Components; using Content.Shared.Shuttles.Systems; using Content.Shared.StatusEffect; @@ -81,6 +82,8 @@ public sealed partial class ShuttleSystem private void InitializeFTL() { SubscribeLocalEvent(OnStationPostInit); + SubscribeLocalEvent(OnFtlShutdown); + _bodyQuery = GetEntityQuery(); _buckleQuery = GetEntityQuery(); _beaconQuery = GetEntityQuery(); @@ -97,6 +100,12 @@ private void InitializeFTL() _cfg.OnValueChanged(CCVars.HyperspaceKnockdownTime, time => _hyperspaceKnockdownTime = TimeSpan.FromSeconds(time), true); } + private void OnFtlShutdown(Entity ent, ref ComponentShutdown args) + { + Del(ent.Comp.VisualizerEntity); + ent.Comp.VisualizerEntity = null; + } + private void OnStationPostInit(ref StationPostInitEvent ev) { // Add all grid maps as ftl destinations that anyone can FTL to. @@ -343,7 +352,11 @@ private bool TrySetupFTL(EntityUid uid, ShuttleComponent shuttle, [NotNullWhen(t component = AddComp(uid); component.State = FTLState.Starting; var audio = _audio.PlayPvs(_startupSound, uid); - audio.Value.Component.Flags |= AudioFlags.GridAudio; + + if (audio == null) + return false; + + audio!.Value.Component.Flags |= AudioFlags.GridAudio; if (_physicsQuery.TryGetComponent(uid, out var gridPhysics)) { @@ -422,8 +435,16 @@ private void UpdateFTLTravelling(Entity entity) var comp = entity.Comp1; comp.StateTime = StartEndTime.FromCurTime(_gameTiming, DefaultArrivalTime); comp.State = FTLState.Arriving; - // TODO: Arrival effects - // For now we'll just use the ss13 bubbles but we can do fancier. + + if (entity.Comp1.VisualizerProto != null) + { + comp.VisualizerEntity = SpawnAtPosition(entity.Comp1.VisualizerProto, entity.Comp1.TargetCoordinates); + var visuals = Comp(comp.VisualizerEntity.Value); + visuals.Grid = entity.Owner; + Dirty(comp.VisualizerEntity.Value, visuals); + _transform.SetLocalRotation(comp.VisualizerEntity.Value, entity.Comp1.TargetAngle); + _pvs.AddGlobalOverride(comp.VisualizerEntity.Value); + } _thruster.DisableLinearThrusters(shuttle); _thruster.EnableLinearThrustDirection(shuttle, DirectionFlag.South); @@ -509,7 +530,11 @@ private void UpdateFTLArriving(Entity entity) comp.TravelStream = _audio.Stop(comp.TravelStream); var audio = _audio.PlayPvs(_arrivalSound, uid); - audio.Value.Component.Flags |= AudioFlags.GridAudio; + + if (audio == null) + return; + + audio!.Value.Component.Flags |= AudioFlags.GridAudio; // TODO: Shitcode til engine fix if (_physicsQuery.TryGetComponent(uid, out var gridPhysics)) @@ -615,7 +640,9 @@ private void KnockOverKids(TransformComponent xform, ref ValueList to var childEnumerator = xform.ChildEnumerator; while (childEnumerator.MoveNext(out var child)) { - if (!_buckleQuery.TryGetComponent(child, out var buckle) || buckle.Buckled) + if (!_buckleQuery.TryGetComponent(child, out var buckle) || buckle.Buckled + || HasComp(child) + || HasComp(child)) continue; toKnock.Add(child); diff --git a/Content.Server/Teleportation/PortalSystem.cs b/Content.Server/Teleportation/PortalSystem.cs index 76900a7e198..27fec274351 100644 --- a/Content.Server/Teleportation/PortalSystem.cs +++ b/Content.Server/Teleportation/PortalSystem.cs @@ -1,4 +1,4 @@ -using Content.Shared.Administration.Logs; +using Content.Shared.Administration.Logs; using Content.Shared.Database; using Content.Shared.Ghost; using Content.Shared.Mind.Components; diff --git a/Content.Server/Weapons/Ranged/Systems/GunSystem.cs b/Content.Server/Weapons/Ranged/Systems/GunSystem.cs index cbbfc289cf5..08ad5e8d6bf 100644 --- a/Content.Server/Weapons/Ranged/Systems/GunSystem.cs +++ b/Content.Server/Weapons/Ranged/Systems/GunSystem.cs @@ -203,7 +203,13 @@ public override void Shoot(EntityUid gunUid, GunComponent gun, List<(EntityUid? if (!rayCastResults.Any()) break; - var result = rayCastResults[0]; + var raycastEvent = new HitScanAfterRayCastEvent(rayCastResults); + RaiseLocalEvent(lastUser, ref raycastEvent); + + if (raycastEvent.RayCastResults == null) + break; + + var result = raycastEvent.RayCastResults[0]; var hit = result.HitEntity; lastHit = hit; diff --git a/Content.Shared/Roles/StartingGearPrototype.cs b/Content.Shared/Roles/StartingGearPrototype.cs index 0ff65617bc9..32b72aaeede 100644 --- a/Content.Shared/Roles/StartingGearPrototype.cs +++ b/Content.Shared/Roles/StartingGearPrototype.cs @@ -43,6 +43,7 @@ public string GetGear(string slot, HumanoidCharacterProfile? profile) { case "jumpsuit" when profile.Clothing == ClothingPreference.Jumpskirt && !string.IsNullOrEmpty(InnerClothingSkirt): case "jumpsuit" when profile.Species == "Harpy" && !string.IsNullOrEmpty(InnerClothingSkirt): + case "jumpsuit" when profile.Species == "Lamia" && !string.IsNullOrEmpty(InnerClothingSkirt): return InnerClothingSkirt; case "back" when profile.Backpack == BackpackPreference.Satchel && !string.IsNullOrEmpty(Satchel): return Satchel; diff --git a/Content.Shared/SegmentedEntity/SegmentSpawnedEvent.cs b/Content.Shared/SegmentedEntity/SegmentSpawnedEvent.cs index 6b0e0407a32..4fdf893ad82 100644 --- a/Content.Shared/SegmentedEntity/SegmentSpawnedEvent.cs +++ b/Content.Shared/SegmentedEntity/SegmentSpawnedEvent.cs @@ -1,4 +1,5 @@ namespace Content.Shared.SegmentedEntity; + public sealed class SegmentSpawnedEvent : EntityEventArgs { public EntityUid Lamia = default!; diff --git a/Content.Shared/SegmentedEntity/SegmentedEntityComponent.cs b/Content.Shared/SegmentedEntity/SegmentedEntityComponent.cs index 83f2450539f..e5613379e74 100644 --- a/Content.Shared/SegmentedEntity/SegmentedEntityComponent.cs +++ b/Content.Shared/SegmentedEntity/SegmentedEntityComponent.cs @@ -1,108 +1,101 @@ -/* -* Delta-V - This file is licensed under AGPLv3 -* Copyright (c) 2024 Delta-V Contributors -* See AGPLv3.txt for details. -*/ - using Robust.Shared.GameStates; -using Robust.Shared.Serialization; -using Robust.Shared.Utility; -namespace Content.Shared.SegmentedEntity +namespace Content.Shared.SegmentedEntity; + +/// +/// Controls initialization of any Multi-segmented entity +/// +[RegisterComponent, NetworkedComponent] +[AutoGenerateComponentState] +public sealed partial class SegmentedEntityComponent : Component { /// - /// Controls initialization of any Multi-segmented entity + /// A list of each UID attached to the Lamia, in order of spawn + /// + [DataField, AutoNetworkedField] + public List Segments = new(); + + /// + /// A clamped variable that represents the number of segments to be spawned + /// + [DataField] + public int NumberOfSegments = 18; + + /// + /// How wide the initial segment should be. + /// + [DataField] + public float InitialRadius = 0.3f; + + /// + /// Texture of the segment. + /// + [DataField(required: true)] + public string TexturePath; + + /// + /// If UseTaperSystem is true, this constant represents the rate at which a segmented entity will taper towards the tip. Tapering is on a logarithmic scale, and will asymptotically approach 0. + /// + [DataField] + public float OffsetConstant = 1.03f; + + /// + /// Represents the prototype used to parent all segments + /// + [DataField] + public string InitialSegmentId = "LamiaInitialSegment"; + + /// + /// Represents the segment prototype to be spawned + /// + [DataField] + public string SegmentId = "LamiaSegment"; + + /// + /// How much to slim each successive segment. + /// + [DataField] + public float SlimFactor = 0.93f; + + /// + /// Set to false for constant width + /// + [DataField] + public bool UseTaperSystem = true; + + /// + /// The standard distance between the centerpoint of each segment. + /// + [DataField] + public float StaticOffset = 0.15f; + + /// + /// The standard sprite scale of each segment. + /// + [DataField] + public float StaticScale = 1f; + + /// + /// Used to more finely tune how much damage should be transfered from tail to body. + /// + [DataField] + public float DamageModifierOffset = 0.4f; + + /// + /// A clamped variable that represents how far from the tip should tapering begin. + /// + [DataField] + public int TaperOffset = 18; + + /// + /// Coefficient used to finely tune how much explosion damage should be transfered to the body. This is calculated multiplicatively with the derived damage modifier set. + /// + [DataField] + public float ExplosiveModifierOffset = 0.1f; + + /// + /// Controls whether or not lamia segments always block bullets, or use the bullet passover system for laying down bodies. /// - [RegisterComponent, NetworkedComponent] - [AutoGenerateComponentState] - public sealed partial class SegmentedEntityComponent : Component - { - /// - /// A list of each UID attached to the Lamia, in order of spawn - /// - [DataField("segments")] - [AutoNetworkedField] - public List Segments = new(); - - /// - /// A clamped variable that represents the number of segments to be spawned - /// - [DataField("numberOfSegments")] - public int NumberOfSegments = 18; - - /// - /// How wide the initial segment should be. - /// - [DataField("initialRadius")] - public float InitialRadius = 0.3f; - - /// - /// Texture of the segment. - /// - [DataField("texturePath", required: true)] - public string TexturePath; - - /// - /// If UseTaperSystem is true, this constant represents the rate at which a segmented entity will taper towards the tip. Tapering is on a logarithmic scale, and will asymptotically approach 0. - /// - [DataField("offsetConstant")] - public float OffsetConstant = 1.03f; - - /// - /// Represents the prototype used to parent all segments - /// - [DataField("initialSegmentId")] - public string InitialSegmentId = "LamiaInitialSegment"; - - /// - /// Represents the segment prototype to be spawned - /// - [DataField("segmentId")] - public string SegmentId = "LamiaSegment"; - - /// - /// How much to slim each successive segment. - /// - [DataField("slimFactor")] - public float SlimFactor = 0.93f; - - /// - /// Set to 1f for constant width - /// - [DataField("useTaperSystem")] - public bool UseTaperSystem = true; - - /// - /// The standard distance between the centerpoint of each segment. - /// - [DataField("staticOffset")] - public float StaticOffset = 0.15f; - - /// - /// The standard sprite scale of each segment. - /// - [DataField("staticScale")] - public float StaticScale = 1f; - - /// - /// Used to more finely tune how much damage should be transfered from tail to body. - /// - [DataField("damageModifierOffset")] - public float DamageModifierOffset = 0.4f; - - /// - /// A clamped variable that represents how far from the tip should tapering begin. - /// - [DataField("taperOffset")] - public int TaperOffset = 18; - - /// - /// Coefficient used to finely tune how much explosion damage should be transfered to the body. This is calculated multiplicatively with the derived damage modifier set. - /// - [DataField("explosiveModifierOffset")] - public float ExplosiveModifierOffset = 0.1f; - - [DataField("bulletPassover")] - public bool BulletPassover = true; - } + [DataField] + public bool BulletPassover = true; } diff --git a/Content.Shared/SegmentedEntity/SegmentedEntitySegmentComponent.cs b/Content.Shared/SegmentedEntity/SegmentedEntitySegmentComponent.cs index b29e282e9e3..0e708e69189 100644 --- a/Content.Shared/SegmentedEntity/SegmentedEntitySegmentComponent.cs +++ b/Content.Shared/SegmentedEntity/SegmentedEntitySegmentComponent.cs @@ -1,35 +1,48 @@ -/* -* Delta-V - This file is licensed under AGPLv3 -* Copyright (c) 2024 Delta-V Contributors -* See AGPLv3.txt for details. -*/ - using Robust.Shared.GameStates; -namespace Content.Shared.SegmentedEntity +namespace Content.Shared.SegmentedEntity; + +/// +/// This is a tracking component used for storing quick reference information related to a Lamia's main body on each of her segments, +/// which is needed both for simplifying a lot of code, as well as tracking who the original body was. +/// None of these are Datafields for a reason, they are modified by the original parent body upon spawning her segments. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class SegmentedEntitySegmentComponent : Component { - /// - /// Lamia segment - /// - [RegisterComponent] - [NetworkedComponent] - public sealed partial class SegmentedEntitySegmentComponent : Component - { - [DataField("AttachedToUid")] - public EntityUid AttachedToUid = default!; - public float DamageModifyFactor = default!; - public float OffsetSwitching = default!; - public float ScaleFactor = default!; - [DataField("DamageModifierCoefficient")] - public float DamageModifierCoefficient = default!; - public float ExplosiveModifyFactor = default!; - public float OffsetConstant = default!; - [DataField("Lamia")] - public EntityUid Lamia = default!; - public int MaxSegments = default!; - public int SegmentNumber = default!; - public float DamageModifierConstant = default!; - [DataField("segmentId")] - public string? segmentId; - } + [ViewVariables] + public EntityUid AttachedToUid = default!; + + [ViewVariables] + public float DamageModifyFactor = default!; + + [ViewVariables] + public float OffsetSwitching = default!; + + [ViewVariables] + public float ScaleFactor = default!; + + [ViewVariables] + public float DamageModifierCoefficient = default!; + + [ViewVariables] + public float ExplosiveModifyFactor = default!; + + [ViewVariables] + public float OffsetConstant = default!; + + [ViewVariables] + public EntityUid Lamia = default!; + + [ViewVariables] + public int MaxSegments = default!; + + [ViewVariables] + public int SegmentNumber = default!; + + [ViewVariables] + public float DamageModifierConstant = default!; + + [ViewVariables] + public string? SegmentId; } diff --git a/Content.Shared/SegmentedEntity/SegmentedEntitySystem.cs b/Content.Shared/SegmentedEntity/SegmentedEntitySystem.cs index 5ab786c429e..928edbfb043 100644 --- a/Content.Shared/SegmentedEntity/SegmentedEntitySystem.cs +++ b/Content.Shared/SegmentedEntity/SegmentedEntitySystem.cs @@ -11,296 +11,288 @@ using Robust.Shared.Map; using Robust.Shared.Physics.Systems; using Robust.Shared.Physics.Components; +using Robust.Shared.Prototypes; using System.Numerics; using Robust.Shared.Network; -using Robust.Shared.GameStates; -namespace Content.Shared.SegmentedEntity +namespace Content.Shared.SegmentedEntity; + +public sealed partial class LamiaSystem : EntitySystem { - public sealed partial class LamiaSystem : EntitySystem - { - [Dependency] private readonly TagSystem _tagSystem = default!; - [Dependency] private readonly DamageableSystem _damageableSystem = default!; - [Dependency] private readonly SharedJointSystem _jointSystem = default!; - [Dependency] private readonly INetManager _net = default!; - [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly TagSystem _tagSystem = default!; + [Dependency] private readonly DamageableSystem _damageableSystem = default!; + [Dependency] private readonly SharedJointSystem _jointSystem = default!; + [Dependency] private readonly INetManager _net = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; - Queue<(SegmentedEntitySegmentComponent segment, EntityUid lamia)> _segments = new(); + private Queue<(SegmentedEntitySegmentComponent segment, EntityUid lamia)> _segments = new(); - [ValidatePrototypeId] - private const string LamiaHardsuitTag = "AllowLamiaHardsuit"; - public override void Initialize() - { - base.Initialize(); - //Parent subscriptions - SubscribeLocalEvent(OnShootHitscan); - SubscribeLocalEvent(OnLamiaStorageInsertAttempt); - SubscribeLocalEvent(OnDidEquipEvent); - SubscribeLocalEvent(OnDidUnequipEvent); - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnShutdown); - SubscribeLocalEvent(OnJointRemoved); - SubscribeLocalEvent(OnParentChanged); - SubscribeLocalEvent(OnStoreSnekAttempt); - - //Child subscriptions - SubscribeLocalEvent(OnSegmentStorageInsertAttempt); - SubscribeLocalEvent(OnSnekBoom); - SubscribeLocalEvent(HandleDamageTransfer); - SubscribeLocalEvent(HandleSegmentDamage); - } - public override void Update(float frameTime) - { - //I HATE THIS, SO MUCH. I AM FORCED TO DEAL WITH THIS MONSTROSITY. PLEASE. SEND HELP. - base.Update(frameTime); - foreach (var segment in _segments) - { - var segmentUid = segment.segment.Owner; - var attachedUid = segment.segment.AttachedToUid; - if (!Exists(segmentUid) || !Exists(attachedUid) - || MetaData(segmentUid).EntityLifeStage > EntityLifeStage.MapInitialized - || MetaData(attachedUid).EntityLifeStage > EntityLifeStage.MapInitialized - || Transform(segmentUid).MapID == MapId.Nullspace - || Transform(attachedUid).MapID == MapId.Nullspace) - continue; - - EnsureComp(segmentUid); - EnsureComp(attachedUid); // Hello I hate tests - - // This is currently HERE and not somewhere more sane like OnInit because HumanoidAppearanceComponent is for whatever - // ungodly reason not initialized when ComponentStartup is called. Kill me. - var humanoidFactor = TryComp(segment.segment.Lamia, out var humanoid) ? (humanoid.Height + humanoid.Width) / 2 : 1; - - var ev = new SegmentSpawnedEvent(segment.lamia); - RaiseLocalEvent(segmentUid, ev, false); - - if (segment.segment.SegmentNumber == 1) - { - _transform.SetCoordinates(segmentUid, Transform(attachedUid).Coordinates); - var revoluteJoint = _jointSystem.CreateWeldJoint(attachedUid, segmentUid, id: "Segment" + segment.segment.SegmentNumber + segment.segment.Lamia); - revoluteJoint.CollideConnected = false; - } - if (segment.segment.SegmentNumber <= segment.segment.MaxSegments) - _transform.SetCoordinates(segmentUid, Transform(attachedUid).Coordinates.Offset(new Vector2(0, segment.segment.OffsetSwitching * humanoidFactor))); - else - _transform.SetCoordinates(segmentUid, Transform(attachedUid).Coordinates.Offset(new Vector2(0, segment.segment.OffsetSwitching * humanoidFactor))); - - var joint = _jointSystem.CreateDistanceJoint(attachedUid, segmentUid, id: ("Segment" + segment.segment.SegmentNumber + segment.segment.Lamia)); - joint.CollideConnected = false; - joint.Stiffness = 0.2f; - } - _segments.Clear(); - } - private void OnInit(EntityUid uid, SegmentedEntityComponent component, ComponentInit args) - { - EnsureComp(uid); //Temporary, remove when Portal handling is added - Math.Clamp(component.NumberOfSegments, 2, 18); - Math.Clamp(component.TaperOffset, 1, component.NumberOfSegments - 1); - SpawnSegments(uid, component); - } + private ProtoId _lamiaHardsuitTag = "AllowLamiaHardsuit"; - private void OnShutdown(EntityUid uid, SegmentedEntityComponent component, ComponentShutdown args) + public override void Initialize() + { + base.Initialize(); + //Parent subscriptions + SubscribeLocalEvent(OnShootHitscan); + SubscribeLocalEvent(OnLamiaStorageInsertAttempt); + SubscribeLocalEvent(OnDidEquipEvent); + SubscribeLocalEvent(OnDidUnequipEvent); + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnJointRemoved); + SubscribeLocalEvent(OnParentChanged); + SubscribeLocalEvent(OnStoreSnekAttempt); + + //Child subscriptions + SubscribeLocalEvent(OnSegmentStorageInsertAttempt); + SubscribeLocalEvent(OnSnekBoom); + SubscribeLocalEvent(HandleDamageTransfer); + SubscribeLocalEvent(HandleSegmentDamage); + } + public override void Update(float frameTime) + { + //I HATE THIS, SO MUCH. I AM FORCED TO DEAL WITH THIS MONSTROSITY. PLEASE. SEND HELP. + base.Update(frameTime); + foreach (var segment in _segments) { - if (_net.IsClient) - return; - - foreach (var segment in component.Segments) + var segmentUid = segment.segment.Owner; + var attachedUid = segment.segment.AttachedToUid; + if (!Exists(segmentUid) || !Exists(attachedUid) + || MetaData(segmentUid).EntityLifeStage > EntityLifeStage.MapInitialized + || MetaData(attachedUid).EntityLifeStage > EntityLifeStage.MapInitialized + || Transform(segmentUid).MapID == MapId.Nullspace + || Transform(attachedUid).MapID == MapId.Nullspace) + continue; + + EnsureComp(segmentUid); + EnsureComp(attachedUid); // Hello I hate tests + + // This is currently HERE and not somewhere more sane like OnInit because HumanoidAppearanceComponent is for whatever + // ungodly reason not initialized when ComponentStartup is called. Kill me. + var humanoidFactor = TryComp(segment.segment.Lamia, out var humanoid) ? (humanoid.Height + humanoid.Width) / 2 : 1; + + var ev = new SegmentSpawnedEvent(segment.lamia); + RaiseLocalEvent(segmentUid, ev, false); + + if (segment.segment.SegmentNumber == 1) { - QueueDel(GetEntity(segment)); + _transform.SetCoordinates(segmentUid, Transform(attachedUid).Coordinates); + var revoluteJoint = _jointSystem.CreateWeldJoint(attachedUid, segmentUid, id: "Segment" + segment.segment.SegmentNumber + segment.segment.Lamia); + revoluteJoint.CollideConnected = false; } + if (segment.segment.SegmentNumber <= segment.segment.MaxSegments) + _transform.SetCoordinates(segmentUid, Transform(attachedUid).Coordinates.Offset(new Vector2(0, segment.segment.OffsetSwitching * humanoidFactor))); + else + _transform.SetCoordinates(segmentUid, Transform(attachedUid).Coordinates.Offset(new Vector2(0, segment.segment.OffsetSwitching * humanoidFactor))); - component.Segments.Clear(); + var joint = _jointSystem.CreateDistanceJoint(attachedUid, segmentUid, id: ("Segment" + segment.segment.SegmentNumber + segment.segment.Lamia)); + joint.CollideConnected = false; + joint.Stiffness = 0.2f; } + _segments.Clear(); + } + private void OnInit(EntityUid uid, SegmentedEntityComponent component, ComponentInit args) + { + EnsureComp(uid); //Temporary, remove when Portal handling is added + Math.Clamp(component.NumberOfSegments, 2, 18); + Math.Clamp(component.TaperOffset, 1, component.NumberOfSegments - 1); + SpawnSegments(uid, component); + } - /// - /// TODO: Full Self-Test function that intelligently checks the status of where everything is, and calls whatever - /// functions are appropriate - /// - /// - /// - public void SegmentSelfTest(EntityUid uid, SegmentedEntityComponent component) - { + private void OnShutdown(EntityUid uid, SegmentedEntityComponent component, ComponentShutdown args) + { + if (_net.IsClient) + return; - } + DeleteSegments(component); + } - /// - /// TODO: Function that ensures clothing visuals, to be called anytime the tail is reset - /// - /// - /// - private void EnsureSnekSock(EntityUid uid, SegmentedEntityComponent segment) - { + /// + /// TODO: Full Self-Test function that intelligently checks the status of where everything is, and calls whatever + /// functions are appropriate + /// + public void SegmentSelfTest(EntityUid uid, SegmentedEntityComponent component) + { - } - public void OnStoreSnekAttempt(EntityUid uid, SegmentedEntityComponent comp, ref StoreMobInItemContainerAttemptEvent args) - { - args.Cancelled = true; - } + } - private void OnJointRemoved(EntityUid uid, SegmentedEntityComponent component, JointRemovedEvent args) - { - if (!component.Segments.Contains(GetNetEntity(args.OtherEntity))) - return; + /// + /// TODO: Function that ensures clothing visuals, to be called anytime the tail is reset + /// + private void EnsureSnekSock(EntityUid uid, SegmentedEntityComponent segment) + { - DeleteSegments(component); - } + } - private void DeleteSegments(SegmentedEntityComponent component) - { - if (_net.IsClient) - return; //Client is not allowed to predict QueueDel, it'll throw an error(but won't crash in Release build) + public void OnStoreSnekAttempt(EntityUid uid, SegmentedEntityComponent comp, ref StoreMobInItemContainerAttemptEvent args) + { + args.Cancelled = true; + } - foreach (var segment in component.Segments) - QueueDel(GetEntity(segment)); + private void OnJointRemoved(EntityUid uid, SegmentedEntityComponent component, JointRemovedEvent args) + { + if (!component.Segments.Contains(GetNetEntity(args.OtherEntity))) + return; - component.Segments.Clear(); - } + DeleteSegments(component); + } - /// - /// Public call for a SegmentedEntity to reset their tail completely. - /// - /// - /// - public void RespawnSegments(EntityUid uid, SegmentedEntityComponent component) - { - DeleteSegments(component); - SpawnSegments(uid, component); - } + private void DeleteSegments(SegmentedEntityComponent component) + { + if (_net.IsClient) + return; //Client is not allowed to predict QueueDel, it'll throw an error(but won't crash in Release build) - private void SpawnSegments(EntityUid uid, SegmentedEntityComponent component) - { - if (_net.IsClient) - return; //Client is not allowed to spawn entities. It won't throw an error, but it'll make fake client entities. + foreach (var segment in component.Segments) + QueueDel(GetEntity(segment)); - int i = 1; - var addTo = uid; - while (i <= component.NumberOfSegments + 1) - { - var segment = AddSegment(addTo, uid, component, i); - addTo = segment; - i++; - } + component.Segments.Clear(); + } - Dirty(uid, component); - } + /// + /// Public call for a SegmentedEntity to reset their tail completely. + /// + public void RespawnSegments(EntityUid uid, SegmentedEntityComponent component) + { + DeleteSegments(component); + SpawnSegments(uid, component); + } - private EntityUid AddSegment(EntityUid segmentuid, EntityUid parentuid, SegmentedEntityComponent segmentedComponent, int segmentNumber) - { - float taperConstant = segmentedComponent.NumberOfSegments - segmentedComponent.TaperOffset; - EntityUid segment; - if (segmentNumber == 1) - segment = EntityManager.SpawnEntity(segmentedComponent.InitialSegmentId, Transform(segmentuid).Coordinates); - else - segment = EntityManager.SpawnEntity(segmentedComponent.SegmentId, Transform(segmentuid).Coordinates); + private void SpawnSegments(EntityUid uid, SegmentedEntityComponent component) + { + if (_net.IsClient) + return; //Client is not allowed to spawn entities. It won't throw an error, but it'll make fake client entities. - var segmentComponent = EnsureComp(segment); + int i = 1; + var addTo = uid; + while (i <= component.NumberOfSegments + 1) + { + var segment = AddSegment(addTo, uid, component, i); + addTo = segment; + i++; + } - segmentComponent.Lamia = parentuid; - segmentComponent.AttachedToUid = segmentuid; - segmentComponent.DamageModifierConstant = segmentedComponent.NumberOfSegments * segmentedComponent.DamageModifierOffset; - float damageModifyCoefficient = segmentComponent.DamageModifierConstant / segmentedComponent.NumberOfSegments; - segmentComponent.DamageModifyFactor = segmentComponent.DamageModifierConstant * damageModifyCoefficient; - segmentComponent.ExplosiveModifyFactor = 1 / segmentComponent.DamageModifyFactor / (segmentedComponent.NumberOfSegments * segmentedComponent.ExplosiveModifierOffset); - segmentComponent.SegmentNumber = segmentNumber; - segmentComponent.Owner = segment; + Dirty(uid, component); + } - if (segmentedComponent.UseTaperSystem == true) + private EntityUid AddSegment(EntityUid segmentuid, EntityUid parentuid, SegmentedEntityComponent segmentedComponent, int segmentNumber) + { + float taperConstant = segmentedComponent.NumberOfSegments - segmentedComponent.TaperOffset; + EntityUid segment = EntityManager.SpawnEntity(segmentNumber == 1 + ? segmentedComponent.InitialSegmentId + : segmentedComponent.SegmentId, Transform(segmentuid).Coordinates); + + var segmentComponent = EnsureComp(segment); + + segmentComponent.Lamia = parentuid; + segmentComponent.AttachedToUid = segmentuid; + segmentComponent.DamageModifierConstant = segmentedComponent.NumberOfSegments * segmentedComponent.DamageModifierOffset; + float damageModifyCoefficient = segmentComponent.DamageModifierConstant / segmentedComponent.NumberOfSegments; + segmentComponent.DamageModifyFactor = segmentComponent.DamageModifierConstant * damageModifyCoefficient; + segmentComponent.ExplosiveModifyFactor = 1 / segmentComponent.DamageModifyFactor / (segmentedComponent.NumberOfSegments * segmentedComponent.ExplosiveModifierOffset); + segmentComponent.SegmentNumber = segmentNumber; + + if (segmentedComponent.UseTaperSystem) + { + if (segmentNumber >= taperConstant) { - if (segmentNumber >= taperConstant) - { - segmentComponent.OffsetSwitching = segmentedComponent.StaticOffset - * MathF.Pow(segmentedComponent.OffsetConstant, segmentNumber - taperConstant); - - segmentComponent.ScaleFactor = segmentedComponent.StaticScale - * MathF.Pow(1f / segmentedComponent.OffsetConstant, segmentNumber - taperConstant); - } - if (segmentNumber < taperConstant) - { - segmentComponent.OffsetSwitching = segmentedComponent.StaticOffset; - segmentComponent.ScaleFactor = segmentedComponent.StaticScale; - } + segmentComponent.OffsetSwitching = segmentedComponent.StaticOffset + * MathF.Pow(segmentedComponent.OffsetConstant, segmentNumber - taperConstant); + + segmentComponent.ScaleFactor = segmentedComponent.StaticScale + * MathF.Pow(1f / segmentedComponent.OffsetConstant, segmentNumber - taperConstant); } - else + if (segmentNumber < taperConstant) { segmentComponent.OffsetSwitching = segmentedComponent.StaticOffset; segmentComponent.ScaleFactor = segmentedComponent.StaticScale; } - - // We invert the Y axis offset on every odd numbered tail so that the segmented entity spawns in a neat pile - // Rather than stretching across 5 to 10 vertical tiles, and potentially getting trapped in a wall - if (segmentNumber % 2 != 0) - { - segmentComponent.OffsetSwitching *= -1; - } - - EnsureComp(segment); //Not temporary, segments must never be allowed to go through portals for physics limitation reasons - _segments.Enqueue((segmentComponent, parentuid)); - segmentedComponent.Segments.Add(GetNetEntity(segment)); - return segment; } - - private void HandleSegmentDamage(EntityUid uid, SegmentedEntitySegmentComponent component, DamageModifyEvent args) + else { - if (args.Origin == component.Lamia) - args.Damage *= 0; - args.Damage = args.Damage / component.DamageModifyFactor; - } - private void HandleDamageTransfer(EntityUid uid, SegmentedEntitySegmentComponent component, DamageChangedEvent args) - { - if (args.DamageDelta == null) return; - _damageableSystem.TryChangeDamage(component.Lamia, args.DamageDelta); + segmentComponent.OffsetSwitching = segmentedComponent.StaticOffset; + segmentComponent.ScaleFactor = segmentedComponent.StaticScale; } - private void OnLamiaStorageInsertAttempt(EntityUid uid, SegmentedEntityComponent comp, ref InsertIntoEntityStorageAttemptEvent args) + // We invert the Y axis offset on every odd numbered tail so that the segmented entity spawns in a neat pile + // Rather than stretching across 5 to 10 vertical tiles, and potentially getting trapped in a wall + if (segmentNumber % 2 != 0) { - args.Cancelled = true; + segmentComponent.OffsetSwitching *= -1; } - private void OnSegmentStorageInsertAttempt(EntityUid uid, SegmentedEntitySegmentComponent comp, ref InsertIntoEntityStorageAttemptEvent args) - { - args.Cancelled = true; - } + EnsureComp(segment); //Not temporary, segments must never be allowed to go through portals for physics limitation reasons + _segments.Enqueue((segmentComponent, parentuid)); + segmentedComponent.Segments.Add(GetNetEntity(segment)); + return segment; + } - private void OnDidEquipEvent(EntityUid equipee, SegmentedEntityComponent component, DidEquipEvent args) - { - if (!TryComp(args.Equipment, out var clothing)) return; - if (args.Slot == "outerClothing" && _tagSystem.HasTag(args.Equipment, LamiaHardsuitTag)) - { - // TODO: Switch segment sprite - } - } + private void HandleSegmentDamage(EntityUid uid, SegmentedEntitySegmentComponent component, DamageModifyEvent args) + { + if (args.Origin == component.Lamia) + args.Damage *= 0; + args.Damage = args.Damage / component.DamageModifyFactor; + } - private void OnSnekBoom(EntityUid uid, SegmentedEntitySegmentComponent component, ref GetExplosionResistanceEvent args) - { - args.DamageCoefficient = component.ExplosiveModifyFactor; - } + private void HandleDamageTransfer(EntityUid uid, SegmentedEntitySegmentComponent component, DamageChangedEvent args) + { + if (args.DamageDelta == null) + return; - private void OnDidUnequipEvent(EntityUid equipee, SegmentedEntityComponent component, DidUnequipEvent args) - { - if (args.Slot == "outerClothing" && _tagSystem.HasTag(args.Equipment, LamiaHardsuitTag)) - { - // TODO: Revert to default segment sprite - } - } + _damageableSystem.TryChangeDamage(component.Lamia, args.DamageDelta); + } - private void OnShootHitscan(EntityUid uid, SegmentedEntityComponent component, ref HitScanAfterRayCastEvent args) - { - if (args.RayCastResults == null) return; + private void OnLamiaStorageInsertAttempt(EntityUid uid, SegmentedEntityComponent comp, ref InsertIntoEntityStorageAttemptEvent args) + { + args.Cancelled = true; + } - var entityList = new List(); - foreach (var entity in args.RayCastResults) - { - if (!component.Segments.Contains(GetNetEntity(entity.HitEntity))) - entityList.Add(entity); - } - args.RayCastResults = entityList; + private void OnSegmentStorageInsertAttempt(EntityUid uid, SegmentedEntitySegmentComponent comp, ref InsertIntoEntityStorageAttemptEvent args) + { + args.Cancelled = true; + } + + private void OnDidEquipEvent(EntityUid equipee, SegmentedEntityComponent component, DidEquipEvent args) + { + if (!TryComp(args.Equipment, out var clothing) + || args.Slot != "outerClothing" + || !_tagSystem.HasTag(args.Equipment, _lamiaHardsuitTag)) + return; + + // TODO: Switch segment sprite + } + + private void OnSnekBoom(EntityUid uid, SegmentedEntitySegmentComponent component, ref GetExplosionResistanceEvent args) + { + args.DamageCoefficient = component.ExplosiveModifyFactor; + } + + private void OnDidUnequipEvent(EntityUid equipee, SegmentedEntityComponent component, DidUnequipEvent args) + { + if (args.Slot == "outerClothing" && _tagSystem.HasTag(args.Equipment, _lamiaHardsuitTag)) + { + // TODO: Revert to default segment sprite } + } - private void OnParentChanged(EntityUid uid, SegmentedEntityComponent component, ref EntParentChangedMessage args) + private void OnShootHitscan(EntityUid uid, SegmentedEntityComponent component, ref HitScanAfterRayCastEvent args) + { + if (args.RayCastResults == null) + return; + + var entityList = new List(); + foreach (var entity in args.RayCastResults) { - //If the change was NOT to a different map - //if (args.OldMapId == args.Transform.MapUid) - // RespawnSegments(uid, component); + if (!component.Segments.Contains(GetNetEntity(entity.HitEntity))) + entityList.Add(entity); } + + args.RayCastResults = entityList; + } + + private void OnParentChanged(EntityUid uid, SegmentedEntityComponent component, ref EntParentChangedMessage args) + { + //If the change was NOT to a different map + //if (args.OldMapId == args.Transform.MapUid) + // RespawnSegments(uid, component); } } diff --git a/Content.Shared/Teleportation/Systems/SharedPortalSystem.cs b/Content.Shared/Teleportation/Systems/SharedPortalSystem.cs index 31fb2177733..7e1124cef7a 100644 --- a/Content.Shared/Teleportation/Systems/SharedPortalSystem.cs +++ b/Content.Shared/Teleportation/Systems/SharedPortalSystem.cs @@ -1,10 +1,9 @@ -using System.Linq; +using System.Linq; using Content.Shared.Ghost; using Content.Shared.Movement.Pulling.Components; using Content.Shared.Movement.Pulling.Systems; using Content.Shared.Popups; using Content.Shared.Projectiles; -using Content.Shared.Shadowkin; using Content.Shared.Teleportation.Components; using Content.Shared.Verbs; using Robust.Shared.Audio; @@ -13,7 +12,6 @@ using Robust.Shared.Network; using Robust.Shared.Physics.Dynamics; using Robust.Shared.Physics.Events; -using Robust.Shared.Physics.Systems; using Robust.Shared.Player; using Robust.Shared.Random; using Robust.Shared.Utility; @@ -32,7 +30,6 @@ public abstract class SharedPortalSystem : EntitySystem [Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly PullingSystem _pulling = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; - [Dependency] private readonly SharedJointSystem _joints = default!; // Floofstation private const string PortalFixture = "portalFixture"; private const string ProjectileFixture = "projectile"; @@ -49,11 +46,9 @@ public override void Initialize() private void OnGetVerbs(EntityUid uid, PortalComponent component, GetVerbsEvent args) { - - if (!args.CanAccess || !HasComp(args.User)) // FloofStation Edit - // Traversal altverb for ghosts to use that bypasses normal functionality - if (!args.CanAccess || !HasComp(args.User)) - return; + // Traversal altverb for ghosts to use that bypasses normal functionality + if (!args.CanAccess || !HasComp(args.User)) + return; // Don't use the verb with unlinked or with multi-output portals // (this is only intended to be useful for ghosts to see where a linked portal leads) @@ -69,8 +64,6 @@ private void OnGetVerbs(EntityUid uid, PortalComponent component, GetVerbsEvent< var ent = link.LinkedEntities.First(); TeleportEntity(uid, args.User, Transform(ent).Coordinates, ent, false); - if (TryComp(args.User, out var timeout)) // Floofstation Edit - RemCompDeferred(args.User, timeout); }, Disabled = disabled, Text = Loc.GetString("portal-component-ghost-traverse"), @@ -90,7 +83,7 @@ private bool ShouldCollide(string ourId, string otherId, Fixture our, Fixture ot private void OnCollide(EntityUid uid, PortalComponent component, ref StartCollideEvent args) { - if (HasComp(uid) || HasComp(args.OtherEntity)) // Floofstation Edit + if (HasComp(args.OtherEntity)) return; if (!ShouldCollide(args.OurFixtureId, args.OtherFixtureId, args.OurFixture, args.OtherFixture)) @@ -147,7 +140,6 @@ private void OnCollide(EntityUid uid, PortalComponent component, ref StartCollid } TeleportEntity(uid, subject, Transform(target).Coordinates, target); - return; } @@ -161,10 +153,7 @@ private void OnCollide(EntityUid uid, PortalComponent component, ref StartCollid private void OnEndCollide(EntityUid uid, PortalComponent component, ref EndCollideEvent args) { - if (HasComp(uid) || HasComp(args.OtherEntity)) // Floofstation Edit - return; - - if (!ShouldCollide(args.OurFixtureId, args.OtherFixtureId, args.OurFixture, args.OtherFixture)) + if (!ShouldCollide(args.OurFixtureId, args.OtherFixtureId,args.OurFixture, args.OtherFixture)) return; var subject = args.OtherEntity; @@ -176,7 +165,7 @@ private void OnEndCollide(EntityUid uid, PortalComponent component, ref EndColli } } - private void TeleportEntity(EntityUid portal, EntityUid subject, EntityCoordinates target, EntityUid? targetEntity = null, bool playSound = true, + private void TeleportEntity(EntityUid portal, EntityUid subject, EntityCoordinates target, EntityUid? targetEntity=null, bool playSound=true, PortalComponent? portalComponent = null) { if (!Resolve(portal, ref portalComponent)) @@ -221,8 +210,6 @@ private void TeleportEntity(EntityUid portal, EntityUid subject, EntityCoordinat LogTeleport(portal, subject, Transform(subject).Coordinates, target); - _joints.RecursiveClearJoints(subject); // Floofstation - clear all joints on teleported entities so they don't end up being pulled 'cross the map or worse. - _transform.SetCoordinates(subject, target); if (!playSound) diff --git a/Content.Shared/Weapons/Ranged/Events/HitScanAfterRayCastEvent.cs b/Content.Shared/Weapons/Ranged/Events/HitScanAfterRayCastEvent.cs index 99bfd1eabc0..65dbb5cb391 100644 --- a/Content.Shared/Weapons/Ranged/Events/HitScanAfterRayCastEvent.cs +++ b/Content.Shared/Weapons/Ranged/Events/HitScanAfterRayCastEvent.cs @@ -3,7 +3,7 @@ namespace Content.Shared.Weapons.Ranged.Events; /// -/// Raised after an entity fires a hitscan weapon, but before the list is truncated to the first target. Necessary for Entities that need to prevent friendly fire +/// Raised after an entity fires a hitscan weapon, but before the list is truncated to the first target. Necessary for Entities that need to prevent friendly fire /// [ByRefEvent] public struct HitScanAfterRayCastEvent diff --git a/Resources/Prototypes/Entities/Objects/Specific/Species/lamia.yml b/Resources/Prototypes/DeltaV/Entities/Objects/Specific/Species/lamia.yml similarity index 89% rename from Resources/Prototypes/Entities/Objects/Specific/Species/lamia.yml rename to Resources/Prototypes/DeltaV/Entities/Objects/Specific/Species/lamia.yml index ba15340ff86..1ca5f5a6824 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Species/lamia.yml +++ b/Resources/Prototypes/DeltaV/Entities/Objects/Specific/Species/lamia.yml @@ -5,7 +5,7 @@ - type: entity id: LamiaInitialSegment save: false - noSpawn: true + categories: [ HideSpawnMenu ] components: - type: Damageable - type: StandingState @@ -20,12 +20,12 @@ shape: !type:PhysShapeCircle radius: 0.25 - density: 1 + density: 80 restitution: 0.0 mask: - - FlyingMobMask + - MobMask layer: - - FlyingMobLayer + - MobLayer - type: Transform anchored: false - type: Tag @@ -39,7 +39,7 @@ save: false parent: LamiaInitialSegment name: lamia segment - noSpawn: true + categories: [ HideSpawnMenu ] description: A tail segment, hopefully attached to a lamia. components: - type: Sprite diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/Rainlizard.yml b/Resources/Prototypes/Entities/Mobs/NPCs/Rainlizard.yml index dddd1542b20..20c83b6cfca 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/Rainlizard.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/Rainlizard.yml @@ -38,8 +38,6 @@ energy: 1 color: "#30ce00" - type: SegmentedEntity - initialSegmentId: LamiaInitialSegment - segmentId: LamiaSegment numberOfSegments: 3 initialRadius: 0.25 staticScale: 0.9 From 96b605aa8d8ec3ef665bad73947f9ad83707186b Mon Sep 17 00:00:00 2001 From: fenndragon Date: Mon, 30 Dec 2024 10:55:22 -0700 Subject: [PATCH 25/25] f --- .../DeltaV/Entities/Mobs/Species/lamia.yml | 286 ++++++++++++++++++ .../DeltaV/Entities/Objects - Shortcut.lnk | Bin 0 -> 1593 bytes 2 files changed, 286 insertions(+) create mode 100644 Resources/Prototypes/DeltaV/Entities/Mobs/Species/lamia.yml create mode 100644 Resources/Prototypes/DeltaV/Entities/Objects - Shortcut.lnk diff --git a/Resources/Prototypes/DeltaV/Entities/Mobs/Species/lamia.yml b/Resources/Prototypes/DeltaV/Entities/Mobs/Species/lamia.yml new file mode 100644 index 00000000000..38601f21b54 --- /dev/null +++ b/Resources/Prototypes/DeltaV/Entities/Mobs/Species/lamia.yml @@ -0,0 +1,286 @@ +# Delta-V - This file is licensed under AGPLv3 +# Copyright (c) 2024 Delta-V Contributors +# See AGPLv3.txt for details. + +- type: entity + save: false + name: Lamia + parent: BaseMobHuman #parenting human so I can remove most of these components + id: MobLamiaBase + abstract: true + description: A miserable pile of scales. #TODO: Add a better description + components: + - type: Flashable + - type: Polymorphable + - type: Identity + - type: Hands + - type: HumanoidAppearance + species: Lamia + - type: MovementSpeedModifier + baseWalkSpeed : 4 + baseSprintSpeed : 6 + - type: MovedByPressure + - type: Hunger + - type: Thirst + - type: IdExaminable + - type: Inventory + speciesId: lamia + templateId: lamia + - type: HealthExaminable + examinableTypes: + - Blunt + - Slash + - Piercing + - Heat + - Shock + - type: Stamina + - type: Blindable + - type: Clickable + - type: InteractionOutline + - type: InteractionPopup + successChance: 0.5 + interactSuccessString: petting-success-lamia + interactFailureString: petting-failure-lamia + interactSuccessSpawn: EffectHearts + interactSuccessSound: + path: /Audio/Animals/lizard_happy.ogg #placeholder sound + interactFailureSound: + path: /Audio/Animals/snake_hiss.ogg #placeholder sound + - type: Icon + sprite: Mobs/Species/Human/parts.rsi + state: full + - type: Physics + bodyType: KinematicController + - type: Tag + tags: + - CanPilot + - DoorBumpOpener + - type: Sprite + netsync: false + noRot: true + drawdepth: Mobs + scale: 1, 1 + offset: 0, 0.4 + layers: #TODO: manually fix these layers + - map: [ "enum.HumanoidVisualLayers.Chest" ] + color: "#e8b59b" + sprite: Nyanotrasen/Mobs/Species/lamia.rsi + state: torso_m + - map: [ "enum.HumanoidVisualLayers.Head" ] + color: "#e8b59b" + sprite: Mobs/Species/Human/parts.rsi + state: head_m + - map: [ "enum.HumanoidVisualLayers.Eyes" ] + color: "#008800" + sprite: Mobs/Customization/eyes.rsi + state: eyes + - map: [ "enum.HumanoidVisualLayers.RArm" ] + color: "#e8b59b" + sprite: Mobs/Species/Human/parts.rsi + state: r_arm + - map: [ "enum.HumanoidVisualLayers.LArm" ] + color: "#e8b59b" + sprite: Mobs/Species/Human/parts.rsi + state: l_arm + - map: [ "enum.HumanoidVisualLayers.Tail" ] + - map: [ "jumpsuit" ] + - map: [ "enum.HumanoidVisualLayers.LHand" ] + color: "#e8b59b" + sprite: Mobs/Species/Human/parts.rsi + state: l_hand + - map: [ "enum.HumanoidVisualLayers.RHand" ] + color: "#e8b59b" + sprite: Mobs/Species/Human/parts.rsi + state: r_hand + - map: [ "enum.HumanoidVisualLayers.Handcuffs" ] + color: "#ffffff" + sprite: Objects/Misc/handcuffs.rsi + state: body-overlay-2 + visible: false + - map: [ "id" ] + - map: [ "gloves" ] + - map: [ "ears" ] + - map: [ "outerClothing" ] + - map: [ "eyes" ] + - map: [ "belt" ] + - map: [ "belt2" ] + - map: [ "neck" ] + - map: [ "back" ] + - map: [ "enum.HumanoidVisualLayers.Hair" ] + state: bald + sprite: Mobs/Customization/human_hair.rsi + - map: [ "mask" ] + - map: [ "head" ] + - map: [ "pocket1" ] + - map: [ "pocket2" ] + - map: [ "enum.HumanoidVisualLayers.HeadTop" ] + - type: Damageable + damageContainer: Biological + damageModifierSet: Scale #TODO: make a new damage modifier set + - type: NoSlip + - type: Internals + - type: MobState + - type: DamageVisuals + thresholds: [ 60, 120, 200 ] #these values aren't final, adjust accordingly with thresholds above' + targetLayers: + - "enum.HumanoidVisualLayers.Chest" + - "enum.HumanoidVisualLayers.Head" + - "enum.HumanoidVisualLayers.LArm" + - "enum.HumanoidVisualLayers.RArm" + damageOverlayGroups: + Brute: + sprite: Nyanotrasen/Mobs/Effects/Lamia/brute_damage.rsi + color: "#FF0000" + Burn: + sprite: Nyanotrasen/Mobs/Effects/Lamia/burn_damage.rsi + - type: MobThresholds + thresholds: + 0: Alive + 200: Critical #these values aren't final' + 300: Dead #these values aren't final' + - type: SlowOnDamage + speedModifierThresholds: #these values aren't final, adjust accordingly with thresholds above' + 60: 0.9 + 80: 0.8 + 100: 0.7 + 120: 0.6 + 140: 0.5 + - type: FireVisuals + sprite: Mobs/Effects/onfire.rsi + normalState: Generic_mob_burning + alternateState: Standing + fireStackAlternateState: 3 + - type: CombatMode + - type: Climbing + - type: Cuffable + - type: AnimationPlayer + - type: MeleeWeapon #This damage is most likely final + soundHit: + path: /Audio/Items/hypospray.ogg #this sound is not final, but is a pretty good placeholder so we might keep it + animation: WeaponArcBite + damage: + types: + Piercing: 1 + Poison: 2 + Asphyxiation: 2 + - type: SolutionContainerManager + solutions: + melee: + maxVol: 30 + - type: SolutionRegeneration + solution: melee + generated: + reagents: + - ReagentId: SpaceDrugs + Quantity: 1 + - type: MeleeChemicalInjector + solution: melee + transferAmount: 3 #amount to inject is not final + - type: Pullable + - type: DoAfter + - type: CreamPied + - type: Stripping + - type: Strippable + - type: Puller + - type: Fixtures + fixtures: # TODO: This needs a second fixture just for mob collisions. + fix1: + shape: + !type:PhysShapeCircle + radius: 0.35 + density: 1000 #Density is not final, adjust accordingly if the number of tail segments is reduced or increased + restitution: 0.0 + mask: + - MobMask + layer: + - MobLayer + - type: SegmentedEntity + numberOfSegments: 18 + texturePath: /Textures/Nyanotrasen/Mobs/Species/lamia.rsi/segment.png + - type: Speech + speechSounds: Alto + - type: Vocal + - type: Emoting + - type: Grammar + attributes: + proper: true + - type: StandingState + - type: Fingerprint + - type: Perishable + - type: Bloodstream + bloodMaxVolume: 60000 + bloodlossDamage: + types: + Bloodloss: 1 + bloodlossHealDamage: + types: + Bloodloss: -1 + - type: PortalExempt + +- type: entity + save: false + name: Lamia Dummy + parent: MobHumanDummy + id: MobLamiaDummy + description: A dummy lamia meant to be used in character setup. + components: + - type: Sprite + netsync: false + noRot: true + drawdepth: Mobs + scale: 1, 1 + layers: + - map: [ "enum.HumanoidVisualLayers.Chest" ] + color: "#e8b59b" + sprite: Nyanotrasen/Mobs/Species/lamia.rsi + state: torso_m + - map: [ "enum.HumanoidVisualLayers.Head" ] + color: "#e8b59b" + sprite: Mobs/Species/Human/parts.rsi + state: head_m + - map: [ "enum.HumanoidVisualLayers.Eyes" ] + color: "#008800" + sprite: Mobs/Customization/eyes.rsi + state: eyes + - map: [ "enum.HumanoidVisualLayers.RArm" ] + color: "#e8b59b" + sprite: Mobs/Species/Human/parts.rsi + state: r_arm + - map: [ "enum.HumanoidVisualLayers.LArm" ] + color: "#e8b59b" + sprite: Mobs/Species/Human/parts.rsi + state: l_arm + - map: [ "jumpsuit" ] + shader: StencilDraw + - map: [ "enum.HumanoidVisualLayers.LHand" ] + color: "#e8b59b" + sprite: Mobs/Species/Human/parts.rsi + state: l_hand + - map: [ "enum.HumanoidVisualLayers.RHand" ] + color: "#e8b59b" + sprite: Mobs/Species/Human/parts.rsi + state: r_hand + - map: [ "enum.HumanoidVisualLayers.Handcuffs" ] + color: "#ffffff" + sprite: Objects/Misc/handcuffs.rsi + state: body-overlay-2 + visible: false + - map: [ "id" ] + - map: [ "gloves" ] + - map: [ "ears" ] + - map: [ "outerClothing" ] + - map: [ "eyes" ] + - map: [ "belt" ] + - map: [ "neck" ] + - map: [ "back" ] + - map: [ "enum.HumanoidVisualLayers.Hair" ] + state: bald + sprite: Mobs/Customization/human_hair.rsi + - map: [ "mask" ] + - map: [ "head" ] + - map: [ "pocket1" ] + - map: [ "pocket2" ] + - map: [ "enum.HumanoidVisualLayers.HeadTop" ] + - map: [ "enum.HumanoidVisualLayers.Tail" ] + - type: Inventory + templateId: lamia diff --git a/Resources/Prototypes/DeltaV/Entities/Objects - Shortcut.lnk b/Resources/Prototypes/DeltaV/Entities/Objects - Shortcut.lnk new file mode 100644 index 0000000000000000000000000000000000000000..c3a792c9ca1b2581d437464c4396c19e07b76bfd GIT binary patch literal 1593 zcmcgsZAep57=C9;Gc2!VqGq~+z_4s1=wo1M(@n_Q(zLE!!8z}6b9%?_Rgb^!&?mW=b^}_o=O;a!S=ca8Z5_Ov81dLGalhlMGpwkLEGW zjNZZAo=`)MR(mTAb{X|t)Kq${1f3k!vl+b%7+r#3ujQKo4naJ`!t8J&+tQ ztQ%@d7AwzR>OL<{fDv3E;Li@VzymIaRHzFxOZOPC6SF8;C5O2!{&6m^Tp+5{f3$z%G4>LB-ZHX)M%i${K$W3tR0P!$0|Vw7CvTHP$X8H%Op&+6`UA{R z15RYLAv3Y2AkHB&5acd;?nO{Li`ax9PrUaf{G{V>lyA{g2(G3E9?;Mszj6`w;=(52 zmbQ!wRF?}CgbV3M`MGGKNMY51wB0B1Nv)O_>m|XxbC0vxrQ-z{Uu}5zXnOw0@TsEl z;rjeLCk!K5ZO>ajdfFv6A#8| z^*V&SdifPNgfY_7>$GL(b&eMn>yBm@l0Ak%qGPDD;Bq6v!dNax~T0(kof+T shQf!J-Nk*y`MKV_(;b^JUPlv(5r-$zwd1C~g09y)gqf*>