Add GHOST GANG! (#13734)

Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
This commit is contained in:
Chief-Engineer
2023-05-28 04:21:06 -05:00
committed by GitHub
parent 707b9063f9
commit 485a2fd432
14 changed files with 230 additions and 30 deletions

View File

@@ -5,6 +5,7 @@ using Robust.Shared.Random;
using Robust.Shared.Timing; using Robust.Shared.Timing;
using Content.Server.Physics.Components; using Content.Server.Physics.Components;
using Content.Shared.Follower.Components;
using Content.Shared.Throwing; using Content.Shared.Throwing;
namespace Content.Server.Physics.Controllers; namespace Content.Server.Physics.Controllers;
@@ -41,7 +42,8 @@ internal sealed class RandomWalkController : VirtualController
foreach(var (randomWalk, physics) in EntityManager.EntityQuery<RandomWalkComponent, PhysicsComponent>()) foreach(var (randomWalk, physics) in EntityManager.EntityQuery<RandomWalkComponent, PhysicsComponent>())
{ {
if (EntityManager.HasComponent<ActorComponent>(randomWalk.Owner) if (EntityManager.HasComponent<ActorComponent>(randomWalk.Owner)
|| EntityManager.HasComponent<ThrownItemComponent>(randomWalk.Owner)) || EntityManager.HasComponent<ThrownItemComponent>(randomWalk.Owner)
|| EntityManager.HasComponent<FollowerComponent>(randomWalk.Owner))
continue; continue;
var curTime = _timing.CurTime; var curTime = _timing.CurTime;

View File

@@ -136,6 +136,7 @@ public sealed partial class RevenantSystem : EntitySystem
if (component.Essence <= 0) if (component.Essence <= 0)
{ {
Spawn(component.SpawnOnDeathPrototype, Transform(uid).Coordinates);
QueueDel(uid); QueueDel(uid);
} }
return true; return true;

View File

@@ -31,6 +31,11 @@ namespace Content.Shared.Construction.Steps
return typeof(TagConstructionGraphStep); return typeof(TagConstructionGraphStep);
} }
if (node.Has("prototype"))
{
return typeof(PrototypeConstructionGraphStep);
}
if (node.Has("allTags") || node.Has("anyTags")) if (node.Has("allTags") || node.Has("anyTags"))
{ {
return typeof(MultipleTagsConstructionGraphStep); return typeof(MultipleTagsConstructionGraphStep);

View File

@@ -0,0 +1,27 @@
using System.Linq;
namespace Content.Shared.Construction.Steps
{
[DataDefinition]
public sealed class PrototypeConstructionGraphStep : ArbitraryInsertConstructionGraphStep
{
[DataField("prototype")]
private string _prototype = "BaseItem";
[DataField("allowParents")]
private bool _allowParents;
public override bool EntityValid(EntityUid uid, IEntityManager entityManager, IComponentFactory compFactory)
{
entityManager.TryGetComponent(uid, out MetaDataComponent? metaDataComponent);
if (metaDataComponent?.EntityPrototype == null)
return false;
if (metaDataComponent.EntityPrototype.ID == _prototype)
return true;
if (_allowParents && metaDataComponent.EntityPrototype.Parents != null)
return metaDataComponent.EntityPrototype.Parents.Contains(_prototype);
return false;
}
}
}

View File

@@ -1,18 +1,27 @@
using Content.Shared.Database; using Content.Shared.Database;
using Content.Shared.Follower.Components; using Content.Shared.Follower.Components;
using Content.Shared.Ghost; using Content.Shared.Ghost;
using Content.Shared.Hands;
using Content.Shared.Movement.Events; using Content.Shared.Movement.Events;
using Content.Shared.Movement.Systems; using Content.Shared.Physics.Pull;
using Content.Shared.Tag;
using Content.Shared.Verbs; using Content.Shared.Verbs;
using Robust.Shared.Containers;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Map.Events; using Robust.Shared.Map.Events;
using Robust.Shared.Utility; using Robust.Shared.Utility;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Systems;
namespace Content.Shared.Follower; namespace Content.Shared.Follower;
public sealed class FollowerSystem : EntitySystem public sealed class FollowerSystem : EntitySystem
{ {
[Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly TagSystem _tagSystem = default!;
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
[Dependency] private readonly SharedJointSystem _jointSystem = default!;
[Dependency] private readonly SharedPhysicsSystem _physicsSystem = default!;
public override void Initialize() public override void Initialize()
{ {
@@ -20,6 +29,8 @@ public sealed class FollowerSystem : EntitySystem
SubscribeLocalEvent<GetVerbsEvent<AlternativeVerb>>(OnGetAlternativeVerbs); SubscribeLocalEvent<GetVerbsEvent<AlternativeVerb>>(OnGetAlternativeVerbs);
SubscribeLocalEvent<FollowerComponent, MoveInputEvent>(OnFollowerMove); SubscribeLocalEvent<FollowerComponent, MoveInputEvent>(OnFollowerMove);
SubscribeLocalEvent<FollowerComponent, PullStartedMessage>(OnPullStarted);
SubscribeLocalEvent<FollowerComponent, GotEquippedHandEvent>(OnGotEquippedHand);
SubscribeLocalEvent<FollowedComponent, EntityTerminatingEvent>(OnFollowedTerminating); SubscribeLocalEvent<FollowedComponent, EntityTerminatingEvent>(OnFollowedTerminating);
SubscribeLocalEvent<BeforeSaveEvent>(OnBeforeSave); SubscribeLocalEvent<BeforeSaveEvent>(OnBeforeSave);
} }
@@ -44,32 +55,55 @@ public sealed class FollowerSystem : EntitySystem
private void OnGetAlternativeVerbs(GetVerbsEvent<AlternativeVerb> ev) private void OnGetAlternativeVerbs(GetVerbsEvent<AlternativeVerb> ev)
{ {
if (!HasComp<SharedGhostComponent>(ev.User)) if (ev.User == ev.Target || ev.Target.IsClientSide())
return; return;
if (ev.User == ev.Target || ev.Target.IsClientSide()) if (HasComp<SharedGhostComponent>(ev.User))
{
var verb = new AlternativeVerb()
{
Priority = 10,
Act = () => StartFollowingEntity(ev.User, ev.Target),
Impact = LogImpact.Low,
Text = Loc.GetString("verb-follow-text"),
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/open.svg.192dpi.png"))
};
ev.Verbs.Add(verb);
}
if (_tagSystem.HasTag(ev.Target, "ForceableFollow"))
{
if (!ev.CanAccess || !ev.CanInteract)
return; return;
var verb = new AlternativeVerb var verb = new AlternativeVerb
{ {
Priority = 10, Priority = 10,
Act = (() => Act = () => StartFollowingEntity(ev.Target, ev.User),
{
StartFollowingEntity(ev.User, ev.Target);
}),
Impact = LogImpact.Low, Impact = LogImpact.Low,
Text = Loc.GetString("verb-follow-text"), Text = Loc.GetString("verb-follow-me-text"),
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/open.svg.192dpi.png")), Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/close.svg.192dpi.png")),
}; };
ev.Verbs.Add(verb); ev.Verbs.Add(verb);
} }
}
private void OnFollowerMove(EntityUid uid, FollowerComponent component, ref MoveInputEvent args) private void OnFollowerMove(EntityUid uid, FollowerComponent component, ref MoveInputEvent args)
{ {
StopFollowingEntity(uid, component.Following); StopFollowingEntity(uid, component.Following);
} }
private void OnPullStarted(EntityUid uid, FollowerComponent component, PullStartedMessage args)
{
StopFollowingEntity(uid, component.Following);
}
private void OnGotEquippedHand(EntityUid uid, FollowerComponent component, GotEquippedHandEvent args)
{
StopFollowingEntity(uid, component.Following, deparent:false);
}
// Since we parent our observer to the followed entity, we need to detach // Since we parent our observer to the followed entity, we need to detach
// before they get deleted so that we don't get recursively deleted too. // before they get deleted so that we don't get recursively deleted too.
private void OnFollowedTerminating(EntityUid uid, FollowedComponent component, ref EntityTerminatingEvent args) private void OnFollowedTerminating(EntityUid uid, FollowedComponent component, ref EntityTerminatingEvent args)
@@ -102,24 +136,35 @@ public sealed class FollowerSystem : EntitySystem
if (!followedComp.Following.Add(follower)) if (!followedComp.Following.Add(follower))
return; return;
if (TryComp<JointComponent>(follower, out var joints))
_jointSystem.ClearJoints(follower, joints);
_physicsSystem.SetLinearVelocity(follower, Vector2.Zero);
var xform = Transform(follower); var xform = Transform(follower);
_transform.SetCoordinates(follower, xform, new EntityCoordinates(entity, Vector2.Zero), Angle.Zero); _containerSystem.AttachParentToContainerOrGrid(xform);
// If we didn't get to parent's container.
if (xform.ParentUid != Transform(xform.ParentUid).ParentUid)
{
_transform.SetCoordinates(follower, xform, new EntityCoordinates(entity, Vector2.Zero), rotation: Angle.Zero);
}
EnsureComp<OrbitVisualsComponent>(follower); EnsureComp<OrbitVisualsComponent>(follower);
var followerEv = new StartedFollowingEntityEvent(entity, follower); var followerEv = new StartedFollowingEntityEvent(entity, follower);
var entityEv = new EntityStartedFollowingEvent(entity, follower); var entityEv = new EntityStartedFollowingEvent(entity, follower);
RaiseLocalEvent(follower, followerEv, true); RaiseLocalEvent(follower, followerEv);
RaiseLocalEvent(entity, entityEv, false); RaiseLocalEvent(entity, entityEv);
Dirty(followedComp); Dirty(followedComp);
} }
/// <summary> /// <summary>
/// Forces an entity to stop following another entity, if it is doing so. /// Forces an entity to stop following another entity, if it is doing so.
/// </summary> /// </summary>
public void StopFollowingEntity(EntityUid uid, EntityUid target, /// <param name="deparent">Should the entity deparent itself</param>
FollowedComponent? followed=null) public void StopFollowingEntity(EntityUid uid, EntityUid target, FollowedComponent? followed = null, bool deparent = true)
{ {
if (!Resolve(target, ref followed, false)) if (!Resolve(target, ref followed, false))
return; return;
@@ -130,24 +175,27 @@ public sealed class FollowerSystem : EntitySystem
followed.Following.Remove(uid); followed.Following.Remove(uid);
if (followed.Following.Count == 0) if (followed.Following.Count == 0)
RemComp<FollowedComponent>(target); RemComp<FollowedComponent>(target);
RemComp<FollowerComponent>(uid); RemComp<FollowerComponent>(uid);
var xform = Transform(uid);
_transform.AttachToGridOrMap(uid, xform);
if (xform.MapID == MapId.Nullspace)
{
QueueDel(uid);
return;
}
RemComp<OrbitVisualsComponent>(uid); RemComp<OrbitVisualsComponent>(uid);
var uidEv = new StoppedFollowingEntityEvent(target, uid); var uidEv = new StoppedFollowingEntityEvent(target, uid);
var targetEv = new EntityStoppedFollowingEvent(target, uid); var targetEv = new EntityStoppedFollowingEvent(target, uid);
RaiseLocalEvent(uid, uidEv, true); RaiseLocalEvent(uid, uidEv, true);
RaiseLocalEvent(target, targetEv, false); RaiseLocalEvent(target, targetEv, false);
Dirty(followed); Dirty(followed);
RaiseLocalEvent(uid, uidEv);
RaiseLocalEvent(target, targetEv);
if (!Deleted(uid) && deparent)
{
var xform = Transform(uid);
_transform.AttachToGridOrMap(uid, xform);
if (xform.MapUid == null)
{
QueueDel(uid);
}
}
} }
/// <summary> /// <summary>

View File

@@ -1,6 +1,7 @@
using Content.Shared.FixedPoint; using Content.Shared.FixedPoint;
using Content.Shared.Store; using Content.Shared.Store;
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Shared.Revenant.Components; namespace Content.Shared.Revenant.Components;
@@ -18,6 +19,12 @@ public sealed class RevenantComponent : Component
[DataField("stolenEssenceCurrencyPrototype", customTypeSerializer: typeof(PrototypeIdSerializer<CurrencyPrototype>))] [DataField("stolenEssenceCurrencyPrototype", customTypeSerializer: typeof(PrototypeIdSerializer<CurrencyPrototype>))]
public string StolenEssenceCurrencyPrototype = "StolenEssence"; public string StolenEssenceCurrencyPrototype = "StolenEssence";
/// <summary>
/// Prototype to spawn when the entity dies.
/// </summary>
[DataField("spawnOnDeathPrototype", customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>))]
public string SpawnOnDeathPrototype = "Ectoplasm";
/// <summary> /// <summary>
/// The entity's current max amount of essence. Can be increased /// The entity's current max amount of essence. Can be increased
/// through harvesting player souls. /// through harvesting player souls.

View File

@@ -1 +1,2 @@
verb-follow-text = Follow verb-follow-text = Follow
verb-follow-me-text = Make follow

View File

@@ -11,6 +11,7 @@
- type: RandomSpawner - type: RandomSpawner
rarePrototypes: rarePrototypes:
- FoamBlade - FoamBlade
- PlushieGhost
rareChance: 0.03 rareChance: 0.03
prototypes: prototypes:
- PlushieBee - PlushieBee

View File

@@ -55,6 +55,8 @@
orGroup: GiftPool orGroup: GiftPool
- id: JetpackMiniFilled - id: JetpackMiniFilled
orGroup: GiftPool orGroup: GiftPool
- id: PlushieGhost
orGroup: GiftPool
- id: PlushieBee - id: PlushieBee
orGroup: GiftPool orGroup: GiftPool
- id: PlushieRGBee - id: PlushieRGBee

View File

@@ -35,6 +35,59 @@
- type: StaticPrice - type: StaticPrice
price: 5 price: 5
- type: entity
parent: BasePlushie
id: PlushieGhost
name: ghost soft toy
description: The start of your personal GHOST GANG!
components:
- type: Sprite
sprite: Mobs/Ghosts/ghost_human.rsi
state: icon
noRot: true
- type: Item
- type: Fixtures
fixtures:
fix1:
shape:
!type:PhysShapeAabb
bounds: "-0.25,-0.25,0.25,0.25"
density: 20
mask:
- ItemMask
restitution: 0.98
friction: 0.01
- type: Physics
angularDamping: 0.02
linearDamping: 0.02
fixedRotation: true
bodyType: Dynamic
- type: TileFrictionModifier
modifier: 0.1
- type: Tag
tags:
- ForceableFollow
- type: RandomWalk
accumulatorRatio: 0.5
maxSpeed: 1
minSpeed: 0.25
- type: entity
parent: PlushieGhost
id: PlushieGhostRevenant
name: revenant soft toy
suffix: DO NOT MAP
description: So soft it almost makes you want to take a nap...
components:
- type: Sprite
sprite: Mobs/Ghosts/revenant.rsi
state: icon
netsync: false
noRot: true
- type: Construction
graph: PlushieGhostRevenant
node: plushie
- type: entity - type: entity
parent: BasePlushie parent: BasePlushie
id: PlushieBee id: PlushieBee

View File

@@ -123,3 +123,26 @@
- type: DeleteOnTrigger - type: DeleteOnTrigger
- type: Extractable - type: Extractable
grindableSolutionName: food grindableSolutionName: food
- type: entity
parent: Ash
id: Ectoplasm
name: ectoplasm
description: Much less deadly in this form.
components:
- type: Sprite
netsync: false
sprite: Mobs/Ghosts/revenant.rsi
state: ectoplasm
- type: Tag
tags:
- Trash
- type: SolutionContainerManager
solutions:
food:
maxVol: 50
reagents:
- ReagentId: Ash
Quantity: 5
- ReagentId: SpaceLube
Quantity: 5

View File

@@ -0,0 +1,15 @@
- type: constructionGraph
id: PlushieGhostRevenant
start: start
graph:
- node: start
edges:
- to: plushie
steps:
- prototype: PlushieGhost
name: a ghost plushie
- prototype: Ectoplasm
name: ectoplasm
doAfter: 10
- node: plushie
entity: PlushieGhostRevenant

View File

@@ -0,0 +1,12 @@
- type: construction
name: revenant plushie
id: PlushieGhostRevenant
graph: PlushieGhostRevenant
startNode: start
targetNode: plushie
category: construction-category-misc
objectType: Item
description: A toy to scare the medbay with.
icon:
sprite: Mobs/Ghosts/revenant.rsi
state: icon

View File

@@ -284,6 +284,9 @@
- type: Tag - type: Tag
id: FootstepSound id: FootstepSound
- type: Tag
id: ForceableFollow
- type: Tag - type: Tag
id: ForceFixRotations # fixrotations command WILL target this id: ForceFixRotations # fixrotations command WILL target this