diff --git a/Content.IntegrationTests/Tests/_DV/DVDionaTest.cs b/Content.IntegrationTests/Tests/_DV/DVDionaTest.cs new file mode 100644 index 00000000000..989f9cfc310 --- /dev/null +++ b/Content.IntegrationTests/Tests/_DV/DVDionaTest.cs @@ -0,0 +1,412 @@ +#nullable enable +using System.Collections.Generic; +using System.Linq; +using Content.IntegrationTests.Fixtures; +using Content.IntegrationTests.Fixtures.Attributes; +using Content.Server.NPC; +using Content.Server.NPC.HTN; +using Content.Shared._DV.Diona; +using Content.Shared.Gibbing; +using Content.Shared.Humanoid; +using Content.Shared.Humanoid.Prototypes; +using Content.Shared.Mind; +using Content.Shared.Species; +using Robust.Shared.Enums; +using Robust.Shared.GameObjects; +using Robust.Shared.Map; +using Robust.Shared.Map.Components; +using Robust.Shared.Prototypes; + +namespace Content.IntegrationTests.Tests._DV; + +[TestFixture] +[TestOf(typeof(DVGestaltSystem))] +[TestOf(typeof(DVNymphingBodySystem))] +[TestOf(typeof(DVNymphRelationSystem))] +public sealed class DVDionaTest : GameTest +{ + private static readonly EntProtoId NymphPrototype = "DVMobDionaNymph"; + private static readonly EntProtoId ReformedPrototype = "MobDionaReformed"; + + private static readonly ProtoId Diona = "Diona"; + private static readonly Gender Gender = Gender.Epicene; + public static readonly Sex Sex = Sex.Unsexed; + public static readonly int Age = 72; + public static readonly float Height = 1f; + + [SidedDependency(Side.Server)] private readonly SharedMindSystem _mind = null!; + [SidedDependency(Side.Server)] private readonly DVNymphRelationSystem _relations = null!; + [SidedDependency(Side.Server)] private readonly GibbingSystem _gibbing = null!; + [SidedDependency(Side.Server)] private readonly MetaDataSystem _metadata = null!; + + [Test] + [RunOnSide(Side.Server)] + public void Assimilation() + { + using var cleanup = Cleanup(); + + var actor = SSpawn(NymphPrototype); + var mindlessTarget = SSpawn(NymphPrototype); + var mindedTarget = SSpawn(NymphPrototype); + + var actorMind = _mind.CreateMind(null); + _mind.TransferTo(actorMind, actor); + + var targetMind = _mind.CreateMind(null); + _mind.TransferTo(targetMind, mindedTarget); + + var assimilate = new DVAssimilateNymphActionEvent { Target = mindlessTarget }; + SEntMan.EventBus.RaiseLocalEvent(actor, assimilate); + Assert.That(assimilate.Handled, Is.True); + + var gestalt = GetSingleGestalt(); + var mapComp = SComp(gestalt.Comp.NymphStorageMap); + + using (Assert.EnterMultipleScope()) + { + Assert.That(gestalt.Comp.NymphCount, Is.EqualTo(2)); + Assert.That(gestalt.Comp.StoredNymphs, Is.EquivalentTo([actor, mindlessTarget])); + Assert.That(SComp(actor).StoredInGestalt, Is.EqualTo(gestalt.Owner)); + Assert.That(SComp(mindlessTarget).StoredInGestalt, Is.EqualTo(gestalt.Owner)); + Assert.That(SComp(actor).MapID, Is.EqualTo(mapComp.MapId)); + Assert.That(SComp(mindlessTarget).MapID, Is.EqualTo(mapComp.MapId)); + Assert.That(_mind.GetMind(gestalt), Is.EqualTo(actorMind.Owner)); + } + + var beforeCount = gestalt.Comp.NymphCount; + var beforeStored = gestalt.Comp.StoredNymphs.Count; + assimilate = new DVAssimilateNymphActionEvent { Target = mindedTarget }; + + SEntMan.EventBus.RaiseLocalEvent(gestalt, assimilate); + + using (Assert.EnterMultipleScope()) + { + Assert.That(assimilate.Handled, Is.False); + Assert.That(gestalt.Comp.NymphCount, Is.EqualTo(beforeCount)); + Assert.That(gestalt.Comp.StoredNymphs.Count, Is.EqualTo(beforeStored)); + Assert.That(gestalt.Comp.StoredNymphs, Is.EquivalentTo([actor, mindlessTarget])); + } + } + + [Test] + [RunOnSide(Side.Server)] + public void GestaltMembership() + { + using var cleanup = Cleanup(); + + var actor = SSpawn(NymphPrototype); + var target = SSpawn(NymphPrototype); + + var assimilate = new DVAssimilateNymphActionEvent { Target = target }; + SEntMan.EventBus.RaiseLocalEvent(actor, assimilate); + Assert.That(assimilate.Handled, Is.True); + + var gestalt = GetSingleGestalt(); + Assert.That(gestalt.Comp.NymphCount, Is.EqualTo(2)); + + SEntMan.RemoveComponent(target); + + using (Assert.EnterMultipleScope()) + { + Assert.That(gestalt.Comp.NymphCount, Is.EqualTo(1)); + Assert.That(gestalt.Comp.StoredNymphs, Is.EquivalentTo([actor])); + } + } + + [Test] + [RunOnSide(Side.Server)] + public void GestaltReformWithDifferentIdentities() + { + using var cleanup = Cleanup(); + + var first = SpawnProfiledNymph("Mismatched Rings"); + var second = SpawnProfiledNymph("Different Rings"); + var third = SpawnProfiledNymph("Mismatched Rings"); + + var assimilate = new DVAssimilateNymphActionEvent { Target = second }; + SEntMan.EventBus.RaiseLocalEvent(first, assimilate); + Assert.That(assimilate.Handled, Is.True); + + var gestalt = GetSingleGestalt(); + Assert.That(gestalt.Comp.NymphCount, Is.EqualTo(2)); + + assimilate = new DVAssimilateNymphActionEvent { Target = third }; + SEntMan.EventBus.RaiseLocalEvent(gestalt, assimilate); + Assert.That(assimilate.Handled, Is.True); + + var reform = new ReformSystem.ReformEvent(); + SEntMan.EventBus.RaiseLocalEvent(gestalt, reform); + Assert.That(reform.Handled, Is.True); + + var reformed = FindEntityByPrototype(ReformedPrototype); + Assert.That(reformed, Is.Not.Null); + + Assert.That(SComp(reformed.Value).EntityName, Is.Not.EqualTo("Mismatched Rings")); + Assert.That(SComp(reformed.Value).EntityName, Is.Not.EqualTo("Different Rings")); + } + + [Test] + [RunOnSide(Side.Server)] + public void GestaltReformWithSameIdentities() + { + using var cleanup = Cleanup(); + + var first = SpawnProfiledNymph("Identical Rings"); + var second = SpawnProfiledNymph("Identical Rings"); + var third = SpawnProfiledNymph("Identical Rings"); + + var firstMind = _mind.CreateMind(null); + _mind.TransferTo(firstMind, first); + + var assimilate = new DVAssimilateNymphActionEvent { Target = second }; + SEntMan.EventBus.RaiseLocalEvent(first, assimilate); + Assert.That(assimilate.Handled, Is.True); + + var gestalt = GetSingleGestalt(); + Assert.That(gestalt.Comp.NymphCount, Is.EqualTo(2)); + + assimilate = new DVAssimilateNymphActionEvent { Target = third }; + SEntMan.EventBus.RaiseLocalEvent(gestalt, assimilate); + Assert.That(assimilate.Handled, Is.True); + + var reform = new ReformSystem.ReformEvent(); + SEntMan.EventBus.RaiseLocalEvent(gestalt, reform); + Assert.That(reform.Handled, Is.True); + + var reformed = FindEntityByPrototype(ReformedPrototype); + Assert.That(reformed, Is.Not.Null); + + var profile = SComp(reformed.Value); + + Assert.Multiple(() => + { + Assert.That(_mind.GetMind(reformed.Value), Is.EqualTo(firstMind.Owner)); + Assert.That(SComp(reformed.Value).EntityName, Is.EqualTo("Identical Rings")); + Assert.That(profile.Species, Is.EqualTo(Diona)); + Assert.That(profile.Gender, Is.EqualTo(Gender)); + Assert.That(profile.Sex, Is.EqualTo(Sex)); + Assert.That(profile.Age, Is.EqualTo(Age)); + Assert.That(profile.Height, Is.EqualTo(Height)); + }); + } + + [Test] + [RunOnSide(Side.Server)] + public void GestaltMapPreservation([Range(1, 4)] int additionalNymphCount) + { + using var cleanup = Cleanup(); + + var first = SpawnProfiledNymph("Identical Rings"); + var others = new List(); + for (var i = 0; i < additionalNymphCount; i++) + { + others.Add(SpawnProfiledNymph("Identical Rings")); + } + + var second = SpawnProfiledNymph("Identical Rings"); + + var assimilate = new DVAssimilateNymphActionEvent { Target = second }; + SEntMan.EventBus.RaiseLocalEvent(first, assimilate); + Assert.That(assimilate.Handled, Is.True); + + var gestalt = GetSingleGestalt(); + + foreach (var other in others) + { + assimilate = new DVAssimilateNymphActionEvent { Target = other }; + SEntMan.EventBus.RaiseLocalEvent(gestalt, assimilate); + Assert.That(assimilate.Handled, Is.True); + } + + Assert.That(gestalt.Comp.NymphCount, Is.EqualTo(2 + additionalNymphCount)); + + var gestaltMap = SComp(gestalt.Comp.NymphStorageMap); + + foreach (var other in others) + { + Assert.That(SComp(other).MapID, Is.EqualTo(gestaltMap.MapId)); + } + + var reform = new ReformSystem.ReformEvent(); + SEntMan.EventBus.RaiseLocalEvent(gestalt, reform); + Assert.That(reform.Handled, Is.True); + + var reformed = FindEntityByPrototype(ReformedPrototype); + Assert.That(reformed, Is.Not.Null); + + var reformedGestalt = SComp(reformed.Value); + Assert.That(reformedGestalt.NymphCount, Is.EqualTo(2 + additionalNymphCount)); + + var reformedGestaltMap = SComp(reformedGestalt.NymphStorageMap); + + foreach (var other in others) + { + Assert.That(SComp(other).MapID, Is.EqualTo(reformedGestaltMap.MapId)); + } + } + + + [Test] + [RunOnSide(Side.Server)] + public void Relations() + { + using var cleanup = Cleanup(); + + var leader = SSpawn(NymphPrototype); + var follower = SSpawn(NymphPrototype); + + _relations.Follow( + (leader, SComp(leader)), + (follower, SComp(follower))); + + var leaderComp = SComp(leader); + var followerComp = SComp(follower); + var htn = SComp(follower); + + using (Assert.EnterMultipleScope()) + { + Assert.That(leaderComp.Followers, Does.Contain(follower)); + Assert.That(followerComp.Lead, Is.EqualTo(leader)); + Assert.That(htn.Blackboard.TryGetValue(NPCBlackboard.FollowTarget, out var coords, SEntMan), Is.True); + Assert.That(coords!.EntityId, Is.EqualTo(leader)); + } + + SEntMan.RemoveComponent(leader); + + using (Assert.EnterMultipleScope()) + { + Assert.That(followerComp.Lead, Is.Null); + Assert.That(htn.Blackboard.TryGetValue(NPCBlackboard.FollowTarget, out _, SEntMan), Is.False); + } + } + + [Test] + [RunOnSide(Side.Server)] + public void GibRelations() + { + using var cleanup = Cleanup(); + + var body = SSpawn("MobDiona"); + _metadata.SetEntityName(body, "Remembered Rings"); + + var bodyMind = _mind.CreateMind(null); + _mind.TransferTo(bodyMind, body); + + var giblets = _gibbing.Gib(body); + Assert.That(giblets.Count, Is.GreaterThanOrEqualTo(3)); + + var nymphs = giblets + .Where(SEntMan.EntityExists) + .Where(SEntMan.HasComponent) + .ToList(); + + Assert.That(nymphs.Count, Is.EqualTo(3)); + + var lead = nymphs.Single(uid => _mind.GetMind(uid) == bodyMind); + var leadComp = SComp(lead); + var memory = SComp(lead); + + using (Assert.EnterMultipleScope()) + { + Assert.That(memory.Mind, Is.EqualTo(bodyMind.Owner)); + Assert.That(leadComp.Followers, Is.EquivalentTo(nymphs)); + } + + foreach (var nymph in nymphs) + { + var profile = SComp(nymph); + var follower = SComp(nymph); + + using (Assert.EnterMultipleScope()) + { + Assert.That(profile.Name, Is.EqualTo("Remembered Rings")); + Assert.That(follower.Lead, Is.EqualTo(lead)); + } + } + } + + private EntityUid SpawnProfiledNymph(string name) + { + var uid = SSpawn(NymphPrototype); + var profile = SComp(uid); + + profile.Name = name; + profile.Species = Diona; + profile.Gender = Gender; + profile.Sex = Sex; + profile.Age = Age; + profile.Height = Height; + + return uid; + } + + private sealed class ActionDisposable(Action action) : IDisposable + { + public void Dispose() + { + action.Invoke(); + } + } + + private ActionDisposable Cleanup() + { + return new ActionDisposable(() => + { + var gestalts = SEntMan.EntityQueryEnumerator(); + + while (gestalts.MoveNext(out var uid, out _)) + { + if (SEntMan.Deleted(uid)) + continue; + + SEntMan.QueueDeleteEntity(uid); + } + + var nymphs = SEntMan.EntityQueryEnumerator(); + + while (nymphs.MoveNext(out var uid, out _)) + { + if (SEntMan.Deleted(uid)) + continue; + + SEntMan.QueueDeleteEntity(uid); + } + }); + } + + private Entity GetSingleGestalt() + { + EntityUid foundUid = default; + DVGestaltComponent? foundComp = null; + var count = 0; + var query = SEntMan.EntityQueryEnumerator(); + + while (query.MoveNext(out var uid, out var comp)) + { + if (SEntMan.Deleted(uid)) + continue; + + foundUid = uid; + foundComp = comp; + count++; + } + + Assert.That(count, Is.EqualTo(1)); + return (foundUid, foundComp!); + } + + private EntityUid? FindEntityByPrototype(EntProtoId prototype) + { + var query = SEntMan.EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var meta)) + { + if (SEntMan.Deleted(uid)) + continue; + + if (meta.EntityPrototype?.ID == prototype.Id) + return uid; + } + + return null; + } +} diff --git a/Content.Server/_DV/Diona/DVNymphNPCSystem.cs b/Content.Server/_DV/Diona/DVNymphNPCSystem.cs new file mode 100644 index 00000000000..0c15e13183a --- /dev/null +++ b/Content.Server/_DV/Diona/DVNymphNPCSystem.cs @@ -0,0 +1,30 @@ +using System.Numerics; +using Content.Server.NPC; +using Content.Server.NPC.HTN; +using Content.Server.NPC.Systems; +using Content.Shared._DV.Diona; +using Robust.Shared.Map; + +namespace Content.Server._DV.Diona; + +public sealed class DVNymphNPCSystem : EntitySystem +{ + [Dependency] private readonly NPCSystem _npc = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnLeadGotChanged); + } + + private void OnLeadGotChanged(Entity ent, ref DVNymphFollowerLeadGotChangedEvent args) + { + if (ent.Comp.Lead is { } leader) + _npc.SetBlackboard(ent, NPCBlackboard.FollowTarget, new EntityCoordinates(leader, Vector2.Zero)); + else if (TryComp(ent, out var htn)) + { + htn.Blackboard.Remove(NPCBlackboard.FollowTarget); + } + } +} diff --git a/Content.Server/_DV/Diona/DVNymphingOrganSystem.cs b/Content.Server/_DV/Diona/DVNymphingOrganSystem.cs new file mode 100644 index 00000000000..a4912515a45 --- /dev/null +++ b/Content.Server/_DV/Diona/DVNymphingOrganSystem.cs @@ -0,0 +1,112 @@ +using Content.Server.Mind; +using Content.Server.Zombies; +using Content.Shared._DV.Diona; +using Content.Shared.Actions; +using Content.Shared.Actions.Components; +using Content.Shared.Body; +using Content.Shared.Gibbing; +using Content.Shared.Humanoid; +using Content.Shared.Mobs; +using Content.Shared.Mobs.Components; +using Content.Shared.NameIdentifier; +using Content.Shared.NameModifier.EntitySystems; +using Content.Shared.Species.Components; +using Content.Shared.Zombies; +using Robust.Shared.Prototypes; + +namespace Content.Server._DV.Diona; + +public sealed class DVNymphingOrganSystem : EntitySystem +{ + private static readonly EntProtoId AssimilateAction = "DVDionaAssimilateAction"; + private static readonly TimeSpan AliveGibAssimilateCooldown = TimeSpan.FromSeconds(30); + private static readonly TimeSpan DeadGibAssimilateCooldown = TimeSpan.FromMinutes(10); + + [Dependency] private readonly IPrototypeManager _protoManager = default!; + [Dependency] private readonly SharedActionsSystem _actions = default!; + [Dependency] private readonly MindSystem _mindSystem = default!; + [Dependency] private readonly ZombieSystem _zombie = default!; + [Dependency] private readonly SharedVisualBodySystem _visualBody = default!; + [Dependency] private readonly NameModifierSystem _nameModifier = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent>(OnBeingGibbed); + SubscribeLocalEvent(OnRefreshNameModifiers); + } + + private void OnBeingGibbed(Entity ent, ref BodyRelayedEvent args) + { + if (TerminatingOrDeleted(ent)) + return; + + if (!_protoManager.TryIndex(ent.Comp.EntityPrototype, out var entityProto)) + return; + + // Get the organs' position & spawn a nymph there + var coords = Transform(ent).Coordinates; + var nymph = SpawnAtPosition(entityProto.ID, coords); + SetAssimilateCooldown(nymph, GetAssimilateCooldown(args.Body)); + + if (HasComp(args.Body)) // Zombify the new nymph if old one is a zombie + _zombie.ZombifyEntity(nymph); + + // Move the mind if there is one and it's supposed to be transferred + if (ent.Comp.TransferMind && _mindSystem.TryGetMind(args.Body, out var mindId, out var mind)) + _mindSystem.TransferTo(mindId, nymph, true, mind: mind); + + if (TryComp(nymph, out var nymphProfile)) + { + nymphProfile.Name = Name(args.Body); + + if (TryComp(args.Body, out var bodyProfile)) + { + nymphProfile.Species = bodyProfile.Species; + nymphProfile.Gender = bodyProfile.Gender; + nymphProfile.Sex = bodyProfile.Sex; + nymphProfile.Age = bodyProfile.Age; + nymphProfile.Height = bodyProfile.Height; + } + + if (_visualBody.TryGatherMarkingsData(args.Body.Owner, null, out var profiles, out _, out var applied)) + { + nymphProfile.OrganMarkings = applied; + nymphProfile.OrganProfiles = profiles; + } + + Dirty(nymph, nymphProfile); + _nameModifier.RefreshNameModifiers(nymph); + } + + // Delete the old organ + QueueDel(ent); + args.Args.Giblets.Add(nymph); + } + + private TimeSpan GetAssimilateCooldown(EntityUid body) + { + return TryComp(body, out var mobState) && mobState.CurrentState == MobState.Alive + ? AliveGibAssimilateCooldown + : DeadGibAssimilateCooldown; + } + + private void SetAssimilateCooldown(EntityUid nymph, TimeSpan cooldown) + { + foreach (var action in _actions.GetActions(nymph)) + { + if (Prototype(action.Owner)?.ID != AssimilateAction.Id) + continue; + + _actions.SetCooldown((action.Owner, action.Comp), cooldown); + return; + } + } + + private void OnRefreshNameModifiers(Entity ent, ref RefreshNameModifiersEvent args) + { + if (ent.Comp.Name is { } name) + args.AddModifier("nymph-name-prefix", 0, ("identityName", name)); + } +} diff --git a/Content.Shared/Body/GibbableOrganComponent.cs b/Content.Shared/Body/GibbableOrganComponent.cs index 0ac2025facb..d5d12f12ef4 100644 --- a/Content.Shared/Body/GibbableOrganComponent.cs +++ b/Content.Shared/Body/GibbableOrganComponent.cs @@ -9,4 +9,11 @@ namespace Content.Shared.Body; /// [RegisterComponent, NetworkedComponent] [Access(typeof(GibbableOrganSystem))] -public sealed partial class GibbableOrganComponent : Component; +public sealed partial class GibbableOrganComponent : Component +{ + /// + /// DeltaV - whether the organ will become a giblet + /// + [DataField] + public bool Active = true; +} diff --git a/Content.Shared/Body/GibbableOrganSystem.cs b/Content.Shared/Body/GibbableOrganSystem.cs index 56b0af11c19..eafd16658db 100644 --- a/Content.Shared/Body/GibbableOrganSystem.cs +++ b/Content.Shared/Body/GibbableOrganSystem.cs @@ -13,6 +13,9 @@ public sealed class GibbableOrganSystem : EntitySystem private void OnBeingGibbed(Entity ent, ref BodyRelayedEvent args) { - args.Args.Giblets.Add(ent); + // Begin DeltaV - gibbable activation + if (ent.Comp.Active) + args.Args.Giblets.Add(ent); + // End DeltaV - gibbable activation } } diff --git a/Content.Shared/_DV/Diona/DVAssimilateNymphActionEvent.cs b/Content.Shared/_DV/Diona/DVAssimilateNymphActionEvent.cs new file mode 100644 index 00000000000..b8cc279cd7c --- /dev/null +++ b/Content.Shared/_DV/Diona/DVAssimilateNymphActionEvent.cs @@ -0,0 +1,5 @@ +using Content.Shared.Actions; + +namespace Content.Shared._DV.Diona; + +public sealed partial class DVAssimilateNymphActionEvent : EntityTargetActionEvent; diff --git a/Content.Shared/_DV/Diona/DVGestaltComponent.cs b/Content.Shared/_DV/Diona/DVGestaltComponent.cs new file mode 100644 index 00000000000..adae5cbee1b --- /dev/null +++ b/Content.Shared/_DV/Diona/DVGestaltComponent.cs @@ -0,0 +1,26 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared._DV.Diona; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +[Access(typeof(DVGestaltSystem))] +public sealed partial class DVGestaltComponent : Component +{ + [DataField(serverOnly: true)] + public EntityUid NymphStorageMap; + + [DataField(serverOnly: true)] + public HashSet StoredNymphs = new(); + + [DataField, AutoNetworkedField] + public int NymphCount; + + [DataField] + public int RequiredNymphs = 3; + + /// + /// The text that appears when attempting to split. + /// + [DataField] + public LocId PopupText = "diona-gib-action-use"; +} diff --git a/Content.Shared/_DV/Diona/DVGestaltMemberComponent.cs b/Content.Shared/_DV/Diona/DVGestaltMemberComponent.cs new file mode 100644 index 00000000000..12a9162a5d7 --- /dev/null +++ b/Content.Shared/_DV/Diona/DVGestaltMemberComponent.cs @@ -0,0 +1,10 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared._DV.Diona; + +[RegisterComponent, NetworkedComponent] +public sealed partial class DVGestaltMemberComponent : Component +{ + [DataField] + public EntityUid? StoredInGestalt; +} diff --git a/Content.Shared/_DV/Diona/DVGestaltSystem.cs b/Content.Shared/_DV/Diona/DVGestaltSystem.cs new file mode 100644 index 00000000000..ba4a30cbaf7 --- /dev/null +++ b/Content.Shared/_DV/Diona/DVGestaltSystem.cs @@ -0,0 +1,227 @@ +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Numerics; +using Content.Shared.Body; +using Content.Shared.Gibbing; +using Content.Shared.Humanoid; +using Content.Shared.Mind; +using Content.Shared.Popups; +using Content.Shared.Preferences; +using Content.Shared.Species; +using Robust.Shared.Map; +using Robust.Shared.Map.Components; +using Robust.Shared.Network; +using Robust.Shared.Prototypes; + +namespace Content.Shared._DV.Diona; + +public sealed class DVGestaltSystem : EntitySystem +{ + [Dependency] private readonly SharedMapSystem _map = default!; + [Dependency] private readonly INetManager _net = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly GibbingSystem _gibbing = default!; + [Dependency] private readonly SharedMindSystem _mind = default!; + [Dependency] private readonly MetaDataSystem _metadata = default!; + [Dependency] private readonly HumanoidProfileSystem _profile = default!; + [Dependency] private readonly SharedVisualBodySystem _visualBody = default!; + + private static readonly EntProtoId GestaltPrototype = "DVMobDionaGestalt"; + private static readonly EntProtoId ReformedPrototype = "MobDionaReformed"; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnNymphAssimilate); + + SubscribeLocalEvent(OnGestaltInit); + SubscribeLocalEvent(OnGestaltShutdown); + SubscribeLocalEvent(OnGestaltMemberShutdown); + SubscribeLocalEvent(OnGestaltAssimilate); + SubscribeLocalEvent(OnGestaltGib); + SubscribeLocalEvent(OnGestaltBeingGibbed); + SubscribeLocalEvent(OnGestaltGibbedBeforeDeletion); + SubscribeLocalEvent(OnGestaltReforming); + } + + private void OnGestaltInit(Entity ent, ref MapInitEvent args) + { + var map = _map.CreateMap(runMapInit: false); + ent.Comp.NymphStorageMap = map; + _metadata.SetEntityName(map, $"Diona Gestalt Storage {ToPrettyString(ent)}"); + Dirty(ent); + } + + private void OnGestaltMemberShutdown(Entity ent, ref ComponentShutdown args) + { + if (!TryComp(ent.Comp.StoredInGestalt, out var gestalt)) + return; + + gestalt.StoredNymphs.Remove(ent); + gestalt.NymphCount--; + Dirty(ent.Comp.StoredInGestalt.Value, gestalt); + } + + private void OnGestaltShutdown(Entity ent, ref ComponentShutdown args) + { + if (TryComp(ent.Comp.NymphStorageMap, out var map)) + _map.QueueDeleteMap(map.MapId); + } + + private void OnGestaltAssimilate(Entity ent, ref DVAssimilateNymphActionEvent args) + { + if (!_net.IsServer) + return; + + if (_mind.TryGetMind(args.Target, out _, out _)) + return; + + Assimilate(ent, args.Target); + args.Handled = true; + } + + private void OnNymphAssimilate(Entity ent, ref DVAssimilateNymphActionEvent args) + { + if (!_net.IsServer) + return; + + if (_mind.TryGetMind(args.Target, out _, out _)) + return; + + var gestalt = SpawnAtPosition(GestaltPrototype, Transform(ent).Coordinates); + var gestaltComp = Comp(gestalt); + Assimilate((gestalt, gestaltComp), ent.Owner); + Assimilate((gestalt, gestaltComp), args.Target); + + if (_mind.TryGetMind(ent, out var mindId, out var mind)) + _mind.TransferTo(mindId, gestalt, true, mind: mind); + + args.Handled = true; + } + + private void Assimilate(Entity gestalt, Entity nymph) + { + if (!Resolve(nymph, ref nymph.Comp) || !TryComp(gestalt.Comp.NymphStorageMap, out var map)) + return; + + _transform.SetMapCoordinates(nymph, new MapCoordinates(Vector2.Zero, map.MapId)); + gestalt.Comp.StoredNymphs.Add(nymph); + gestalt.Comp.NymphCount++; + nymph.Comp.StoredInGestalt = gestalt; + Dirty(nymph, nymph.Comp); + Dirty(gestalt); + } + + private void OnGestaltGib(Entity ent, ref GibActionSystem.GibActionEvent args) + { + _popup.PopupPredicted(Loc.GetString(ent.Comp.PopupText, ("name", ent)), ent, ent); + _gibbing.Gib(ent, user: args.Performer); + } + + private void OnGestaltBeingGibbed(Entity ent, ref BeingGibbedEvent args) + { + args.Giblets.UnionWith(ent.Comp.StoredNymphs); + } + + private void OnGestaltGibbedBeforeDeletion(Entity ent, ref GibbedBeforeDeletionEvent args) + { + foreach (var giblet in args.Giblets) + { + if (!TryComp(giblet, out var memory)) + continue; + + if (!Exists(memory.Mind) || !TryComp(memory.Mind, out var mindComponent)) + continue; + + _mind.TransferTo(memory.Mind.Value, giblet, true, mind: mindComponent); + } + } + + private bool DetermineProfile( + Entity ent, + [NotNullWhen(true)] out DVNymphProfileComponent? profile) + { + profile = null; + var nymphs = ent.Comp.StoredNymphs.ToList(); + if (nymphs.Count == 0) + return false; + + if (nymphs.Count == 1) + { + profile = Comp(nymphs[0]); + return true; + } + + var headProfile = Comp(nymphs[0]); + foreach (var nymph in nymphs.Skip(1)) + { + var nymphProfile = Comp(nymph); + + if (nymphProfile.Name != headProfile.Name + || nymphProfile.Species != headProfile.Species + || nymphProfile.Gender != headProfile.Gender + || nymphProfile.Sex != headProfile.Sex + || nymphProfile.Age != headProfile.Age + // ReSharper disable once CompareOfFloatsByEqualityOperator + || nymphProfile.Height != headProfile.Height) + { + return false; + } + } + + profile = headProfile; + return true; + } + + private void OnGestaltReforming(Entity ent, ref ReformSystem.ReformEvent args) + { + if (ent.Comp.NymphCount < ent.Comp.RequiredNymphs) + return; + + args.Handled = true; + if (!_net.IsServer) + return; + + var child = SpawnNextToOrDrop(ReformedPrototype, ent); + if (_mind.TryGetMind(ent, out var mindId, out var mind)) + _mind.TransferTo(mindId, child, mind: mind); + + if (DetermineProfile(ent, out var profile)) + { + if (profile.Name is { } name) + _metadata.SetEntityName(child, name); + + _profile.ApplyProfileTo(child, + new HumanoidCharacterProfile() + .WithSpecies(profile.Species) + .WithAge(profile.Age) + .WithSex(profile.Sex) + .WithHeight(profile.Height) + .WithGender(profile.Gender) + .WithHeight(profile.Height)); + + if (profile.OrganProfiles is { } profiles) + _visualBody.ApplyProfiles(child, profiles); + + if (profile.OrganMarkings is { } markings) + _visualBody.ApplyMarkings(child, markings); + } + + var newComp = CopyComp(ent, child, ent.Comp); + newComp.StoredNymphs.Clear(); + newComp.StoredNymphs.UnionWith(ent.Comp.StoredNymphs); + var newMap = Comp(newComp.NymphStorageMap); + foreach (var nymph in ent.Comp.StoredNymphs) + { + var gestaltMember = Comp(nymph); + gestaltMember.StoredInGestalt = child; + _transform.SetMapCoordinates(nymph, new MapCoordinates(Vector2.Zero, newMap.MapId)); + } + ent.Comp.StoredNymphs.Clear(); + _metadata.SetEntityName(ent.Comp.NymphStorageMap, $"Diona Gestalt Storage {ToPrettyString(ent)}"); + + QueueDel(ent); + } +} diff --git a/Content.Shared/_DV/Diona/DVNymphFollowerComponent.cs b/Content.Shared/_DV/Diona/DVNymphFollowerComponent.cs new file mode 100644 index 00000000000..1922cfb23a0 --- /dev/null +++ b/Content.Shared/_DV/Diona/DVNymphFollowerComponent.cs @@ -0,0 +1,14 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared._DV.Diona; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +[Access(typeof(DVNymphRelationSystem))] +public sealed partial class DVNymphFollowerComponent : Component +{ + [DataField, AutoNetworkedField] + public EntityUid? Lead; +} + +[ByRefEvent] +public readonly record struct DVNymphFollowerLeadGotChangedEvent; diff --git a/Content.Shared/_DV/Diona/DVNymphLeadComponent.cs b/Content.Shared/_DV/Diona/DVNymphLeadComponent.cs new file mode 100644 index 00000000000..e0b217ea813 --- /dev/null +++ b/Content.Shared/_DV/Diona/DVNymphLeadComponent.cs @@ -0,0 +1,11 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared._DV.Diona; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +[Access(typeof(DVNymphRelationSystem))] +public sealed partial class DVNymphLeadComponent : Component +{ + [DataField, AutoNetworkedField] + public HashSet Followers = new(); +} diff --git a/Content.Shared/_DV/Diona/DVNymphMindMemoryComponent.cs b/Content.Shared/_DV/Diona/DVNymphMindMemoryComponent.cs new file mode 100644 index 00000000000..3633bdf942d --- /dev/null +++ b/Content.Shared/_DV/Diona/DVNymphMindMemoryComponent.cs @@ -0,0 +1,10 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared._DV.Diona; + +[RegisterComponent, NetworkedComponent] +public sealed partial class DVNymphMindMemoryComponent : Component +{ + [DataField(serverOnly: true)] + public EntityUid? Mind; +} diff --git a/Content.Shared/_DV/Diona/DVNymphProfileComponent.cs b/Content.Shared/_DV/Diona/DVNymphProfileComponent.cs new file mode 100644 index 00000000000..092e00a8685 --- /dev/null +++ b/Content.Shared/_DV/Diona/DVNymphProfileComponent.cs @@ -0,0 +1,37 @@ +using Content.Shared.Body; +using Content.Shared.Humanoid; +using Content.Shared.Humanoid.Markings; +using Content.Shared.Humanoid.Prototypes; +using Robust.Shared.Enums; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared._DV.Diona; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class DVNymphProfileComponent : Component +{ + [DataField, AutoNetworkedField] + public Dictionary, OrganProfileData>? OrganProfiles; + + [DataField, AutoNetworkedField] + public Dictionary, Dictionary>>? OrganMarkings; + + [DataField, AutoNetworkedField] + public string? Name; + + [DataField, AutoNetworkedField] + public ProtoId Species = "Diona"; + + [DataField, AutoNetworkedField] + public Gender Gender; + + [DataField, AutoNetworkedField] + public Sex Sex; + + [DataField, AutoNetworkedField] + public int Age = 18; + + [DataField, AutoNetworkedField] + public float Height = 1f; +} diff --git a/Content.Shared/_DV/Diona/DVNymphRelationsSystem.cs b/Content.Shared/_DV/Diona/DVNymphRelationsSystem.cs new file mode 100644 index 00000000000..4e0d40d89c4 --- /dev/null +++ b/Content.Shared/_DV/Diona/DVNymphRelationsSystem.cs @@ -0,0 +1,54 @@ +using JetBrains.Annotations; + +namespace Content.Shared._DV.Diona; + +public sealed class DVNymphRelationSystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnLeadShutdown); + SubscribeLocalEvent(OnFollowerShutdown); + } + + private void OnFollowerShutdown(Entity ent, ref ComponentShutdown args) + { + if (ent.Comp.Lead is not { } nymph || !TryComp(nymph, out var lead)) + return; + + lead.Followers.Remove(ent); + Dirty(nymph, lead); + } + + private void OnLeadShutdown(Entity ent, ref ComponentShutdown args) + { + foreach (var nymph in ent.Comp.Followers) + { + if (!TryComp(nymph, out var follower)) + continue; + + follower.Lead = null; + Dirty(nymph, follower); + + var evt = new DVNymphFollowerLeadGotChangedEvent(); + RaiseLocalEvent(nymph, ref evt); + } + } + + [PublicAPI] + public void Follow(Entity leader, Entity follower) + { + if (!Resolve(leader, ref leader.Comp) || !Resolve(follower, ref follower.Comp)) + return; + + leader.Comp.Followers.Add(follower); + follower.Comp.Lead = leader; + + var evt = new DVNymphFollowerLeadGotChangedEvent(); + RaiseLocalEvent(follower, ref evt); + + Dirty(leader, leader.Comp); + Dirty(follower, follower.Comp); + } +} diff --git a/Content.Shared/_DV/Diona/DVNymphingBodyComponent.cs b/Content.Shared/_DV/Diona/DVNymphingBodyComponent.cs new file mode 100644 index 00000000000..e7020869917 --- /dev/null +++ b/Content.Shared/_DV/Diona/DVNymphingBodyComponent.cs @@ -0,0 +1,13 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared._DV.Diona; + +[RegisterComponent, NetworkedComponent] +public sealed partial class DVNymphingBodyComponent : Component +{ + /// + /// The text that appears when attempting to split. + /// + [DataField] + public LocId PopupText = "diona-gib-action-use"; +} diff --git a/Content.Shared/_DV/Diona/DVNymphingBodySystem.cs b/Content.Shared/_DV/Diona/DVNymphingBodySystem.cs new file mode 100644 index 00000000000..8d7dcbc5ffd --- /dev/null +++ b/Content.Shared/_DV/Diona/DVNymphingBodySystem.cs @@ -0,0 +1,60 @@ +using Content.Shared.Gibbing; +using Content.Shared.Mind; +using Content.Shared.Popups; +using Content.Shared.Species; + +namespace Content.Shared._DV.Diona; + +public sealed class DVNymphingBodySystem : EntitySystem +{ + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly GibbingSystem _gibbing = default!; + [Dependency] private readonly SharedMindSystem _mind = default!; + [Dependency] private readonly DVNymphRelationSystem _nymph = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnNymphingBodyGib); + SubscribeLocalEvent(OnGibbedBeforeDeletion); + } + + private void OnNymphingBodyGib(Entity ent, ref GibActionSystem.GibActionEvent args) + { + _popup.PopupPredicted(Loc.GetString(ent.Comp.PopupText, ("name", ent)), ent, ent); + _gibbing.Gib(ent, user: args.Performer); + } + + private void OnGibbedBeforeDeletion(Entity ent, ref GibbedBeforeDeletionEvent args) + { + EntityUid? leadGiblet = null; + var leadMind = EntityUid.Invalid; + + foreach (var giblet in args.Giblets) + { + if (!_mind.TryGetMind(giblet, out leadMind, out _)) + continue; + + leadGiblet = giblet; + break; + } + + if (leadGiblet is not { } leader || !TryComp(leader, out var leaderComp)) + return; + + if (TryComp(leader, out var memory) && Exists(leadMind)) + { + memory.Mind = leadMind; + } + + foreach (var giblet in args.Giblets) + { + if (!TryComp(giblet, out var follower)) + continue; + + _nymph.Follow((leader, leaderComp), (giblet, follower)); + } + } + +} diff --git a/Content.Shared/_DV/Diona/DVNymphingOrganComponent.cs b/Content.Shared/_DV/Diona/DVNymphingOrganComponent.cs new file mode 100644 index 00000000000..5c5b5e3bb7b --- /dev/null +++ b/Content.Shared/_DV/Diona/DVNymphingOrganComponent.cs @@ -0,0 +1,23 @@ +using Robust.Shared.Prototypes; +using Robust.Shared.GameStates; + +namespace Content.Shared._DV.Diona; + +/// +/// Component that will cause an organ to turn into a nymph when removed from its body. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class DVNymphingOrganComponent : Component +{ + /// + /// The entity to replace the organ with. + /// + [DataField(required: true)] + public EntProtoId EntityPrototype; + + /// + /// Whether to transfer the mind to this new entity. + /// + [DataField] + public bool TransferMind; +} diff --git a/Resources/Locale/en-US/_DV/armor/diona/names.ftl b/Resources/Locale/en-US/_DV/armor/diona/names.ftl new file mode 100644 index 00000000000..d2afd694568 --- /dev/null +++ b/Resources/Locale/en-US/_DV/armor/diona/names.ftl @@ -0,0 +1 @@ +nymph-name-prefix = {$baseName} ({$identityName}) diff --git a/Resources/Prototypes/Body/Species/diona.yml b/Resources/Prototypes/Body/Species/diona.yml index fb7f7aaad73..5373171ee94 100644 --- a/Resources/Prototypes/Body/Species/diona.yml +++ b/Resources/Prototypes/Body/Species/diona.yml @@ -165,10 +165,18 @@ - type: IgniteOnHeatDamage fireStacks: 1 threshold: 12 - - type: GibAction - actionPrototype: DionaGibAction - allowedStates: - - Dead + # Begin DeltaV Changes - gib at will + # - type: GibAction + # actionPrototype: DionaGibAction + # allowedStates: + # - Alive + # - Critical + # - Dead + - type: DVNymphingBody + - type: ActionGrant + actions: + - DVDionaGibAction + # End DeltaV Changes - gib at will - type: Rootable - type: MovementSpeedModifier # DeltaV baseWalkSpeed: 1.0 @@ -284,11 +292,20 @@ - type: entity parent: [ OrganBaseBrain, OrganDionaInternal ] id: OrganDionaBrain + # Begin DeltaV - diona nymphing overhaul + components: + - type: GibbableOrgan + active: false + # End DeltaV - diona nymphing overhaul - type: entity parent: [ OrganDionaVisual, OrganBaseEyes, OrganDionaInternal ] id: OrganDionaEyes components: + # Begin DeltaV - diona nymphing overhaul + - type: GibbableOrgan + active: false + # End DeltaV - diona nymphing overhaul - type: VisualOrgan data: sprite: Mobs/Customization/eyes.rsi @@ -298,6 +315,10 @@ parent: [ OrganBaseLungs, OrganDionaInternal, OrganDionaMetabolizer ] id: OrganDionaLungs components: + # Begin DeltaV - diona nymphing overhaul + - type: GibbableOrgan + active: false + # End DeltaV - diona nymphing overhaul - type: Sprite layers: - state: lungs @@ -306,6 +327,10 @@ parent: [ OrganBaseStomach, OrganDionaInternal, OrganDionaMetabolizer ] id: OrganDionaStomach components: + # Begin DeltaV - diona nymphing overhaul + - type: GibbableOrgan + active: false + # End DeltaV - diona nymphing overhaul - type: Metabolizer maxReagents: 6 stages: [ Digestion, Bloodstream, Metabolites ] @@ -315,7 +340,11 @@ suffix: "Diona, Nymphing" id: OrganDionaBrainNymphing components: - - type: Nymph + # Begin DeltaV - diona nymphing overhaul + - type: GibbableOrgan + active: true + # End DeltaV - diona nymphing overhaul + - type: DVNymphingOrgan # DeltaV - new diona mechanics transferMind: true entityPrototype: OrganDionaNymphBrain @@ -324,7 +353,11 @@ suffix: "Diona, Nymphing" id: OrganDionaLungsNymphing components: - - type: Nymph + # Begin DeltaV - diona nymphing overhaul + - type: GibbableOrgan + active: true + # End DeltaV - diona nymphing overhaul + - type: DVNymphingOrgan # DeltaV - new diona mechanics entityPrototype: OrganDionaNymphLungs - type: entity @@ -332,57 +365,64 @@ suffix: "Diona, Nymphing" id: OrganDionaStomachNymphing components: - - type: Nymph + # Begin DeltaV - diona nymphing overhaul + - type: GibbableOrgan + active: true + # End DeltaV - diona nymphing overhaul + - type: DVNymphingOrgan # DeltaV - new diona mechanics entityPrototype: OrganDionaNymphStomach - type: entity - parent: MobDionaNymph + parent: DVMobDionaNymph # DeltaV - new diona mechanics id: OrganDionaNymphBrain suffix: brain components: - type: IsDeadIC - - type: EntityTableContainerFill - containers: - body_organs: !type:AllSelector - children: - - id: OrganDionaBrain - - id: OrganAnimalLungs - - id: OrganAnimalStomach - - id: OrganAnimalLiver - - id: OrganAnimalHeart - - id: OrganAnimalKidneys + # - type: EntityTableContainerFill + # containers: + # body_organs: !type:AllSelector + # children: + # - id: OrganDionaBrain + # - id: OrganAnimalLungs + # - id: OrganAnimalStomach + # - id: OrganAnimalLiver + # - id: OrganAnimalHeart + # - id: OrganAnimalKidneys + # End DeltaV - we have a uniform organ configuration - type: entity - parent: MobDionaNymph + parent: DVMobDionaNymph # DeltaV - new diona mechanics id: OrganDionaNymphStomach suffix: stomach components: - type: IsDeadIC - - type: EntityTableContainerFill - containers: - body_organs: !type:AllSelector - children: - - id: OrganDionaLungs - - id: OrganAnimalStomach - - id: OrganAnimalLiver - - id: OrganAnimalHeart - - id: OrganAnimalKidneys + # - type: EntityTableContainerFill + # containers: + # body_organs: !type:AllSelector + # children: + # - id: OrganDionaLungs + # - id: OrganAnimalStomach + # - id: OrganAnimalLiver + # - id: OrganAnimalHeart + # - id: OrganAnimalKidneys + # End DeltaV - we have a uniform organ configuration - type: entity - parent: MobDionaNymph + parent: DVMobDionaNymph # DeltaV - new diona mechanics id: OrganDionaNymphLungs suffix: lungs components: - type: IsDeadIC - - type: EntityTableContainerFill - containers: - body_organs: !type:AllSelector - children: - - id: OrganAnimalLungs - - id: OrganDionaStomach - - id: OrganAnimalLiver - - id: OrganAnimalHeart - - id: OrganAnimalKidneys + # - type: EntityTableContainerFill + # containers: + # body_organs: !type:AllSelector + # children: + # - id: OrganAnimalLungs + # - id: OrganDionaStomach + # - id: OrganAnimalLiver + # - id: OrganAnimalHeart + # - id: OrganAnimalKidneys + # End DeltaV - we have a uniform organ configuration - type: entity parent: MobDiona @@ -391,3 +431,21 @@ components: - type: IsDeadIC - type: RandomHumanoidAppearance + # Begin DeltaV - diona nymph overhaul + - type: InitialBody + organs: + Torso: OrganDionaTorso + Head: OrganDionaHead + ArmLeft: OrganDionaArmLeft + ArmRight: OrganDionaArmRight + HandRight: OrganDionaHandRight + HandLeft: OrganDionaHandLeft + LegLeft: OrganDionaLegLeft + LegRight: OrganDionaLegRight + FootLeft: OrganDionaFootLeft + FootRight: OrganDionaFootRight + Brain: OrganDionaBrain + Eyes: OrganDionaEyes + Lungs: OrganDionaLungs + Stomach: OrganDionaStomach + # End DeltaV - diona nymph overhaul \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index 7b0283d3175..8cf330314c9 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -3865,9 +3865,10 @@ task: RuminantCompound - type: entity + abstract: true # DeltaV - we don't use this prototype name: diona nymph - parent: [BaseMobAnimal, SimpleMobBase, StripableInventoryBase, DVNodeCrawler] - id: MobDionaNymph + parent: [BaseMobAnimal, SimpleMobBase, StripableInventoryBase] + id: MobDionaNymphUnused # DeltaV - we don't use this prototype description: It's like a cat, only.... branch-ier. components: - type: Sprite @@ -3876,26 +3877,6 @@ - map: ["enum.DamageStateVisualLayers.Base"] state: nymph sprite: Mobs/Animals/nymph.rsi - # DeltaV changes begin - - type: Carriable - - type: GhostRole - makeSentient: true - allowSpeech: true - allowMovement: true - name: ghost-role-information-nymph-name - description: ghost-role-information-nymph-description - rules: ghost-role-information-nonantagonist-rules - - type: GhostTakeoverAvailable - - type: IntrinsicRadioReceiver - - type: IntrinsicRadioTransmitter - channels: - - Rootsong - - type: ActiveRadio - channels: - - Rootsong - - type: TypingIndicator - proto: diona - # DeltaV changes end - type: Physics - type: Fixtures fixtures: @@ -3924,8 +3905,8 @@ Base: nymph_sleep Dead: Base: nymph_dead - - type: Butcherable - spawned: + - type: ToolRefinable + refineResult: - id: MaterialWoodPlank1 amount: 2 - type: InteractionPopup @@ -3966,27 +3947,11 @@ reformTime: 10 popupText: diona-reform-attempt reformPrototype: MobDionaReformed - - type: LightReactive # DeltaV - manual: true - - type: LightLevelHealth # DeltaV - darkThreshold: 0.4 - lightThreshold: 1.2 - darkDamage: - types: - Heat: 1.0 - lightDamage: - types: - Blunt: -0.1 - Piercing: -0.1 - Slash: -0.1 - Heat: -0.1 - Poison: -0.1 - Asphyxiation: -0.1 - darkMovementSpeedMultiplier: 0.7 - type: entity - parent: MobDionaNymph - id: MobDionaNymphAccent # No talky. For non-brain & wild nymphs + abstract: true # DeltaV - we don't use this prototype + parent: MobDionaNymphUnused # DeltaV - we don't use this prototype + id: MobDionaNymphAccentUnused # No talky. For non-brain & wild nymphs # DeltaV - we don't use this prototype suffix: Accent components: - type: ReplacementAccent diff --git a/Resources/Prototypes/Reagents/botany.yml b/Resources/Prototypes/Reagents/botany.yml index 5330dfa528b..ce40448beef 100644 --- a/Resources/Prototypes/Reagents/botany.yml +++ b/Resources/Prototypes/Reagents/botany.yml @@ -146,14 +146,33 @@ Asphyxiation: 1 Heat: 2 Poison: 1 - - !type:Polymorph - prototype: TreeMorph + # Begin DeltaV changes - robust harvest OD makes more nymphs, not treemorph + - !type:SpawnEntity + entity: DVDionaNymphDeferredSpawn # deferred spawn to prevent crash from spawning a new metabolizer during metabolism conditions: - - !type:MetabolizerTypeCondition - type: [Plant] - - !type:ReagentCondition - reagent: RobustHarvest - min: 80 + - !type:MetabolizerTypeCondition + type: [Plant] + - !type:ReagentCondition + reagent: RobustHarvest + min: 40 + - !type:AdjustReagent + reagent: RobustHarvest + amount: -20 + conditions: + - !type:MetabolizerTypeCondition + type: [Plant] + - !type:ReagentCondition + reagent: RobustHarvest + min: 40 + # - !type:Polymorph + # prototype: TreeMorph + # conditions: + # - !type:MetabolizerTypeCondition + # type: [Plant] + # - !type:ReagentCondition + # reagent: RobustHarvest + # min: 80 + # End DeltaV changes - robust harvest OD makes more nymphs, not treemorph - type: reagent id: Sedin diff --git a/Resources/Prototypes/_DV/Actions/diona.yml b/Resources/Prototypes/_DV/Actions/diona.yml new file mode 100644 index 00000000000..d9c656979c2 --- /dev/null +++ b/Resources/Prototypes/_DV/Actions/diona.yml @@ -0,0 +1,39 @@ +- type: entity + parent: BaseAction + id: DVDionaAssimilateAction + name: Assimilate + description: Assimilate with another nymph. + components: + - type: Action + icon: + sprite: _DV/Mobs/Species/Diona/gestalt.rsi + state: gestalt + - type: TargetAction + - type: EntityTargetAction + event: !type:DVAssimilateNymphActionEvent + +- type: entity + parent: BaseSuicideAction + id: DVDionaGibAction + name: Split up + description: Dissolve your gestalt and split apart. + components: + - type: Action + icon: + sprite: _DV/Mobs/Species/Diona/gestalt.rsi + state: nymph + - type: InstantAction + event: !type:GibActionEvent + +- type: entity + parent: BaseAction + id: DVDionaReformAction + name: Assume Humanoid Form + description: Assume a humanoid form. Requires three nymphs. + components: + - type: Action + icon: + sprite: Mobs/Species/Diona/parts.rsi + state: full + - type: InstantAction + event: !type:ReformEvent diff --git a/Resources/Prototypes/_DV/Entities/Mobs/Player/diona.yml b/Resources/Prototypes/_DV/Entities/Mobs/Player/diona.yml new file mode 100644 index 00000000000..6b748db71ac --- /dev/null +++ b/Resources/Prototypes/_DV/Entities/Mobs/Player/diona.yml @@ -0,0 +1,188 @@ +- type: entity + abstract: true + id: DVDionaMixin + components: + - type: IntrinsicRadioReceiver + - type: IntrinsicRadioTransmitter + channels: + - Rootsong + - type: ActiveRadio + channels: + - Rootsong + - type: TypingIndicator + proto: diona + - type: Grammar + attributes: + gender: epicene + - type: Speech + speechVerb: Plant + speechSounds: Alto + allowedEmotes: ['Chirp'] + - type: Vocal + sounds: + Male: UnisexDiona + Female: UnisexDiona + Unsexed: UnisexDiona + - type: Emoting + - type: BodyEmotes + soundsId: Nymph + - type: LightReactive # DeltaV + manual: true + - type: LightLevelHealth # DeltaV + darkThreshold: 0.4 + lightThreshold: 1.2 + darkDamage: + types: + Heat: 1.0 + lightDamage: + types: + Blunt: -0.1 + Piercing: -0.1 + Slash: -0.1 + Heat: -0.1 + Poison: -0.1 + Asphyxiation: -0.1 + darkMovementSpeedMultiplier: 0.7 + - type: EntityTableContainerFill + containers: + body_organs: !type:AllSelector + children: + - id: OrganDionaBrain + - id: OrganDionaLungs + - id: OrganDionaStomach + - type: Bloodstream + bloodReferenceSolution: + reagents: + - ReagentId: Sap + Quantity: 60 + +- type: entity + parent: [DVDionaMixin, BaseMobAnimal, SimpleMobBase, StripableInventoryBase, DVNodeCrawler] + id: DVMobDionaNymph + name: diona nymph + description: It's a little skittery critter. Chirp. + components: + - type: Sprite + drawdepth: Mobs + sprite: _DV/Mobs/Species/Diona/gestalt.rsi + layers: + - map: ["enum.DamageStateVisualLayers.Base"] + state: nymph + - state: eyes_nymph + shader: unshaded + - type: DVNymphLead + - type: DVNymphFollower + - type: DVNymphProfile + - type: DVGestaltMember + - type: DVNymphMindMemory + - type: HTN + rootTask: + task: DVNymphCompound + blackboard: + IdleRange: !type:Single + 2.5 + FollowCloseRange: !type:Single + 1.0 + FollowRange: !type:Single + 2.0 + - type: Clothing + quickEquip: false + sprite: _DV/Mobs/Species/Diona/gestalt.rsi + slots: + - HEAD + - NECK + - type: Item + size: Normal + sprite: _DV/Mobs/Species/Diona/gestalt.rsi + inhandVisuals: + left: + - state: inhand-left + right: + - state: inhand-right + - type: Physics + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeCircle + radius: 0.35 + density: 100 # High, because wood is heavy. + mask: + - MobMask + layer: + - MobLayer + - type: Inventory + speciesId: cat + templateId: pet + - type: DamageStateVisuals + states: + Alive: + Base: nymph + Critical: + Base: nymph_sleep + Dead: + Base: nymph_dead + - type: Butcherable + spawned: + - id: MaterialWoodPlank1 + amount: 2 + - type: InteractionPopup + successChance: 0.7 + interactSuccessString: petting-success-nymph + interactFailureString: petting-failure-nymph + interactSuccessSound: + path: /Audio/Animals/nymph_chirp.ogg + - type: MobThresholds + thresholds: + 0: Alive + 30: Critical + 60: Dead + - type: MovementSpeedModifier + baseWalkSpeed : 2.5 + baseSprintSpeed : 4.5 + - type: Tag + tags: + - DoorBumpOpener + - VimPilot + - type: ActionGrant + actions: + - DVDionaAssimilateAction + +- type: entity + parent: [DVDionaMixin, BaseMobAnimal, SimpleMobBase] + id: DVMobDionaGestalt + name: nascent diona gestalt + description: A heaving mass of nymphs. Chirp? + components: + - type: DVGestalt + - type: ComplexInteraction + - type: MovementSpeedModifier + baseWalkSpeed : 1.0 + baseSprintSpeed : 1.0 + - type: Sprite + drawdepth: Mobs + sprite: _DV/Mobs/Species/Diona/gestalt.rsi + layers: + - map: ["enum.DamageStateVisualLayers.Base"] + state: gestalt + - state: eyes_gestalt + shader: unshaded + - type: ActionGrant + actions: + - DVDionaAssimilateAction + - DVDionaGibAction + - DVDionaReformAction + + +- type: entity + id: DVDionaNymphDeferredSpawn + categories: [ HideSpawnMenu ] + components: + - type: TimedSpawner + prototypes: + - DVMobDionaNymph + intervalSeconds: 1 + minimumEntitiesSpawned: 1 + maximumEntitiesSpawned: 1 + - type: TimedDespawn + lifetime: 2 diff --git a/Resources/Prototypes/_DV/NPC/nymph.yml b/Resources/Prototypes/_DV/NPC/nymph.yml new file mode 100644 index 00000000000..b972a2302e9 --- /dev/null +++ b/Resources/Prototypes/_DV/NPC/nymph.yml @@ -0,0 +1,27 @@ +- type: htnCompound + id: DVNymphCompound + branches: + # - preconditions: + # - !type:HasOrdersPrecondition + # orders: enum.RatKingOrderType.Stay + # tasks: + # - !type:HTNCompoundTask + # task: IdleCompound + # - preconditions: + # - !type:HasOrdersPrecondition + # orders: enum.RatKingOrderType.Follow + - tasks: + - !type:HTNCompoundTask + task: FollowCompound + # - preconditions: + # - !type:HasOrdersPrecondition + # orders: enum.RatKingOrderType.CheeseEm + # tasks: + # - !type:HTNCompoundTask + # task: RatServantTargetAttackCompound + # - preconditions: + # - !type:HasOrdersPrecondition + # orders: enum.RatKingOrderType.Loose + # tasks: + # - !type:HTNCompoundTask + # task: SimpleHostileCompound diff --git a/Resources/Prototypes/_DV/Roles/GhostRoles/vent_critter.yml b/Resources/Prototypes/_DV/Roles/GhostRoles/vent_critter.yml index 19d3f8b070c..4050e75b23f 100644 --- a/Resources/Prototypes/_DV/Roles/GhostRoles/vent_critter.yml +++ b/Resources/Prototypes/_DV/Roles/GhostRoles/vent_critter.yml @@ -34,6 +34,6 @@ - MindRoleGhostRoleFreeAgentHarmless - type: dvSpawnableGhostRole - id: MobDionaNymph - entity: MobDionaNymph + id: DVMobDionaNymph + entity: DVMobDionaNymph rules: ghost-role-information-nonantagonist-rules diff --git a/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/equipped-HELMET.png b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/equipped-HELMET.png new file mode 100644 index 00000000000..90400777253 Binary files /dev/null and b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/equipped-HELMET.png differ diff --git a/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/equipped-NECK.png b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/equipped-NECK.png new file mode 100644 index 00000000000..16a6612501e Binary files /dev/null and b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/equipped-NECK.png differ diff --git a/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/eyes_gestalt.png b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/eyes_gestalt.png new file mode 100644 index 00000000000..adf73f17201 Binary files /dev/null and b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/eyes_gestalt.png differ diff --git a/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/eyes_nymph.png b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/eyes_nymph.png new file mode 100644 index 00000000000..8752226f4e8 Binary files /dev/null and b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/eyes_nymph.png differ diff --git a/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/floor.png b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/floor.png new file mode 100644 index 00000000000..815d3a67e81 Binary files /dev/null and b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/floor.png differ diff --git a/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/flower_back.png b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/flower_back.png new file mode 100644 index 00000000000..61024a28f3c Binary files /dev/null and b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/flower_back.png differ diff --git a/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/flower_fore.png b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/flower_fore.png new file mode 100644 index 00000000000..4ce806e0c4b Binary files /dev/null and b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/flower_fore.png differ diff --git a/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/gestalt.png b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/gestalt.png new file mode 100644 index 00000000000..23a91b35c4b Binary files /dev/null and b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/gestalt.png differ diff --git a/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/hat.png b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/hat.png new file mode 100644 index 00000000000..5f0fb2fbe57 Binary files /dev/null and b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/hat.png differ diff --git a/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/health0.png b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/health0.png new file mode 100644 index 00000000000..886232c12da Binary files /dev/null and b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/health0.png differ diff --git a/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/health1.png b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/health1.png new file mode 100644 index 00000000000..9f2473a1129 Binary files /dev/null and b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/health1.png differ diff --git a/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/health2.png b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/health2.png new file mode 100644 index 00000000000..0896a767e7b Binary files /dev/null and b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/health2.png differ diff --git a/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/health3.png b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/health3.png new file mode 100644 index 00000000000..4065fb50eb2 Binary files /dev/null and b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/health3.png differ diff --git a/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/health4.png b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/health4.png new file mode 100644 index 00000000000..3f0ab54bdcc Binary files /dev/null and b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/health4.png differ diff --git a/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/health5.png b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/health5.png new file mode 100644 index 00000000000..2b6c297d7c2 Binary files /dev/null and b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/health5.png differ diff --git a/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/health6.png b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/health6.png new file mode 100644 index 00000000000..3b507a178ef Binary files /dev/null and b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/health6.png differ diff --git a/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/health7.png b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/health7.png new file mode 100644 index 00000000000..6eec9c14faf Binary files /dev/null and b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/health7.png differ diff --git a/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/held.png b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/held.png new file mode 100644 index 00000000000..8ca5b496d2c Binary files /dev/null and b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/held.png differ diff --git a/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/inhand-left.png b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/inhand-left.png new file mode 100644 index 00000000000..c4f19d7a4a0 Binary files /dev/null and b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/inhand-left.png differ diff --git a/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/inhand-right.png b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/inhand-right.png new file mode 100644 index 00000000000..a71eb15a7ef Binary files /dev/null and b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/inhand-right.png differ diff --git a/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/intent_devour.png b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/intent_devour.png new file mode 100644 index 00000000000..2c1178372d6 Binary files /dev/null and b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/intent_devour.png differ diff --git a/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/intent_expel.png b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/intent_expel.png new file mode 100644 index 00000000000..e49bbb98c30 Binary files /dev/null and b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/intent_expel.png differ diff --git a/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/meta.json b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/meta.json new file mode 100644 index 00000000000..530af1b84dc --- /dev/null +++ b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/meta.json @@ -0,0 +1,304 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from https://github.com/Baystation12/Baystation12/blob/2753dca5a7a85d18a8c51b176cd6fd61f0e64d70/icons/mob/gestalt.dmi", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "nymph", + "directions": 4, + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + }, + { + "name": "eyes_nymph", + "directions": 4, + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + }, + { + "name": "nymph_sleep", + "directions": 4 + }, + { + "name": "nymph_dead" + }, + { + "name": "gestalt", + "directions": 4, + "delays": [ + [ + 0.5, + 0.5, + 0.5, + 0.5 + ], + [ + 0.5, + 0.5, + 0.5, + 0.5 + ], + [ + 0.5, + 0.5, + 0.5, + 0.5 + ], + [ + 0.5, + 0.5, + 0.5, + 0.5 + ] + ] + }, + { + "name": "eyes_gestalt", + "directions": 4, + "delays": [ + [ + 0.5, + 0.5, + 0.5, + 0.5 + ], + [ + 0.5, + 0.5, + 0.5, + 0.5 + ], + [ + 0.5, + 0.5, + 0.5, + 0.5 + ], + [ + 0.5, + 0.5, + 0.5, + 0.5 + ] + ] + }, + { + "name": "wall" + }, + { + "name": "floor" + }, + { + "name": "flower_back", + "directions": 4, + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + }, + { + "name": "flower_fore", + "directions": 4, + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + }, + { + "name": "intent_expel" + }, + { + "name": "intent_devour" + }, + { + "name": "health0" + }, + { + "name": "health1" + }, + { + "name": "health2" + }, + { + "name": "health3" + }, + { + "name": "health4" + }, + { + "name": "health5" + }, + { + "name": "health6" + }, + { + "name": "health7" + }, + { + "name": "held" + }, + { + "name": "hat" + }, + { + "name": "equipped-HELMET", + "directions": 4 + }, + { + "name": "equipped-NECK", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/nymph.png b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/nymph.png new file mode 100644 index 00000000000..1515af25fbf Binary files /dev/null and b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/nymph.png differ diff --git a/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/nymph_dead.png b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/nymph_dead.png new file mode 100644 index 00000000000..c3371bf949f Binary files /dev/null and b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/nymph_dead.png differ diff --git a/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/nymph_sleep.png b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/nymph_sleep.png new file mode 100644 index 00000000000..fe49b15fba8 Binary files /dev/null and b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/nymph_sleep.png differ diff --git a/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/wall.png b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/wall.png new file mode 100644 index 00000000000..a2c5c18d851 Binary files /dev/null and b/Resources/Textures/_DV/Mobs/Species/Diona/gestalt.rsi/wall.png differ