-
Notifications
You must be signed in to change notification settings - Fork 146
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Port Bloodstains And Footprints (#1190)
# Description This ports WWhiteDreamProject/wwdpublic#110 from WWDP. This features significantly increases the interactions players have with Blood puddles, but introducing a mechanic whereby if players walk through puddles of blood, or attempt to drag a bloody corpse, they leave behind bloodstains as evidence of their presence or actions. You can avoid leaving marks from dragging corpses by using a rollerbed, and you can avoid leaving bloody footprints by avoiding walking in blood. This creates a great deal of emergent gameplay for all roles, but especially Traitors, Security(DETECTIVES!) and Janitors(Who now have to clean up bloody footprints!). Naturally, you can use syndicate soap to clean up any evidence of your crimes. # TODO - [x] Code Cleanup and Namespaces <details><summary><h1>Media</h1></summary> <p> ![image](https://github.com/user-attachments/assets/a60b3af7-6881-4338-836f-32bf45df9f90) </p> </details> # Changelog :cl: - add: Added Bloodstains, Body Dragging Marks, and Bloody Footprints to the game. Characters that walk through puddles of blood will now leave behind bloody footprints. Dragging a corpse leaves a trail of blood wherever the corpse was moved. --------- Co-authored-by: Spatison <[email protected]>
- Loading branch information
Showing
42 changed files
with
534 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
using Content.Shared.FootPrint; | ||
using Robust.Client.GameObjects; | ||
using Robust.Client.Graphics; | ||
using Robust.Shared.Random; | ||
|
||
namespace Content.Client.FootPrint; | ||
|
||
public sealed class FootPrintsVisualizerSystem : VisualizerSystem<FootPrintComponent> | ||
{ | ||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!; | ||
[Dependency] private readonly IRobustRandom _random = default!; | ||
|
||
public override void Initialize() | ||
{ | ||
base.Initialize(); | ||
|
||
SubscribeLocalEvent<FootPrintComponent, ComponentInit>(OnInitialized); | ||
SubscribeLocalEvent<FootPrintComponent, ComponentShutdown>(OnShutdown); | ||
} | ||
|
||
private void OnInitialized(EntityUid uid, FootPrintComponent comp, ComponentInit args) | ||
{ | ||
if (!TryComp<SpriteComponent>(uid, out var sprite)) | ||
return; | ||
|
||
sprite.LayerMapReserveBlank(FootPrintVisualLayers.Print); | ||
UpdateAppearance(uid, comp, sprite); | ||
} | ||
|
||
private void OnShutdown(EntityUid uid, FootPrintComponent comp, ComponentShutdown args) | ||
{ | ||
if (TryComp<SpriteComponent>(uid, out var sprite) | ||
&& sprite.LayerMapTryGet(FootPrintVisualLayers.Print, out var layer)) | ||
sprite.RemoveLayer(layer); | ||
} | ||
|
||
private void UpdateAppearance(EntityUid uid, FootPrintComponent component, SpriteComponent sprite) | ||
{ | ||
if (!sprite.LayerMapTryGet(FootPrintVisualLayers.Print, out var layer) | ||
|| !TryComp<FootPrintsComponent>(component.PrintOwner, out var printsComponent) | ||
|| !TryComp<AppearanceComponent>(uid, out var appearance) | ||
|| !_appearance.TryGetData<FootPrintVisuals>(uid, FootPrintVisualState.State, out var printVisuals, appearance)) | ||
return; | ||
|
||
sprite.LayerSetState(layer, new RSI.StateId(printVisuals switch | ||
{ | ||
FootPrintVisuals.BareFootPrint => printsComponent.RightStep ? printsComponent.RightBarePrint : printsComponent.LeftBarePrint, | ||
FootPrintVisuals.ShoesPrint => printsComponent.ShoesPrint, | ||
FootPrintVisuals.SuitPrint => printsComponent.SuitPrint, | ||
FootPrintVisuals.Dragging => _random.Pick(printsComponent.DraggingPrint), | ||
_ => throw new ArgumentOutOfRangeException($"Unknown {printVisuals} parameter.") | ||
}), printsComponent.RsiPath); | ||
|
||
if (_appearance.TryGetData<Color>(uid, FootPrintVisualState.Color, out var printColor, appearance)) | ||
sprite.LayerSetColor(layer, printColor); | ||
} | ||
|
||
protected override void OnAppearanceChange (EntityUid uid, FootPrintComponent component, ref AppearanceChangeEvent args) | ||
{ | ||
if (args.Sprite is not { } sprite) | ||
return; | ||
|
||
UpdateAppearance(uid, component, sprite); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
using Content.Server.Atmos.Components; | ||
using Content.Shared.Inventory; | ||
using Content.Shared.Mobs; | ||
using Content.Shared.Mobs.Components; | ||
using Content.Shared.FootPrint; | ||
using Content.Shared.Standing; | ||
using Content.Shared.Chemistry.Components.SolutionManager; | ||
using Content.Shared.Chemistry.EntitySystems; | ||
using Robust.Shared.Map; | ||
using Robust.Shared.Random; | ||
|
||
namespace Content.Server.FootPrint; | ||
|
||
public sealed class FootPrintsSystem : EntitySystem | ||
{ | ||
[Dependency] private readonly IRobustRandom _random = default!; | ||
[Dependency] private readonly InventorySystem _inventory = default!; | ||
[Dependency] private readonly IMapManager _map = default!; | ||
|
||
[Dependency] private readonly SharedSolutionContainerSystem _solution = default!; | ||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!; | ||
[Dependency] private readonly SharedTransformSystem _transform = default!; | ||
|
||
private EntityQuery<TransformComponent> _transformQuery; | ||
private EntityQuery<MobThresholdsComponent> _mobThresholdQuery; | ||
private EntityQuery<AppearanceComponent> _appearanceQuery; | ||
private EntityQuery<LayingDownComponent> _layingQuery; | ||
|
||
public override void Initialize() | ||
{ | ||
base.Initialize(); | ||
|
||
_transformQuery = GetEntityQuery<TransformComponent>(); | ||
_mobThresholdQuery = GetEntityQuery<MobThresholdsComponent>(); | ||
_appearanceQuery = GetEntityQuery<AppearanceComponent>(); | ||
_layingQuery = GetEntityQuery<LayingDownComponent>(); | ||
|
||
SubscribeLocalEvent<FootPrintsComponent, ComponentStartup>(OnStartupComponent); | ||
SubscribeLocalEvent<FootPrintsComponent, MoveEvent>(OnMove); | ||
} | ||
|
||
private void OnStartupComponent(EntityUid uid, FootPrintsComponent component, ComponentStartup args) | ||
{ | ||
component.StepSize = Math.Max(0f, component.StepSize + _random.NextFloat(-0.05f, 0.05f)); | ||
} | ||
|
||
private void OnMove(EntityUid uid, FootPrintsComponent component, ref MoveEvent args) | ||
{ | ||
if (component.PrintsColor.A <= 0f | ||
|| !_transformQuery.TryComp(uid, out var transform) | ||
|| !_mobThresholdQuery.TryComp(uid, out var mobThreshHolds) | ||
|| !_map.TryFindGridAt(_transform.GetMapCoordinates((uid, transform)), out var gridUid, out _)) | ||
return; | ||
|
||
var dragging = mobThreshHolds.CurrentThresholdState is MobState.Critical or MobState.Dead | ||
|| _layingQuery.TryComp(uid, out var laying) && laying.IsCrawlingUnder; | ||
var distance = (transform.LocalPosition - component.StepPos).Length(); | ||
var stepSize = dragging ? component.DragSize : component.StepSize; | ||
|
||
if (!(distance > stepSize)) | ||
return; | ||
|
||
component.RightStep = !component.RightStep; | ||
|
||
var entity = Spawn(component.StepProtoId, CalcCoords(gridUid, component, transform, dragging)); | ||
var footPrintComponent = EnsureComp<FootPrintComponent>(entity); | ||
|
||
footPrintComponent.PrintOwner = uid; | ||
Dirty(entity, footPrintComponent); | ||
|
||
if (_appearanceQuery.TryComp(entity, out var appearance)) | ||
{ | ||
_appearance.SetData(entity, FootPrintVisualState.State, PickState(uid, dragging), appearance); | ||
_appearance.SetData(entity, FootPrintVisualState.Color, component.PrintsColor, appearance); | ||
} | ||
|
||
if (!_transformQuery.TryComp(entity, out var stepTransform)) | ||
return; | ||
|
||
stepTransform.LocalRotation = dragging | ||
? (transform.LocalPosition - component.StepPos).ToAngle() + Angle.FromDegrees(-90f) | ||
: transform.LocalRotation + Angle.FromDegrees(180f); | ||
|
||
component.PrintsColor = component.PrintsColor.WithAlpha(Math.Max(0f, component.PrintsColor.A - component.ColorReduceAlpha)); | ||
component.StepPos = transform.LocalPosition; | ||
|
||
if (!TryComp<SolutionContainerManagerComponent>(entity, out var solutionContainer) | ||
|| !_solution.ResolveSolution((entity, solutionContainer), footPrintComponent.SolutionName, ref footPrintComponent.Solution, out var solution) | ||
|| string.IsNullOrWhiteSpace(component.ReagentToTransfer) || solution.Volume >= 1) | ||
return; | ||
|
||
_solution.TryAddReagent(footPrintComponent.Solution.Value, component.ReagentToTransfer, 1, out _); | ||
} | ||
|
||
private EntityCoordinates CalcCoords(EntityUid uid, FootPrintsComponent component, TransformComponent transform, bool state) | ||
{ | ||
if (state) | ||
return new EntityCoordinates(uid, transform.LocalPosition); | ||
|
||
var offset = component.RightStep | ||
? new Angle(Angle.FromDegrees(180f) + transform.LocalRotation).RotateVec(component.OffsetPrint) | ||
: new Angle(transform.LocalRotation).RotateVec(component.OffsetPrint); | ||
|
||
return new EntityCoordinates(uid, transform.LocalPosition + offset); | ||
} | ||
|
||
private FootPrintVisuals PickState(EntityUid uid, bool dragging) | ||
{ | ||
var state = FootPrintVisuals.BareFootPrint; | ||
|
||
if (_inventory.TryGetSlotEntity(uid, "shoes", out _)) | ||
state = FootPrintVisuals.ShoesPrint; | ||
|
||
if (_inventory.TryGetSlotEntity(uid, "outerClothing", out var suit) && TryComp<PressureProtectionComponent>(suit, out _)) | ||
state = FootPrintVisuals.SuitPrint; | ||
|
||
if (dragging) | ||
state = FootPrintVisuals.Dragging; | ||
|
||
return state; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
using System.Linq; | ||
using Content.Shared.FootPrint; | ||
using Content.Shared.Chemistry.Components.SolutionManager; | ||
using Content.Shared.Chemistry.EntitySystems; | ||
using Content.Shared.Fluids; | ||
using Content.Shared.Fluids.Components; | ||
using Robust.Shared.Physics.Events; | ||
|
||
namespace Content.Server.FootPrint; | ||
|
||
public sealed class PuddleFootPrintsSystem : EntitySystem | ||
{ | ||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!; | ||
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!; | ||
|
||
public override void Initialize() | ||
{ | ||
base.Initialize(); | ||
SubscribeLocalEvent<PuddleFootPrintsComponent, EndCollideEvent>(OnStepTrigger); | ||
} | ||
|
||
private void OnStepTrigger(EntityUid uid, PuddleFootPrintsComponent component, ref EndCollideEvent args) | ||
{ | ||
if (!TryComp<AppearanceComponent>(uid, out var appearance) | ||
|| !TryComp<PuddleComponent>(uid, out var puddle) | ||
|| !TryComp<FootPrintsComponent>(args.OtherEntity, out var tripper) | ||
|| !TryComp<SolutionContainerManagerComponent>(uid, out var solutionManager) | ||
|| !_solutionContainer.ResolveSolution((uid, solutionManager), puddle.SolutionName, ref puddle.Solution, out var solutions)) | ||
return; | ||
|
||
var totalSolutionQuantity = solutions.Contents.Sum(sol => (float) sol.Quantity); | ||
var waterQuantity = (from sol in solutions.Contents where sol.Reagent.Prototype == "Water" select (float) sol.Quantity).FirstOrDefault(); | ||
|
||
if (waterQuantity / (totalSolutionQuantity / 100f) > component.OffPercent || solutions.Contents.Count <= 0) | ||
return; | ||
|
||
tripper.ReagentToTransfer = | ||
solutions.Contents.Aggregate((l, r) => l.Quantity > r.Quantity ? l : r).Reagent.Prototype; | ||
|
||
if (_appearance.TryGetData(uid, PuddleVisuals.SolutionColor, out var color, appearance) | ||
&& _appearance.TryGetData(uid, PuddleVisuals.CurrentVolume, out var volume, appearance)) | ||
AddColor((Color) color, (float) volume * component.SizeRatio, tripper); | ||
|
||
_solutionContainer.RemoveEachReagent(puddle.Solution.Value, 1); | ||
} | ||
|
||
private void AddColor(Color col, float quantity, FootPrintsComponent component) | ||
{ | ||
component.PrintsColor = component.ColorQuantity == 0f ? col : Color.InterpolateBetween(component.PrintsColor, col, component.ColorInterpolationFactor); | ||
component.ColorQuantity += quantity; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
using Content.Shared.Chemistry.Components; | ||
using Robust.Shared.GameStates; | ||
|
||
namespace Content.Shared.FootPrint; | ||
|
||
/// <summary> | ||
/// This is used for marking footsteps, handling footprint drawing. | ||
/// </summary> | ||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] | ||
public sealed partial class FootPrintComponent : Component | ||
{ | ||
/// <summary> | ||
/// Owner (with <see cref="FootPrintsComponent"/>) of a print (this component). | ||
/// </summary> | ||
[AutoNetworkedField] | ||
public EntityUid PrintOwner; | ||
|
||
[DataField] | ||
public string SolutionName = "step"; | ||
|
||
[DataField] | ||
public Entity<SolutionComponent>? Solution; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
using Robust.Shared.Serialization; | ||
|
||
namespace Content.Shared.FootPrint; | ||
|
||
[Serializable, NetSerializable] | ||
public enum FootPrintVisuals : byte | ||
{ | ||
BareFootPrint, | ||
ShoesPrint, | ||
SuitPrint, | ||
Dragging | ||
} | ||
|
||
[Serializable, NetSerializable] | ||
public enum FootPrintVisualState : byte | ||
{ | ||
State, | ||
Color | ||
} | ||
|
||
[Serializable, NetSerializable] | ||
public enum FootPrintVisualLayers : byte | ||
{ | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
using System.Numerics; | ||
using Robust.Shared.Prototypes; | ||
using Robust.Shared.Utility; | ||
|
||
namespace Content.Shared.FootPrint; | ||
|
||
[RegisterComponent] | ||
public sealed partial class FootPrintsComponent : Component | ||
{ | ||
[ViewVariables(VVAccess.ReadOnly), DataField] | ||
public ResPath RsiPath = new("/Textures/Effects/footprints.rsi"); | ||
|
||
// all of those are set as a layer | ||
[ViewVariables(VVAccess.ReadOnly), DataField] | ||
public string LeftBarePrint = "footprint-left-bare-human"; | ||
|
||
[ViewVariables(VVAccess.ReadOnly), DataField] | ||
public string RightBarePrint = "footprint-right-bare-human"; | ||
|
||
[ViewVariables(VVAccess.ReadOnly), DataField] | ||
public string ShoesPrint = "footprint-shoes"; | ||
|
||
[ViewVariables(VVAccess.ReadOnly), DataField] | ||
public string SuitPrint = "footprint-suit"; | ||
|
||
[ViewVariables(VVAccess.ReadOnly), DataField] | ||
public string[] DraggingPrint = | ||
[ | ||
"dragging-1", | ||
"dragging-2", | ||
"dragging-3", | ||
"dragging-4", | ||
"dragging-5", | ||
]; | ||
// yea, those | ||
|
||
[ViewVariables(VVAccess.ReadOnly), DataField] | ||
public EntProtoId<FootPrintComponent> StepProtoId = "Footstep"; | ||
|
||
[ViewVariables(VVAccess.ReadOnly), DataField] | ||
public Color PrintsColor = Color.FromHex("#00000000"); | ||
|
||
/// <summary> | ||
/// The size scaling factor for footprint steps. Must be positive. | ||
/// </summary> | ||
[DataField] | ||
public float StepSize = 0.7f; | ||
|
||
/// <summary> | ||
/// The size scaling factor for drag marks. Must be positive. | ||
/// </summary> | ||
[DataField] | ||
public float DragSize = 0.5f; | ||
|
||
/// <summary> | ||
/// The amount of color to transfer from the source (e.g., puddle) to the footprint. | ||
/// </summary> | ||
[DataField] | ||
public float ColorQuantity; | ||
|
||
/// <summary> | ||
/// The factor by which the alpha channel is reduced in subsequent footprints. | ||
/// </summary> | ||
[DataField] | ||
public float ColorReduceAlpha = 0.1f; | ||
|
||
[DataField] | ||
public string? ReagentToTransfer; | ||
|
||
[DataField] | ||
public Vector2 OffsetPrint = new(0.1f, 0f); | ||
|
||
/// <summary> | ||
/// Tracks which foot should make the next print. True for right foot, false for left. | ||
/// </summary> | ||
public bool RightStep = true; | ||
|
||
/// <summary> | ||
/// The position of the last footprint in world coordinates. | ||
/// </summary> | ||
public Vector2 StepPos = Vector2.Zero; | ||
|
||
/// <summary> | ||
/// Controls how quickly the footprint color transitions between steps. | ||
/// Value between 0 and 1, where higher values mean faster color changes. | ||
/// </summary> | ||
public float ColorInterpolationFactor = 0.2f; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
namespace Content.Shared.FootPrint; | ||
|
||
[RegisterComponent] | ||
public sealed partial class PuddleFootPrintsComponent : Component | ||
{ | ||
[ViewVariables(VVAccess.ReadWrite)] | ||
public float SizeRatio = 0.2f; | ||
|
||
[ViewVariables(VVAccess.ReadWrite)] | ||
public float OffPercent = 80f; | ||
} |
Oops, something went wrong.