This commit is contained in:
pathetic meowmeow 2026-08-14 01:46:09 +00:00 committed by GitHub
commit 1f2d533aeb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
52 changed files with 1747 additions and 92 deletions

View File

@ -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<SpeciesPrototype> 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<MapComponent>(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<DVGestaltMemberComponent>(actor).StoredInGestalt, Is.EqualTo(gestalt.Owner));
Assert.That(SComp<DVGestaltMemberComponent>(mindlessTarget).StoredInGestalt, Is.EqualTo(gestalt.Owner));
Assert.That(SComp<TransformComponent>(actor).MapID, Is.EqualTo(mapComp.MapId));
Assert.That(SComp<TransformComponent>(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<DVGestaltMemberComponent>(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<MetaDataComponent>(reformed.Value).EntityName, Is.Not.EqualTo("Mismatched Rings"));
Assert.That(SComp<MetaDataComponent>(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<HumanoidProfileComponent>(reformed.Value);
Assert.Multiple(() =>
{
Assert.That(_mind.GetMind(reformed.Value), Is.EqualTo(firstMind.Owner));
Assert.That(SComp<MetaDataComponent>(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<EntityUid>();
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<MapComponent>(gestalt.Comp.NymphStorageMap);
foreach (var other in others)
{
Assert.That(SComp<TransformComponent>(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<DVGestaltComponent>(reformed.Value);
Assert.That(reformedGestalt.NymphCount, Is.EqualTo(2 + additionalNymphCount));
var reformedGestaltMap = SComp<MapComponent>(reformedGestalt.NymphStorageMap);
foreach (var other in others)
{
Assert.That(SComp<TransformComponent>(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<DVNymphLeadComponent>(leader)),
(follower, SComp<DVNymphFollowerComponent>(follower)));
var leaderComp = SComp<DVNymphLeadComponent>(leader);
var followerComp = SComp<DVNymphFollowerComponent>(follower);
var htn = SComp<HTNComponent>(follower);
using (Assert.EnterMultipleScope())
{
Assert.That(leaderComp.Followers, Does.Contain(follower));
Assert.That(followerComp.Lead, Is.EqualTo(leader));
Assert.That(htn.Blackboard.TryGetValue<EntityCoordinates>(NPCBlackboard.FollowTarget, out var coords, SEntMan), Is.True);
Assert.That(coords!.EntityId, Is.EqualTo(leader));
}
SEntMan.RemoveComponent<DVNymphLeadComponent>(leader);
using (Assert.EnterMultipleScope())
{
Assert.That(followerComp.Lead, Is.Null);
Assert.That(htn.Blackboard.TryGetValue<EntityCoordinates>(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<DVNymphProfileComponent>)
.ToList();
Assert.That(nymphs.Count, Is.EqualTo(3));
var lead = nymphs.Single(uid => _mind.GetMind(uid) == bodyMind);
var leadComp = SComp<DVNymphLeadComponent>(lead);
var memory = SComp<DVNymphMindMemoryComponent>(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<DVNymphProfileComponent>(nymph);
var follower = SComp<DVNymphFollowerComponent>(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<DVNymphProfileComponent>(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<DVGestaltComponent>();
while (gestalts.MoveNext(out var uid, out _))
{
if (SEntMan.Deleted(uid))
continue;
SEntMan.QueueDeleteEntity(uid);
}
var nymphs = SEntMan.EntityQueryEnumerator<DVNymphFollowerComponent>();
while (nymphs.MoveNext(out var uid, out _))
{
if (SEntMan.Deleted(uid))
continue;
SEntMan.QueueDeleteEntity(uid);
}
});
}
private Entity<DVGestaltComponent> GetSingleGestalt()
{
EntityUid foundUid = default;
DVGestaltComponent? foundComp = null;
var count = 0;
var query = SEntMan.EntityQueryEnumerator<DVGestaltComponent>();
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<MetaDataComponent>();
while (query.MoveNext(out var uid, out var meta))
{
if (SEntMan.Deleted(uid))
continue;
if (meta.EntityPrototype?.ID == prototype.Id)
return uid;
}
return null;
}
}

View File

@ -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<DVNymphFollowerComponent, DVNymphFollowerLeadGotChangedEvent>(OnLeadGotChanged);
}
private void OnLeadGotChanged(Entity<DVNymphFollowerComponent> ent, ref DVNymphFollowerLeadGotChangedEvent args)
{
if (ent.Comp.Lead is { } leader)
_npc.SetBlackboard(ent, NPCBlackboard.FollowTarget, new EntityCoordinates(leader, Vector2.Zero));
else if (TryComp<HTNComponent>(ent, out var htn))
{
htn.Blackboard.Remove<EntityCoordinates>(NPCBlackboard.FollowTarget);
}
}
}

View File

@ -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<DVNymphingOrganComponent, BodyRelayedEvent<BeingGibbedEvent>>(OnBeingGibbed);
SubscribeLocalEvent<DVNymphProfileComponent, RefreshNameModifiersEvent>(OnRefreshNameModifiers);
}
private void OnBeingGibbed(Entity<DVNymphingOrganComponent> ent, ref BodyRelayedEvent<BeingGibbedEvent> args)
{
if (TerminatingOrDeleted(ent))
return;
if (!_protoManager.TryIndex<EntityPrototype>(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<ZombieComponent>(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<DVNymphProfileComponent>(nymph, out var nymphProfile))
{
nymphProfile.Name = Name(args.Body);
if (TryComp<HumanoidProfileComponent>(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<MobStateComponent>(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<DVNymphProfileComponent> ent, ref RefreshNameModifiersEvent args)
{
if (ent.Comp.Name is { } name)
args.AddModifier("nymph-name-prefix", 0, ("identityName", name));
}
}

View File

@ -9,4 +9,11 @@ namespace Content.Shared.Body;
/// <seealso cref="GibbingSystem" />
[RegisterComponent, NetworkedComponent]
[Access(typeof(GibbableOrganSystem))]
public sealed partial class GibbableOrganComponent : Component;
public sealed partial class GibbableOrganComponent : Component
{
/// <summary>
/// DeltaV - whether the organ will become a giblet
/// </summary>
[DataField]
public bool Active = true;
}

View File

@ -13,6 +13,9 @@ public sealed class GibbableOrganSystem : EntitySystem
private void OnBeingGibbed(Entity<GibbableOrganComponent> ent, ref BodyRelayedEvent<BeingGibbedEvent> args)
{
args.Args.Giblets.Add(ent);
// Begin DeltaV - gibbable activation
if (ent.Comp.Active)
args.Args.Giblets.Add(ent);
// End DeltaV - gibbable activation
}
}

View File

@ -0,0 +1,5 @@
using Content.Shared.Actions;
namespace Content.Shared._DV.Diona;
public sealed partial class DVAssimilateNymphActionEvent : EntityTargetActionEvent;

View File

@ -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<EntityUid> StoredNymphs = new();
[DataField, AutoNetworkedField]
public int NymphCount;
[DataField]
public int RequiredNymphs = 3;
/// <summary>
/// The text that appears when attempting to split.
/// </summary>
[DataField]
public LocId PopupText = "diona-gib-action-use";
}

View File

@ -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;
}

View File

@ -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<DVGestaltComponent> GestaltPrototype = "DVMobDionaGestalt";
private static readonly EntProtoId ReformedPrototype = "MobDionaReformed";
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<DVNymphProfileComponent, DVAssimilateNymphActionEvent>(OnNymphAssimilate);
SubscribeLocalEvent<DVGestaltComponent, MapInitEvent>(OnGestaltInit);
SubscribeLocalEvent<DVGestaltComponent, ComponentShutdown>(OnGestaltShutdown);
SubscribeLocalEvent<DVGestaltMemberComponent, ComponentShutdown>(OnGestaltMemberShutdown);
SubscribeLocalEvent<DVGestaltComponent, DVAssimilateNymphActionEvent>(OnGestaltAssimilate);
SubscribeLocalEvent<DVGestaltComponent, GibActionSystem.GibActionEvent>(OnGestaltGib);
SubscribeLocalEvent<DVGestaltComponent, BeingGibbedEvent>(OnGestaltBeingGibbed);
SubscribeLocalEvent<DVGestaltComponent, GibbedBeforeDeletionEvent>(OnGestaltGibbedBeforeDeletion);
SubscribeLocalEvent<DVGestaltComponent, ReformSystem.ReformEvent>(OnGestaltReforming);
}
private void OnGestaltInit(Entity<DVGestaltComponent> 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<DVGestaltMemberComponent> ent, ref ComponentShutdown args)
{
if (!TryComp<DVGestaltComponent>(ent.Comp.StoredInGestalt, out var gestalt))
return;
gestalt.StoredNymphs.Remove(ent);
gestalt.NymphCount--;
Dirty(ent.Comp.StoredInGestalt.Value, gestalt);
}
private void OnGestaltShutdown(Entity<DVGestaltComponent> ent, ref ComponentShutdown args)
{
if (TryComp<MapComponent>(ent.Comp.NymphStorageMap, out var map))
_map.QueueDeleteMap(map.MapId);
}
private void OnGestaltAssimilate(Entity<DVGestaltComponent> 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<DVNymphProfileComponent> 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<DVGestaltComponent>(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<DVGestaltComponent> gestalt, Entity<DVGestaltMemberComponent?> nymph)
{
if (!Resolve(nymph, ref nymph.Comp) || !TryComp<MapComponent>(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<DVGestaltComponent> 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<DVGestaltComponent> ent, ref BeingGibbedEvent args)
{
args.Giblets.UnionWith(ent.Comp.StoredNymphs);
}
private void OnGestaltGibbedBeforeDeletion(Entity<DVGestaltComponent> ent, ref GibbedBeforeDeletionEvent args)
{
foreach (var giblet in args.Giblets)
{
if (!TryComp<DVNymphMindMemoryComponent>(giblet, out var memory))
continue;
if (!Exists(memory.Mind) || !TryComp<MindComponent>(memory.Mind, out var mindComponent))
continue;
_mind.TransferTo(memory.Mind.Value, giblet, true, mind: mindComponent);
}
}
private bool DetermineProfile(
Entity<DVGestaltComponent> 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<DVNymphProfileComponent>(nymphs[0]);
return true;
}
var headProfile = Comp<DVNymphProfileComponent>(nymphs[0]);
foreach (var nymph in nymphs.Skip(1))
{
var nymphProfile = Comp<DVNymphProfileComponent>(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<DVGestaltComponent> 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<MapComponent>(newComp.NymphStorageMap);
foreach (var nymph in ent.Comp.StoredNymphs)
{
var gestaltMember = Comp<DVGestaltMemberComponent>(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);
}
}

View File

@ -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;

View File

@ -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<EntityUid> Followers = new();
}

View File

@ -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;
}

View File

@ -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<ProtoId<OrganCategoryPrototype>, OrganProfileData>? OrganProfiles;
[DataField, AutoNetworkedField]
public Dictionary<ProtoId<OrganCategoryPrototype>, Dictionary<HumanoidVisualLayers, List<Marking>>>? OrganMarkings;
[DataField, AutoNetworkedField]
public string? Name;
[DataField, AutoNetworkedField]
public ProtoId<SpeciesPrototype> Species = "Diona";
[DataField, AutoNetworkedField]
public Gender Gender;
[DataField, AutoNetworkedField]
public Sex Sex;
[DataField, AutoNetworkedField]
public int Age = 18;
[DataField, AutoNetworkedField]
public float Height = 1f;
}

View File

@ -0,0 +1,54 @@
using JetBrains.Annotations;
namespace Content.Shared._DV.Diona;
public sealed class DVNymphRelationSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<DVNymphLeadComponent, ComponentShutdown>(OnLeadShutdown);
SubscribeLocalEvent<DVNymphFollowerComponent, ComponentShutdown>(OnFollowerShutdown);
}
private void OnFollowerShutdown(Entity<DVNymphFollowerComponent> ent, ref ComponentShutdown args)
{
if (ent.Comp.Lead is not { } nymph || !TryComp<DVNymphLeadComponent>(nymph, out var lead))
return;
lead.Followers.Remove(ent);
Dirty(nymph, lead);
}
private void OnLeadShutdown(Entity<DVNymphLeadComponent> ent, ref ComponentShutdown args)
{
foreach (var nymph in ent.Comp.Followers)
{
if (!TryComp<DVNymphFollowerComponent>(nymph, out var follower))
continue;
follower.Lead = null;
Dirty(nymph, follower);
var evt = new DVNymphFollowerLeadGotChangedEvent();
RaiseLocalEvent(nymph, ref evt);
}
}
[PublicAPI]
public void Follow(Entity<DVNymphLeadComponent?> leader, Entity<DVNymphFollowerComponent?> 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);
}
}

View File

@ -0,0 +1,13 @@
using Robust.Shared.GameStates;
namespace Content.Shared._DV.Diona;
[RegisterComponent, NetworkedComponent]
public sealed partial class DVNymphingBodyComponent : Component
{
/// <summary>
/// The text that appears when attempting to split.
/// </summary>
[DataField]
public LocId PopupText = "diona-gib-action-use";
}

View File

@ -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<DVNymphingBodyComponent, GibActionSystem.GibActionEvent>(OnNymphingBodyGib);
SubscribeLocalEvent<DVNymphingBodyComponent, GibbedBeforeDeletionEvent>(OnGibbedBeforeDeletion);
}
private void OnNymphingBodyGib(Entity<DVNymphingBodyComponent> 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<DVNymphingBodyComponent> 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<DVNymphLeadComponent>(leader, out var leaderComp))
return;
if (TryComp<DVNymphMindMemoryComponent>(leader, out var memory) && Exists(leadMind))
{
memory.Mind = leadMind;
}
foreach (var giblet in args.Giblets)
{
if (!TryComp<DVNymphFollowerComponent>(giblet, out var follower))
continue;
_nymph.Follow((leader, leaderComp), (giblet, follower));
}
}
}

View File

@ -0,0 +1,23 @@
using Robust.Shared.Prototypes;
using Robust.Shared.GameStates;
namespace Content.Shared._DV.Diona;
/// <summary>
/// Component that will cause an organ to turn into a nymph when removed from its body.
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed partial class DVNymphingOrganComponent : Component
{
/// <summary>
/// The entity to replace the organ with.
/// </summary>
[DataField(required: true)]
public EntProtoId EntityPrototype;
/// <summary>
/// Whether to transfer the mind to this new entity.
/// </summary>
[DataField]
public bool TransferMind;
}

View File

@ -0,0 +1 @@
nymph-name-prefix = {$baseName} ({$identityName})

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -34,6 +34,6 @@
- MindRoleGhostRoleFreeAgentHarmless
- type: dvSpawnableGhostRole
id: MobDionaNymph
entity: MobDionaNymph
id: DVMobDionaNymph
entity: DVMobDionaNymph
rules: ghost-role-information-nonantagonist-rules

Binary file not shown.

After

Width:  |  Height:  |  Size: 943 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 791 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 868 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 815 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 811 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 915 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 618 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1020 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 953 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 944 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 606 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 695 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 700 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 748 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 786 B

View File

@ -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
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 655 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB