Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Content.Server/Nutrition/EntitySystems/FoodSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
using Content.Shared.Stacks;
using Content.Shared.Storage;
using Content.Shared.Verbs;
using Content.Shared._Goobstation.EatToGrow;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Utility;
Expand Down Expand Up @@ -284,6 +285,8 @@ private void OnDoAfter(Entity<FoodComponent> entity, ref ConsumeDoAfterEvent arg
_reaction.DoEntityReaction(args.Target.Value, solution, ReactionMethod.Ingestion);
_stomach.TryTransferSolution(stomachToUse!.Value.Owner, split, stomachToUse);

var afterEatingEv = new AfterEatingEvent(entity.Owner);
RaiseLocalEvent(args.Target.Value, ref afterEatingEv);
var flavors = args.FlavorMessage;

if (forceFeed)
Expand Down
33 changes: 33 additions & 0 deletions Content.Server/_Goobstation/Dash/DashActionChatSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using Content.Server.Chat.Systems;
using Content.Shared._Goobstation.Dash;
using Content.Shared.Chat.Prototypes;
using Robust.Shared.Prototypes;

namespace Content.Server._Goobstation.Dash;

public sealed class DashActionChatSystem : EntitySystem
{
[Dependency] private readonly ChatSystem _chat = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;

public override void Initialize()
{
base.Initialize();

SubscribeLocalEvent<DashActionEvent>(OnDash);
}

private void OnDash(DashActionEvent args)
{
if (args.Emote == null)
return;

if (!_prototype.TryIndex<EmotePrototype>(args.Emote.Value, out var emote))
return;

_chat.TryEmoteWithChat(
args.Performer,
emote,
forceEmote: true);
}
}
81 changes: 81 additions & 0 deletions Content.Server/_Goobstation/EatToGrow/EatToGrowSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using Content.Shared._Goobstation.EatToGrow;
using Content.Shared.Mobs;
using Robust.Server.GameObjects;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Collision.Shapes;
using Robust.Shared.Physics.Systems;
using System.Numerics;
using Content.Shared.Sprite;

namespace Content.Server._Goobstation.EatToGrow;


public sealed class EatToGrowSystem : EntitySystem
{
[Dependency] private readonly AppearanceSystem _appearance = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;

public override void Initialize()
{
base.Initialize();

SubscribeLocalEvent<EatToGrowComponent, AfterEatingEvent>(OnFoodEaten);
SubscribeLocalEvent<EatToGrowComponent, MobStateChangedEvent>(ShrinkOnDeath);
}

private void OnFoodEaten(Entity<EatToGrowComponent> ent, ref AfterEatingEvent args)
{
Grow(ent.Owner, ent.Comp, 1);
}

private void Grow(EntityUid eater, EatToGrowComponent comp, float scale)
{
// if growing would go over the limit, return
if (comp.CurrentScale >= comp.MaxGrowth)
return;
// Uses scale variable to multiply the growth, mainly used for shrinking
// Add growth
comp.CurrentScale += comp.Growth;
comp.CurrentScale = MathF.Min(comp.CurrentScale, comp.MaxGrowth);

EnsureComp<ScaleVisualsComponent>(eater);

var appearanceComponent = EnsureComp<AppearanceComponent>(eater);
if (!_appearance.TryGetData<Vector2>(eater, ScaleVisuals.Scale, out var oldScale, appearanceComponent))
oldScale = Vector2.One;

_appearance.SetData(eater, ScaleVisuals.Scale, oldScale + scale * new Vector2(comp.Growth, comp.Growth), appearanceComponent);

Dirty(eater, comp); // Sync updated growth to client.

// add 1 to times grown
comp.TimesGrown += 1;

// Grow the fixture by 1/4 the growth
if (TryComp(eater, out FixturesComponent? manager))
{
foreach (var (id, fixture) in manager.Fixtures)
{
if (fixture.Shape is PhysShapeCircle circle)
{
_physics.SetPositionRadius(
eater, id, fixture, circle,
circle.Position, circle.Radius + scale * (comp.Growth / 4), manager);
}
}
}
}
private void ShrinkOnDeath(Entity<EatToGrowComponent> eater, ref MobStateChangedEvent args)
{
// Copied from TryGrow, just need to grow in reverse
if (args.NewMobState != MobState.Dead || !TryComp<EatToGrowComponent>(eater, out var comp) || comp.ShrinkOnDeath == false)
return;

// shrink the entity
Grow(eater, comp, -comp.TimesGrown); // uses the negative of times grown to shrink the entity back to normal

// Reset data on shrink
comp.CurrentScale = 1f;
comp.TimesGrown = 0;
}
};
13 changes: 13 additions & 0 deletions Content.Server/_Goobstation/Ingestion/GoobEatingEvents.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace Content.Server._Goobstation.Ingestion;

/// <summary>
/// Raised directed at the eater after finishing eating the food before it's deleted.
/// </summary>
[ByRefEvent]
public readonly record struct AfterEatingEvent(EntityUid Food)
{
/// <summary>
/// The UID of the food entity that was fully eaten.
/// </summary>
public readonly EntityUid Food = Food;
}
17 changes: 10 additions & 7 deletions Content.Shared/Clothing/EntitySystems/HideLayerClothingSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ private void OnHideGotUnequipped(Entity<HideLayerClothingComponent> ent, ref Clo

private void SetLayerVisibility(
Entity<HideLayerClothingComponent?, ClothingComponent?> clothing,
Entity<HumanoidAppearanceComponent?> user,
EntityUid user,
bool hideLayers)
{
if (_timing.ApplyingState)
Expand All @@ -45,12 +45,15 @@ private void SetLayerVisibility(
if (!Resolve(clothing.Owner, ref clothing.Comp1, ref clothing.Comp2))
return;

if (!Resolve(user.Owner, ref user.Comp))
if (!TryComp<HumanoidAppearanceComponent>(user, out var humanoid))
return;

var humanoidEntity = new Entity<HumanoidAppearanceComponent>(user, humanoid);

hideLayers &= IsEnabled(clothing!);
hideLayers &= IsEnabled(new Entity<HideLayerClothingComponent, ClothingComponent>( clothing.Owner, clothing.Comp1!, clothing.Comp2! ));

var hideable = user.Comp.HideLayersOnEquip;
var hideable = humanoid.HideLayersOnEquip;

var inSlot = clothing.Comp2.InSlotFlag ?? SlotFlags.NONE;

// This method should only be getting called while the clothing is equipped (though possibly currently in
Expand All @@ -70,7 +73,7 @@ private void SetLayerVisibility(

// Only update this layer if we are currently equipped to the relevant slot.
if (validSlots.HasFlag(inSlot))
_humanoid.SetLayerVisibility(user!, layer, !hideLayers, inSlot, ref dirty);
_humanoid.SetLayerVisibility(humanoidEntity, layer, !hideLayers, inSlot, ref dirty);
}

// Fallback for obsolete field: assume we want to hide **all** layers, as long as we are equipped to any
Expand All @@ -82,12 +85,12 @@ private void SetLayerVisibility(
foreach (var layer in slots)
{
if (hideable.Contains(layer))
_humanoid.SetLayerVisibility(user!, layer, !hideLayers, inSlot, ref dirty);
_humanoid.SetLayerVisibility(humanoidEntity, layer, !hideLayers, inSlot, ref dirty);
}
}

if (dirty)
Dirty(user!);
Dirty(user, humanoid);
}

private bool IsEnabled(Entity<HideLayerClothingComponent, ClothingComponent> clothing)
Expand Down
1 change: 1 addition & 0 deletions Content.Shared/Nutrition/EntitySystems/IngestionSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
using Content.Shared._Goobstation.EatToGrow;
48 changes: 48 additions & 0 deletions Content.Shared/_Goobstation/Dash/DashActionComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// SPDX-License-Identifier: AGPL-3.0-or-later

using Content.Shared.Actions;
using Content.Shared.Chat.Prototypes;
using Robust.Shared.Prototypes;

namespace Content.Shared._Goobstation.Dash;

[RegisterComponent]
public sealed partial class DashActionComponent : Component
{
[DataField]
public string? ActionProto;

[ViewVariables(VVAccess.ReadOnly)]
public EntityUid? ActionUid;
}

public sealed partial class DashActionEvent : WorldTargetActionEvent
{
[DataField]
public float Distance = 4.65f;

[DataField]
public float Speed = 9.65f;

[DataField]
public float? StaminaDrain;

/// <summary>
/// Whether you need gravity to perform the dash. Keep in mind there's no friction without gravity so if this
/// is false, the performer gets every chance to be launched straight to Ohio on dashing without gravity.
/// </summary>
[DataField]
public bool NeedsGravity = true;

/// <summary>
/// Whether dash distance and speed are affected by performer's speed modifiers. Should be true most of the time.
/// </summary>
[DataField]
public bool AffectedBySpeed = true;

/// <summary>
/// Animated emote to play on successful dash.
/// </summary>
[DataField]
public ProtoId<EmotePrototype>? Emote = "Flip";
}
71 changes: 71 additions & 0 deletions Content.Shared/_Goobstation/Dash/DashActionSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// SPDX-License-Identifier: AGPL-3.0-or-later

using Content.Shared.Emoting;
using Content.Shared.Actions;
using Content.Shared.Damage;
using Content.Shared.Damage.Components;
using Content.Shared.Damage.Systems;
using Content.Shared.Gravity;
using Content.Shared.Movement.Components;
using Content.Shared.Throwing;

namespace Content.Shared._Goobstation.Dash;

public sealed class DashActionSystem : EntitySystem
{
[Dependency] private readonly SharedActionsSystem _actions = default!;
[Dependency] private readonly SharedGravitySystem _gravity = default!;
[Dependency] private readonly ThrowingSystem _throwing = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly StaminaSystem _stamina = default!;
public override void Initialize()
{
base.Initialize();

SubscribeLocalEvent<DashActionEvent>(OnDashAction);

SubscribeLocalEvent<DashActionComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<DashActionComponent, ComponentShutdown>(OnComponentShutdown);
}

private void OnDashAction(DashActionEvent args)
{
if (args.Handled)
return;

if (args.NeedsGravity && _gravity.IsWeightless(args.Performer))
return;

args.Handled = true;
var vec = (_transform.ToMapCoordinates(args.Target).Position -
_transform.GetMapCoordinates(args.Performer).Position).Normalized() * args.Distance;
var speed = args.Speed;

if (args.AffectedBySpeed && TryComp<MovementSpeedModifierComponent>(args.Performer, out var speedcomp))
{
vec *= speedcomp.CurrentSprintSpeed / speedcomp.BaseSprintSpeed;
speed *= speedcomp.CurrentSprintSpeed / speedcomp.BaseSprintSpeed;
}

_throwing.TryThrow(args.Performer, vec, speed, animated: false);

if (args.StaminaDrain != null)
_stamina.TakeStaminaDamage(args.Performer, args.StaminaDrain.Value, visual: false, immediate: false);

if (args.Emote != null && TryComp<AnimatedEmotesComponent>(args.Performer, out var emotes))
{
emotes.Emote = args.Emote;
Dirty(args.Performer, emotes);
}
}

private void OnComponentInit(EntityUid uid, DashActionComponent comp, ref ComponentInit args)
{
comp.ActionUid = _actions.AddAction(uid, comp.ActionProto);
}

private void OnComponentShutdown(EntityUid uid, DashActionComponent comp, ref ComponentShutdown args)
{
_actions.RemoveAction(comp.ActionUid);
}
}
8 changes: 8 additions & 0 deletions Content.Shared/_Goobstation/EatToGrow/AfterEatingEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using Robust.Shared.GameObjects;

namespace Content.Shared._Goobstation.EatToGrow;
/// <summary>
/// Raised directed at the eater after finishing eating the food before it is deleted.
/// </summary>
[ByRefEvent]
public readonly record struct AfterEatingEvent(EntityUid Food);
22 changes: 22 additions & 0 deletions Content.Shared/_Goobstation/EatToGrow/EatToGrowComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using Content.Shared.Damage;
using Robust.Shared.GameStates;

namespace Content.Shared._Goobstation.EatToGrow;
[RegisterComponent, NetworkedComponent]
public sealed partial class EatToGrowComponent : Component
{
[DataField]
public float Growth = 0.1f; // percentage growth

[DataField]
public float MaxGrowth = 5.0f; // max allowed scale multiplier

[DataField]
public float CurrentScale = 1.0f; // current scale

[DataField]
public bool ShrinkOnDeath = true; // Revert to original size on death?

[DataField]
public int TimesGrown = 0; // how many times have they grown?
}
23 changes: 23 additions & 0 deletions Content.Shared/_Goobstation/EatToGrow/EatToGrowComponentState.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;

namespace Content.Shared._Goobstation.EatToGrow;

[Serializable, NetSerializable]
public sealed class EatToGrowComponentState : ComponentState
{
public readonly float Growth;
public readonly float MaxGrowth;

public readonly float CurrentScale;
public readonly bool ShrinkOnDeath;
public readonly int TimesGrown;
public EatToGrowComponentState(float growth, float maxGrowth, float currentScale, bool shrinkOnDeath, int timesGrown)
{
Growth = growth;
MaxGrowth = maxGrowth;
CurrentScale = currentScale;
ShrinkOnDeath = shrinkOnDeath;
TimesGrown = timesGrown;
}
}
6 changes: 6 additions & 0 deletions Resources/Audio/_Goobstation/Effects/attributions.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# SPDX-License-Identifier: AGPL-3.0-or-later

- files: ["moth_wings.ogg"]
license: "CC0-1.0"
copyright: "Original by tothrec2"
source: "https://freesound.org/people/tothrec2/sounds/596541/"
Binary file not shown.
3 changes: 3 additions & 0 deletions Resources/Locale/en-US/_Goobstation/emotes.ftl
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
chat-emote-name-spin = Spin
chat-emote-name-jump = Jump
chat-emote-name-flip = Do a flip
chat-emote-msg-spin = spins!
chat-emote-msg-jump = jumps!
chat-emote-msg-flip = does a flip!


# Names
chat-emote-name-trill = Trill
Expand Down
Loading
Loading