From 9075cf61630382d4754945860bfe0eab62a99c7d Mon Sep 17 00:00:00 2001 From: Moony Date: Sun, 28 Nov 2021 20:25:36 -0600 Subject: [PATCH] Space Kudzu (#5472) --- Content.Client/Entry/IgnoredComponents.cs | 4 +- Content.Client/Kudzu/KudzuVisualizer.cs | 28 ++++ .../Atmos/EntitySystems/AirtightSystem.cs | 15 ++ Content.Server/Kudzu/GrowingKudzuComponent.cs | 17 +++ Content.Server/Kudzu/GrowingKudzuSystem.cs | 56 ++++++++ Content.Server/Kudzu/SpreaderComponent.cs | 32 +++++ Content.Server/Kudzu/SpreaderSystem.cs | 133 ++++++++++++++++++ .../StationEvents/Events/GasLeak.cs | 37 +---- .../StationEvents/Events/KudzuGrowth.cs | 55 ++++++++ .../StationEvents/Events/StationEvent.cs | 47 +++++++ .../Components/TemperatureComponent.cs | 2 +- Content.Shared/DrawDepth/DrawDepth.cs | 11 +- Content.Shared/Kudzu/KudzuVisuals.cs | 11 ++ .../Components/SlowContactsComponent.cs | 18 +++ .../Components/SlowsOnContactComponent.cs | 13 ++ .../EntitySystems/SlowContactsSystem.cs | 77 ++++++++++ .../station-events/events/kudzu-growth.ftl | 1 + .../Entities/Mobs/Species/human.yml | 1 - .../Entities/Objects/Misc/kudzu.yml | 62 ++++++++ .../Entities/Objects/Tools/welders.yml | 1 + .../Objects/Misc/kudzu.rsi/kudzu_11.png | Bin 0 -> 1214 bytes .../Objects/Misc/kudzu.rsi/kudzu_12.png | Bin 0 -> 1112 bytes .../Objects/Misc/kudzu.rsi/kudzu_13.png | Bin 0 -> 1072 bytes .../Objects/Misc/kudzu.rsi/kudzu_21.png | Bin 0 -> 1362 bytes .../Objects/Misc/kudzu.rsi/kudzu_22.png | Bin 0 -> 1343 bytes .../Objects/Misc/kudzu.rsi/kudzu_23.png | Bin 0 -> 1251 bytes .../Objects/Misc/kudzu.rsi/kudzu_31.png | Bin 0 -> 1535 bytes .../Objects/Misc/kudzu.rsi/kudzu_32.png | Bin 0 -> 1634 bytes .../Objects/Misc/kudzu.rsi/kudzu_33.png | Bin 0 -> 1579 bytes .../Textures/Objects/Misc/kudzu.rsi/meta.json | 38 +++++ 30 files changed, 617 insertions(+), 42 deletions(-) create mode 100644 Content.Client/Kudzu/KudzuVisualizer.cs create mode 100644 Content.Server/Kudzu/GrowingKudzuComponent.cs create mode 100644 Content.Server/Kudzu/GrowingKudzuSystem.cs create mode 100644 Content.Server/Kudzu/SpreaderComponent.cs create mode 100644 Content.Server/Kudzu/SpreaderSystem.cs create mode 100644 Content.Server/StationEvents/Events/KudzuGrowth.cs create mode 100644 Content.Shared/Kudzu/KudzuVisuals.cs create mode 100644 Content.Shared/Movement/Components/SlowContactsComponent.cs create mode 100644 Content.Shared/Movement/Components/SlowsOnContactComponent.cs create mode 100644 Content.Shared/Movement/EntitySystems/SlowContactsSystem.cs create mode 100644 Resources/Locale/en-US/station-events/events/kudzu-growth.ftl create mode 100644 Resources/Prototypes/Entities/Objects/Misc/kudzu.yml create mode 100644 Resources/Textures/Objects/Misc/kudzu.rsi/kudzu_11.png create mode 100644 Resources/Textures/Objects/Misc/kudzu.rsi/kudzu_12.png create mode 100644 Resources/Textures/Objects/Misc/kudzu.rsi/kudzu_13.png create mode 100644 Resources/Textures/Objects/Misc/kudzu.rsi/kudzu_21.png create mode 100644 Resources/Textures/Objects/Misc/kudzu.rsi/kudzu_22.png create mode 100644 Resources/Textures/Objects/Misc/kudzu.rsi/kudzu_23.png create mode 100644 Resources/Textures/Objects/Misc/kudzu.rsi/kudzu_31.png create mode 100644 Resources/Textures/Objects/Misc/kudzu.rsi/kudzu_32.png create mode 100644 Resources/Textures/Objects/Misc/kudzu.rsi/kudzu_33.png create mode 100644 Resources/Textures/Objects/Misc/kudzu.rsi/meta.json diff --git a/Content.Client/Entry/IgnoredComponents.cs b/Content.Client/Entry/IgnoredComponents.cs index 2d24467e8b..c6f7151d20 100644 --- a/Content.Client/Entry/IgnoredComponents.cs +++ b/Content.Client/Entry/IgnoredComponents.cs @@ -315,7 +315,9 @@ namespace Content.Client.Entry "GhostRadio", "Armor", "Udder", - "PneumaticCannon" + "PneumaticCannon", + "Spreader", + "GrowingKudzu" }; } } diff --git a/Content.Client/Kudzu/KudzuVisualizer.cs b/Content.Client/Kudzu/KudzuVisualizer.cs new file mode 100644 index 0000000000..8c807408b8 --- /dev/null +++ b/Content.Client/Kudzu/KudzuVisualizer.cs @@ -0,0 +1,28 @@ +using Content.Shared.Kudzu; +using Robust.Client.GameObjects; +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization.Manager.Attributes; + +namespace Content.Client.Kudzu; + +public class KudzuVisualizer : AppearanceVisualizer +{ + [DataField("layer")] + private int Layer { get; } = 0; + + public override void OnChangeData(AppearanceComponent component) + { + base.OnChangeData(component); + + if (!component.Owner.TryGetComponent(out SpriteComponent? sprite)) + { + return; + } + + if (component.TryGetData(KudzuVisuals.Variant, out int var) && component.TryGetData(KudzuVisuals.GrowthLevel, out int level)) + { + sprite.LayerMapReserveBlank(Layer); + sprite.LayerSetState(0, $"kudzu_{level}{var}"); + } + } +} diff --git a/Content.Server/Atmos/EntitySystems/AirtightSystem.cs b/Content.Server/Atmos/EntitySystems/AirtightSystem.cs index 8ae5bcb748..33ad557adc 100644 --- a/Content.Server/Atmos/EntitySystems/AirtightSystem.cs +++ b/Content.Server/Atmos/EntitySystems/AirtightSystem.cs @@ -1,4 +1,5 @@ using Content.Server.Atmos.Components; +using Content.Server.Kudzu; using Content.Shared.Atmos; using JetBrains.Annotations; using Robust.Shared.GameObjects; @@ -13,6 +14,7 @@ namespace Content.Server.Atmos.EntitySystems { [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; + [Dependency] private readonly SpreaderSystem _spreaderSystem = default!; public override void Initialize() { @@ -43,6 +45,7 @@ namespace Content.Server.Atmos.EntitySystems SetAirblocked(airtight, false); InvalidatePosition(airtight.LastPosition.Item1, airtight.LastPosition.Item2, airtight.FixVacuum); + RaiseLocalEvent(new AirtightChanged(airtight)); } private void OnMapInit(EntityUid uid, AirtightComponent airtight, MapInitEvent args) @@ -69,12 +72,14 @@ namespace Content.Server.Atmos.EntitySystems airtight.CurrentAirBlockedDirection = (int) Rotate((AtmosDirection)airtight.InitialAirBlockedDirection, ev.NewRotation); UpdatePosition(airtight); + RaiseLocalEvent(uid, new AirtightChanged(airtight)); } public void SetAirblocked(AirtightComponent airtight, bool airblocked) { airtight.AirBlocked = airblocked; UpdatePosition(airtight); + RaiseLocalEvent(airtight.OwnerUid, new AirtightChanged(airtight)); } public void UpdatePosition(AirtightComponent airtight) @@ -119,4 +124,14 @@ namespace Content.Server.Atmos.EntitySystems return newAirBlockedDirs; } } + + public class AirtightChanged : EntityEventArgs + { + public AirtightComponent Airtight; + + public AirtightChanged(AirtightComponent airtight) + { + Airtight = airtight; + } + } } diff --git a/Content.Server/Kudzu/GrowingKudzuComponent.cs b/Content.Server/Kudzu/GrowingKudzuComponent.cs new file mode 100644 index 0000000000..8353844bca --- /dev/null +++ b/Content.Server/Kudzu/GrowingKudzuComponent.cs @@ -0,0 +1,17 @@ +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.ViewVariables; + +namespace Content.Server.Kudzu; + +[RegisterComponent] +public class GrowingKudzuComponent : Component +{ + public override string Name => "GrowingKudzu"; + + [DataField("growthLevel")] + public int GrowthLevel = 1; + + [DataField("growthTickSkipChance")] + public float GrowthTickSkipChange = 0.0f; +} diff --git a/Content.Server/Kudzu/GrowingKudzuSystem.cs b/Content.Server/Kudzu/GrowingKudzuSystem.cs new file mode 100644 index 0000000000..b582429fcb --- /dev/null +++ b/Content.Server/Kudzu/GrowingKudzuSystem.cs @@ -0,0 +1,56 @@ +using Content.Shared.Kudzu; +using Robust.Server.GameObjects; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Log; +using Robust.Shared.Random; + +namespace Content.Server.Kudzu; + +public class GrowingKudzuSystem : EntitySystem +{ + [Dependency] private readonly IRobustRandom _robustRandom = default!; + + private float _accumulatedFrameTime = 0.0f; + + public override void Initialize() + { + SubscribeLocalEvent(SetupKudzu); + } + + private void SetupKudzu(EntityUid uid, GrowingKudzuComponent component, ComponentAdd args) + { + if (!EntityManager.TryGetComponent(uid, out var appearance)) + { + return; + } + + appearance.SetData(KudzuVisuals.Variant, _robustRandom.Next(1, 3)); + appearance.SetData(KudzuVisuals.GrowthLevel, 1); + } + + public override void Update(float frameTime) + { + _accumulatedFrameTime += frameTime; + + if (!(_accumulatedFrameTime >= 0.5f)) + return; + + _accumulatedFrameTime -= 0.5f; + + foreach (var (kudzu, appearance) in EntityManager.EntityQuery()) + { + if (kudzu.GrowthLevel >= 3 || !_robustRandom.Prob(kudzu.GrowthTickSkipChange)) continue; + kudzu.GrowthLevel += 1; + + if (kudzu.GrowthLevel == 3 && + EntityManager.TryGetComponent(kudzu.OwnerUid, out var spreader)) + { + // why cache when you can simply cease to be? Also saves a bit of memory/time. + EntityManager.RemoveComponent(kudzu.OwnerUid); + } + + appearance.SetData(KudzuVisuals.GrowthLevel, kudzu.GrowthLevel); + } + } +} diff --git a/Content.Server/Kudzu/SpreaderComponent.cs b/Content.Server/Kudzu/SpreaderComponent.cs new file mode 100644 index 0000000000..d86eb75436 --- /dev/null +++ b/Content.Server/Kudzu/SpreaderComponent.cs @@ -0,0 +1,32 @@ +using Robust.Shared.Analyzers; +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.ViewVariables; + +namespace Content.Server.Kudzu; + +/// +/// Component for rapidly spreading objects, like Kudzu. +/// ONLY USE THIS FOR ANCHORED OBJECTS. An error will be logged if not anchored/static. +/// Currently does not support growing in space. +/// +[RegisterComponent, Friend(typeof(SpreaderSystem))] +public class SpreaderComponent : Component +{ + public override string Name => "Spreader"; + + /// + /// Chance for it to grow on any given tick, after the normal growth rate-limit (if it doesn't grow, SpreaderSystem will pick another one.). + /// + [DataField("chance", required: true)] + public float Chance; + + /// + /// Prototype spawned on growth success. + /// + [DataField("growthResult", required: true)] + public string GrowthResult = default!; + + [DataField("enabled")] + public bool Enabled = true; +} diff --git a/Content.Server/Kudzu/SpreaderSystem.cs b/Content.Server/Kudzu/SpreaderSystem.cs new file mode 100644 index 0000000000..4be9f0f606 --- /dev/null +++ b/Content.Server/Kudzu/SpreaderSystem.cs @@ -0,0 +1,133 @@ +using System.Collections.Generic; +using System.Linq; +using Content.Server.Atmos.Components; +using Content.Server.Atmos.EntitySystems; +using Content.Server.Temperature.Systems; +using Content.Shared.Atmos; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Map; +using Robust.Shared.Maths; +using Robust.Shared.Random; + +namespace Content.Server.Kudzu; + +// Future work includes making the growths per interval thing not global, but instead per "group" +public class SpreaderSystem : EntitySystem +{ + [Dependency] private readonly IRobustRandom _robustRandom = default!; + [Dependency] private readonly IMapManager _mapManager = default!; + + /// + /// Maximum number of edges that can grow out every interval. + /// + private const int GrowthsPerInterval = 1; + + private float _accumulatedFrameTime = 0.0f; + + private readonly HashSet _edgeGrowths = new (); + + public override void Initialize() + { + SubscribeLocalEvent(SpreaderAddHandler); + SubscribeLocalEvent(e => UpdateNearbySpreaders(e.Airtight.OwnerUid, e.Airtight)); + } + + private void SpreaderAddHandler(EntityUid uid, SpreaderComponent component, ComponentAdd args) + { + if (component.Enabled) + _edgeGrowths.Add(uid); // ez + } + + public void UpdateNearbySpreaders(EntityUid blocker, AirtightComponent comp) + { + if (!EntityManager.TryGetComponent(blocker, out var transform)) + return; // how did we get here? + + if (!_mapManager.TryGetGrid(transform.GridID, out var grid)) return; + + for (var i = 0; i < Atmospherics.Directions; i++) + { + var direction = (AtmosDirection) (1 << i); + if (!comp.AirBlockedDirection.IsFlagSet(direction)) continue; + + foreach (var ent in grid.GetInDir(transform.Coordinates, direction.ToDirection())) + { + if (EntityManager.TryGetComponent(ent, out var s) && s.Enabled) + _edgeGrowths.Add(ent); + } + } + } + + public override void Update(float frameTime) + { + _accumulatedFrameTime += frameTime; + + if (!(_accumulatedFrameTime >= 1.0f)) + return; + + _accumulatedFrameTime -= 1.0f; + + var growthList = _edgeGrowths.ToList(); + _robustRandom.Shuffle(growthList); + + var successes = 0; + foreach (var entity in growthList) + { + if (!TryGrow(entity)) continue; + + successes += 1; + if (successes >= GrowthsPerInterval) + break; + } + } + + private bool TryGrow(EntityUid ent, TransformComponent? transform = null, SpreaderComponent? spreader = null) + { + if (!Resolve(ent, ref transform, ref spreader, false)) + return false; + + if (spreader.Enabled == false) return false; + + if (!_mapManager.TryGetGrid(transform.GridID, out var grid)) return false; + + var didGrow = false; + + for (var i = 0; i < 4; i++) + { + var direction = (DirectionFlag) (1 << i); + var coords = transform.Coordinates.Offset(direction.AsDir().ToVec()); + if (grid.GetTileRef(coords).Tile.IsEmpty || _robustRandom.Prob(spreader.Chance)) continue; + var ents = grid.GetLocal(coords); + + if (ents.Any(x => IsTileBlockedFrom(x, direction))) continue; + + // Ok, spawn a plant + didGrow = true; + EntityManager.SpawnEntity(spreader.GrowthResult, transform.Coordinates.Offset(direction.AsDir().ToVec())); + } + + return didGrow; + } + + public void EnableSpreader(EntityUid ent, SpreaderComponent? component = null) + { + if (!Resolve(ent, ref component)) + return; + component.Enabled = true; + _edgeGrowths.Add(ent); + } + + private bool IsTileBlockedFrom(EntityUid ent, DirectionFlag dir) + { + if (EntityManager.TryGetComponent(ent, out _)) + return true; + + if (!EntityManager.TryGetComponent(ent, out var airtight)) + return false; + + var oppositeDir = dir.AsDir().GetOpposite().ToAtmosDirection(); + + return airtight.AirBlocked && airtight.AirBlockedDirection.IsFlagSet(oppositeDir); + } +} diff --git a/Content.Server/StationEvents/Events/GasLeak.cs b/Content.Server/StationEvents/Events/GasLeak.cs index 8f5313b12f..8f306e1c49 100644 --- a/Content.Server/StationEvents/Events/GasLeak.cs +++ b/Content.Server/StationEvents/Events/GasLeak.cs @@ -92,7 +92,7 @@ namespace Content.Server.StationEvents.Events base.Startup(); // Essentially we'll pick out a target amount of gas to leak, then a rate to leak it at, then work out the duration from there. - if (TryFindRandomTile(out _targetTile)) + if (TryFindRandomTile(out _targetTile, out _targetStation, out _targetGrid, out _targetCoords)) { _foundTile = true; @@ -168,40 +168,5 @@ namespace Content.Server.StationEvents.Events SoundSystem.Play(Filter.Pvs(_targetCoords), "/Audio/Effects/sparks4.ogg", _targetCoords); } } - - private bool TryFindRandomTile(out Vector2i tile) - { - tile = default; - - _targetStation = _robustRandom.Pick(_entityManager.EntityQuery().ToArray()).Station; - var possibleTargets = _entityManager.EntityQuery() - .Where(x => x.Station == _targetStation).ToArray(); - _targetGrid = _robustRandom.Pick(possibleTargets).Owner; - - if (!_entityManager.TryGetComponent(_targetGrid!.Uid, out var gridComp)) - return false; - var grid = gridComp.Grid; - - var atmosphereSystem = EntitySystem.Get(); - var found = false; - var gridBounds = grid.WorldBounds; - var gridPos = grid.WorldPosition; - - for (var i = 0; i < 10; i++) - { - var randomX = _robustRandom.Next((int) gridBounds.Left, (int) gridBounds.Right); - var randomY = _robustRandom.Next((int) gridBounds.Bottom, (int) gridBounds.Top); - - tile = new Vector2i(randomX - (int) gridPos.X, randomY - (int) gridPos.Y); - if (atmosphereSystem.IsTileSpace(grid, tile) || atmosphereSystem.IsTileAirBlocked(grid, tile)) continue; - found = true; - _targetCoords = grid.GridTileToLocal(tile); - break; - } - - if (!found) return false; - - return true; - } } } diff --git a/Content.Server/StationEvents/Events/KudzuGrowth.cs b/Content.Server/StationEvents/Events/KudzuGrowth.cs new file mode 100644 index 0000000000..2b3b730693 --- /dev/null +++ b/Content.Server/StationEvents/Events/KudzuGrowth.cs @@ -0,0 +1,55 @@ +using Content.Shared.Station; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Localization; +using Robust.Shared.Log; +using Robust.Shared.Map; +using Robust.Shared.Maths; +using Robust.Shared.Random; + +namespace Content.Server.StationEvents.Events; + +public class KudzuGrowth : StationEvent +{ + [Dependency] private IRobustRandom _robustRandom = default!; + [Dependency] private IEntityManager _entityManager = default!; + + public override string Name => "KudzuGrowth"; + + public override string? StartAnnouncement => + Loc.GetString("station-event-kudzu-growth-start-announcement"); + + public override string? StartAudio => "/Audio/Announcements/bloblarm.ogg"; + + public override int EarliestStart => 15; + + public override int MinimumPlayers => 15; + + // Get players to scatter a bit looking for it. + protected override float StartAfter => 50f; + + // Give crew at least 9 minutes to either have it gone, or to suffer another event. Kudzu is not actually required to be dead for another event to roll. + protected override float EndAfter => 60*4; + + private IEntity? _targetGrid; + + private Vector2i _targetTile; + + private EntityCoordinates _targetCoords; + + public override void Startup() + { + base.Startup(); + + // Pick a place to plant the kudzu. + if (TryFindRandomTile(out _targetTile, out _, out _targetGrid, out _targetCoords, _robustRandom, _entityManager)) + { + _entityManager.SpawnEntity("Kudzu", _targetCoords); + Logger.InfoS("stationevents", $"Spawning a Kudzu at {_targetTile} on {_targetGrid}"); + } + + // If the kudzu tile selection fails we just let the announcement happen anyways because it's funny and people + // will be hunting the non-existent, dangerous plant. + } + +} diff --git a/Content.Server/StationEvents/Events/StationEvent.cs b/Content.Server/StationEvents/Events/StationEvent.cs index 770f36e774..edd085d499 100644 --- a/Content.Server/StationEvents/Events/StationEvent.cs +++ b/Content.Server/StationEvents/Events/StationEvent.cs @@ -1,11 +1,18 @@ +using System.Linq; using Content.Server.Administration.Logs; +using Content.Server.Atmos.EntitySystems; using Content.Server.Chat.Managers; +using Content.Server.Station; using Content.Shared.Administration.Logs; +using Content.Shared.Station; using Content.Shared.Database; using Robust.Shared.Audio; using Robust.Shared.GameObjects; using Robust.Shared.IoC; +using Robust.Shared.Map; +using Robust.Shared.Maths; using Robust.Shared.Player; +using Robust.Shared.Random; namespace Content.Server.StationEvents.Events { @@ -178,5 +185,45 @@ namespace Content.Server.StationEvents.Events Running = false; } } + + + public static bool TryFindRandomTile(out Vector2i tile, out StationId targetStation, out IEntity? targetGrid, out EntityCoordinates targetCoords, IRobustRandom? robustRandom = null, IEntityManager? entityManager = null) + { + tile = default; + robustRandom ??= IoCManager.Resolve(); + entityManager ??= IoCManager.Resolve(); + + targetCoords = EntityCoordinates.Invalid; + targetStation = robustRandom.Pick(entityManager.EntityQuery().ToArray()).Station; + var t = targetStation; // thanks C# + var possibleTargets = entityManager.EntityQuery() + .Where(x => x.Station == t).ToArray(); + targetGrid = robustRandom.Pick(possibleTargets).Owner; + + if (!entityManager.TryGetComponent(targetGrid!.Uid, out var gridComp)) + return false; + var grid = gridComp.Grid; + + var atmosphereSystem = EntitySystem.Get(); + var found = false; + var gridBounds = grid.WorldBounds; + var gridPos = grid.WorldPosition; + + for (var i = 0; i < 10; i++) + { + var randomX = robustRandom.Next((int) gridBounds.Left, (int) gridBounds.Right); + var randomY = robustRandom.Next((int) gridBounds.Bottom, (int) gridBounds.Top); + + tile = new Vector2i(randomX - (int) gridPos.X, randomY - (int) gridPos.Y); + if (atmosphereSystem.IsTileSpace(grid, tile) || atmosphereSystem.IsTileAirBlocked(grid, tile)) continue; + found = true; + targetCoords = grid.GridTileToLocal(tile); + break; + } + + if (!found) return false; + + return true; + } } } diff --git a/Content.Server/Temperature/Components/TemperatureComponent.cs b/Content.Server/Temperature/Components/TemperatureComponent.cs index dca4e61a0c..7f2ef63e70 100644 --- a/Content.Server/Temperature/Components/TemperatureComponent.cs +++ b/Content.Server/Temperature/Components/TemperatureComponent.cs @@ -45,7 +45,7 @@ namespace Content.Server.Temperature.Components { get { - if (Owner.TryGetComponent(out var physics)) + if (Owner.TryGetComponent(out var physics) && physics.Mass != 0) { return SpecificHeat * physics.Mass; } diff --git a/Content.Shared/DrawDepth/DrawDepth.cs b/Content.Shared/DrawDepth/DrawDepth.cs index a6e1492884..a1812e752a 100644 --- a/Content.Shared/DrawDepth/DrawDepth.cs +++ b/Content.Shared/DrawDepth/DrawDepth.cs @@ -71,17 +71,22 @@ namespace Content.Shared.DrawDepth Doors = DrawDepthTag.Default + 5, + /// + /// Stuff that needs to draw over most things, but not effects, like Kudzu. + /// + Overdoors = DrawDepthTag.Default + 6, + /// /// Explosions, fire, melee swings. Whatever. /// - Effects = DrawDepthTag.Default + 6, + Effects = DrawDepthTag.Default + 7, - Ghosts = DrawDepthTag.Default + 7, + Ghosts = DrawDepthTag.Default + 8, /// /// Use this selectively if it absolutely needs to be drawn above (almost) everything else. Examples include /// the pointing arrow, the drag & drop ghost-entity, and some debug tools. /// - Overlays = DrawDepthTag.Default + 8, + Overlays = DrawDepthTag.Default + 9, } } diff --git a/Content.Shared/Kudzu/KudzuVisuals.cs b/Content.Shared/Kudzu/KudzuVisuals.cs new file mode 100644 index 0000000000..efd4e5ebd7 --- /dev/null +++ b/Content.Shared/Kudzu/KudzuVisuals.cs @@ -0,0 +1,11 @@ +using System; +using Robust.Shared.Serialization; + +namespace Content.Shared.Kudzu; + +[Serializable, NetSerializable] +public enum KudzuVisuals +{ + GrowthLevel, + Variant +} diff --git a/Content.Shared/Movement/Components/SlowContactsComponent.cs b/Content.Shared/Movement/Components/SlowContactsComponent.cs new file mode 100644 index 0000000000..0133eae515 --- /dev/null +++ b/Content.Shared/Movement/Components/SlowContactsComponent.cs @@ -0,0 +1,18 @@ +using Robust.Shared.GameObjects; +using Robust.Shared.GameStates; +using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.ViewVariables; + +namespace Content.Shared.Movement.Components; + +[NetworkedComponent, RegisterComponent] +public class SlowContactsComponent : Component +{ + public override string Name => "SlowContacts"; + + [ViewVariables, DataField("walkSpeedModifier")] + public float WalkSpeedModifier { get; private set; } = 1.0f; + + [ViewVariables, DataField("sprintSpeedModifier")] + public float SprintSpeedModifier { get; private set; } = 1.0f; +} diff --git a/Content.Shared/Movement/Components/SlowsOnContactComponent.cs b/Content.Shared/Movement/Components/SlowsOnContactComponent.cs new file mode 100644 index 0000000000..a411402813 --- /dev/null +++ b/Content.Shared/Movement/Components/SlowsOnContactComponent.cs @@ -0,0 +1,13 @@ +using Robust.Shared.GameObjects; +using Robust.Shared.GameStates; + +namespace Content.Shared.Movement.Components; + +/// +/// Exists just to listen to a single event. What a life. +/// +[NetworkedComponent, RegisterComponent] +public class SlowsOnContactComponent : Component +{ + public override string Name => "SlowsOnContact"; +} diff --git a/Content.Shared/Movement/EntitySystems/SlowContactsSystem.cs b/Content.Shared/Movement/EntitySystems/SlowContactsSystem.cs new file mode 100644 index 0000000000..93c065dec4 --- /dev/null +++ b/Content.Shared/Movement/EntitySystems/SlowContactsSystem.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using Content.Shared.Movement.Components; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Log; +using Robust.Shared.Physics.Dynamics; + +namespace Content.Shared.Movement.EntitySystems; + +public class SlowContactsSystem : EntitySystem +{ + [Dependency] private readonly SharedPhysicsSystem _physics = default!; + [Dependency] private readonly MovementSpeedModifierSystem _speedModifierSystem = default!; + + private readonly Dictionary _statusCapableInContact = new(); + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnEntityEnter); + SubscribeLocalEvent(OnEntityExit); + SubscribeLocalEvent(MovementSpeedCheck); + } + + private void MovementSpeedCheck(EntityUid uid, SlowsOnContactComponent component, RefreshMovementSpeedModifiersEvent args) + { + if (!_statusCapableInContact.ContainsKey(uid) || _statusCapableInContact[uid] <= 0) + return; + + if (!EntityManager.TryGetComponent(uid, out var physicsComponent)) + return; + + var walkSpeed = 1.0f; + var sprintSpeed = 1.0f; + + foreach (var colliding in _physics.GetCollidingEntities(physicsComponent)) + { + var ent = colliding.OwnerUid; + if (!EntityManager.TryGetComponent(ent, out var slowContactsComponent)) + continue; + + walkSpeed = Math.Min(walkSpeed, slowContactsComponent.WalkSpeedModifier); + sprintSpeed = Math.Min(sprintSpeed, slowContactsComponent.SprintSpeedModifier); + } + + args.ModifySpeed(walkSpeed, sprintSpeed); + } + + private void OnEntityExit(EntityUid uid, SlowContactsComponent component, EndCollideEvent args) + { + var otherUid = args.OtherFixture.Body.OwnerUid; + if (!EntityManager.HasComponent(otherUid) + || !EntityManager.HasComponent(otherUid)) + return; + if (!_statusCapableInContact.ContainsKey(otherUid)) + Logger.ErrorS("slowscontacts", $"The entity {otherUid} left a body ({uid}) it was never in."); + _statusCapableInContact[otherUid]--; + if (_statusCapableInContact[otherUid] == 0) + EntityManager.RemoveComponent(otherUid); + _speedModifierSystem.RefreshMovementSpeedModifiers(otherUid); + + } + + private void OnEntityEnter(EntityUid uid, SlowContactsComponent component, StartCollideEvent args) + { + var otherUid = args.OtherFixture.Body.OwnerUid; + if (!EntityManager.HasComponent(otherUid)) + return; + if (!_statusCapableInContact.ContainsKey(otherUid)) + _statusCapableInContact[otherUid] = 0; + _statusCapableInContact[otherUid]++; + if (!EntityManager.HasComponent(otherUid)) + EntityManager.AddComponent(otherUid); + _speedModifierSystem.RefreshMovementSpeedModifiers(otherUid); + } +} diff --git a/Resources/Locale/en-US/station-events/events/kudzu-growth.ftl b/Resources/Locale/en-US/station-events/events/kudzu-growth.ftl new file mode 100644 index 0000000000..e0725deaf1 --- /dev/null +++ b/Resources/Locale/en-US/station-events/events/kudzu-growth.ftl @@ -0,0 +1 @@ +station-event-kudzu-growth-start-announcement = Attention crew, we have detected a Type 2 Biological Invader on-station, that poses potentially serious threat to crew productivity. We advise you to exterminate it. diff --git a/Resources/Prototypes/Entities/Mobs/Species/human.yml b/Resources/Prototypes/Entities/Mobs/Species/human.yml index 4a01685717..cb083c54f3 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/human.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/human.yml @@ -290,7 +290,6 @@ proper: true - type: StandingState - type: DoorBumpOpener - - type: entity save: false name: Urist McHands diff --git a/Resources/Prototypes/Entities/Objects/Misc/kudzu.yml b/Resources/Prototypes/Entities/Objects/Misc/kudzu.yml new file mode 100644 index 0000000000..2fc1a89a7e --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Misc/kudzu.yml @@ -0,0 +1,62 @@ +- type: entity + id: Kudzu + name: kudzu + description: A rapidly growing, dangerous plant. WHY ARE YOU STOPPING TO LOOK AT IT?! + placement: + mode: SnapgridCenter + snap: + - Wall + components: + - type: Sprite + sprite: Objects/Misc/kudzu.rsi + state: kudzu_11 + drawdepth: Overdoors + netsync: false + - type: Appearance + visuals: + - type: KudzuVisualizer + - type: Clickable + - type: Transform + anchored: true + - type: Physics + fixtures: + - hard: false + mass: 7 + shape: + !type:PhysShapeAabb + bounds: "-0.5,-0.5,0.5,0.5" + layer: + - MobImpassable + - type: Damageable + damageModifierSet: Wood + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 10 + behaviors: + - !type:DoActsBehavior + acts: [ "Destruction" ] + - type: Temperature + heatDamage: + types: + Heat: 5 + - type: Flammable + fireSpread: true #If you walk into incredibly dense, flaming vines, you can expect to burn. + cold: + types: {} + damage: + types: + Heat: 1 + - type: Reactive + groups: + Flammable: [Touch] + Extinguish: [Touch] + - type: AtmosExposed + - type: Spreader + growthResult: Kudzu + - type: GrowingKudzu + growthTickSkipChance: 0.1666 + - type: SlowContacts + walkSpeedModifier: 0.2 + sprintSpeedModifier: 0.2 diff --git a/Resources/Prototypes/Entities/Objects/Tools/welders.yml b/Resources/Prototypes/Entities/Objects/Tools/welders.yml index 0967639ce6..716d781635 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/welders.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/welders.yml @@ -23,6 +23,7 @@ damage: types: Blunt: 10 + Heat: 10 # Hotfix to make kudzu die. - type: ItemStatus - type: RefillableSolution solution: Welder diff --git a/Resources/Textures/Objects/Misc/kudzu.rsi/kudzu_11.png b/Resources/Textures/Objects/Misc/kudzu.rsi/kudzu_11.png new file mode 100644 index 0000000000000000000000000000000000000000..68446ce656159ba7ccd1499d52b28a582da4ffb7 GIT binary patch literal 1214 zcmV;v1VQ_WP)St&AfT{-gCZl&Uel|uP~7_Uk|y1d$!oOKisP6`=5UO-rb#iWGf3zo11x_td&Wb zo&!fVyMaxgTm6?tay|htcxuq5?$389?0My^x4+kxZNRQxKI(FSS1+G+59j~2=8LD{ ze=y=faP7{$>F^%w_>X7mhyX6#{?0YL@#UAxVUrpeAG=Zu>vK;GhPY0{G{ES*35??4iLp^zSzEK_^{17bpbn<*(3!&t2-+&bpCjf-9C$@^kek=eyQSr~#vH>q zqXpn|_B`x^r!zi<4eyBR)b!7SQLj{9~!aWD>VR=fw`!JCci zjQmmM&>8sBP9H_V7J7i;Ct@hvTC7R(j6+c1A07CD{fo{2jG(BG4&1puk(8y9J#c^r zU>v`Q!s1*lD%|Q1=Xut$#QuC;9l#pW)b#oRgzpKqP;xN}>%D=x-=Hb%a-I}cAz(84{LqErKt@doDn)08Zi0P%yLKmK{ayel zKn!vrB^nJa-74W8q_?W9O98Nk*h^0Le!QY*Ix>^F+5_^-M3oUlh=@!H2Fdnzv(8rLz7?-9AOKEX>#2a~RiNW)e(a;4vsouVxm cQuZzRKmXFA&$&-51^@s607*qoM6N<$f~)>U>;M1& literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Misc/kudzu.rsi/kudzu_12.png b/Resources/Textures/Objects/Misc/kudzu.rsi/kudzu_12.png new file mode 100644 index 0000000000000000000000000000000000000000..ded87828507bc1a523936870c40ce845583b775e GIT binary patch literal 1112 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 UQ;4KJi~s-t07*qoM6N<$f(>PcOaK4? literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Misc/kudzu.rsi/kudzu_22.png b/Resources/Textures/Objects/Misc/kudzu.rsi/kudzu_22.png new file mode 100644 index 0000000000000000000000000000000000000000..dc862b11ebd2ad1bbc5f0b47983bd65832fade3b GIT binary patch literal 1343 zcmV-F1;F}=P)kp_uRSX z=Dc?%-~+?V-1px5e4O)qyoHGzJMoG;_rWQ<{N;SbddN3dKXv;(Q>lGC;qYDmzLEA` zx%s(kwC-Eu#&YR=ksrRd`lR%Kzk~lrIUK_8$xBn#SYEa5f40MOH7psx&F!ZTlBZwa z`NHvFZXEW!)c=BQZ9h-zIExPrdGF|J;rF7y1M+MAm&%ks*Y>(v&Gkw$pgUws4r@(0 z2!*$PUJ8a*hmRP3LwFI_={XI75gI)>7WAF4U_`;qI7|tb`VG8Ly~i5%Bdk9ytoW7n zv<_wUcwUXjA<`%n+?;Qu5%3(!^)fs=J?&io!G1eld*2~KSO+Af^ALXr;mGfSbA>9y z#^1KucK(w|`|8ZK3f5cA`2>;f`@_RCaq_5vp!BM-Vm*c2NJe=9zXnEET_Pbb9~phi zfIZkhGd)}3+y2HgUBdHs4HYvBzGwd0~}_qZ>U=GOfA>&wj%yi zK%Vq%ILqOIFa>cpl;c@4VD8%suCeksc(ePgZJW=`HmyT0Rn$vpC_cF7ypJ5~NB%tU zq57&_zR@XYh30eaEt!lxTW=mThEh@l7nGc>I{0KRD(2JmyiFXg%+}#H$3ITU-RIWTkU?!{E^jPb^Rx=y~v93&? z4c)Sr0q7~f9AO{y4xYPu%IO59X&N}7b(^JGNvI)*Cu@;dc>%(IC>=lccFOa1BqKd0 zd8@;Jx-^h5Pt(SXOduf?@5-1(V$o3T@oF#|S}iuEJWC2ZV0~6xQ|pvbD)$0>xVO=< zRWEMT*GpW25x8eDGb}WtUV|esXF2Zmfl~>755{Wk_uUn+JPm74Fn>?2xuKvYlI z1XmXXYB>m5@8K0LQzEi9D9-u3keHC9cEXJZGw!XXfY5*NKjB3ZMwa4XU_H9J5TF_;k~){ zzVGMxe%!klsO>#GN|gfmRBGIc+!X;{}_5H^N}N@xi2`&!ER5V{b!c%%Xc_HOs?f@i3)q8UuN zryeR5hdelAqHubtaCf>Egw#$^tZPt=ar^oHoj*qag5qpZ-Z+=#C9lA;tEa5~hqbWM zSyzo4h-hDp;5`pa;9)uiJK`^1O3XiqE@$c?OpDw0Ijq2yOe>X zehgy#W8gU@Oa;-uS~{I@dV-7@DtPADV87XYVz(&|&zYDHK}lH&;|wALB?1d^2gOB5 zy+q+!B^%p!n45$BzM8VyGCYY|d+KnQw*VdEP6VS^L4X2DDOPZQbKV&MUg#T|lFkDZ z1m>~za5d=9LODfnr$g|>vsb3?NM`_~pkNi}10MKx$>bd`3fHuD3NSrz5Fe1%r|U$q z<~iI6my_X289~K1C-tT~zsbbHy#iq9W4%yG9{@A1gglqNT4X?}IN}*;lVNpqOCT6bJZ*O^KM%{WxWv0@N%^U|fcY3Aj|N8fpW9#f;E>oL9rx z)}cs}r75-5GK@9WaMuf@s?pVI06fZ!DW{&TX=>Hlk!029unkXH9o5v`<ev)D* ztF3zlg2n0-L$`C7)Lf^>%GIn<=Gu&O@+qhFg&tOHt=J72NAz(vE0Ct5D$P;rNwsfa z{cBUsvMh7LqK7$iB%E$2i1Fv#KQa6;NKvLDQt!cJ*(=dEJ`nVcy5|wk!PG`K^@=)1Ly~_ywZI@z2X62 zt831?Ju+rxMOV!#?Qx3xIzTXL6$61`4eCuL8({s>sLdW5w3Z&lC52Zr9YR#GU@8uk zs8D13tXorybVBA8jTBeb7_5+?x9NXQ#ca{aBx^2=m2fj+)P%AO{0r`l7AO`61{VMT N002ovPDHLkV1n-$MZN$4 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Misc/kudzu.rsi/kudzu_31.png b/Resources/Textures/Objects/Misc/kudzu.rsi/kudzu_31.png new file mode 100644 index 0000000000000000000000000000000000000000..cc226a2721907749ba4c897600a2f8e8b5bca416 GIT binary patch literal 1535 zcmV)8cYa%7f}k&!=Hs$E zwEy>h$2|&}%)Y4iM&23`IAWFJKNMq)4IdYA#^ufr3uUMQpUf;|RB-iy)VF^Vc>46@ z(Qs(9bVsd*lHK~Bx|e@_G`!#1YZ*;P8g6eO>Q*$dh-k_ELH4@SL%9(l`xTHncwqHG z-}6#QDaXa)&$ophmAw#ztyJ03d$J)i0q?vcSU_dsz4I}{1!JJE?(ShlxR~>oxF{T6 zZ2CQhCjWjxVLse$38*^dGzt*UIW!5qh1&9?{@3r}cLaeprV!qA)>9EYhjL!Be1U;g z*$Kjd9K3CM@2w++lpK;Y0@PT5vTJWN2&bauU;a=5ZEsiNOEXhq`9cEH5gJcJfE5&1 zeW-|@9pWt8#!KmB0}iBn};>xfUWkj1CXTy;>v02~gA9k*U|D@$W%=ZSt(1 zvFB{a;3*8}dn`-f`ciAJ>9d1r9wE24YC|E#x{lqAI}!84@5)j*{fgi`L%@)s;FLW{ zv;B`W6f!QMZqP`O5@**<4T;nfcUbe*L&}#pTyzR301FV`yVy|S*x#*Y)F>E)w4(PU z-Wv`CY@*2tKxQxh-gT*mD{omWN-ZRSv-IJ+fevI2w*)s6>^py5K>%bh${eoy_BKBoH%-pyZcH=~3Bw%?~c{l5_5o=deb(rJz89||qk^5fc-S%LRl zI2%ea3k;qC56A=W0W+qcPQ?i!(t6N06}+6jJfvlvo)S?Ym{uMTz#-Or63*6u1kAdU zCAeBykim?2OKK42lG>jvSm z)K~_tMaWMILV7WlY)Du+M?{-S?eR)t^b82M9d7dUg)sfnQ*sK9guz98A)+81tz37r z%CUe9Yk2dCB*wE5=Q+ctf~7S(!!BNEVN3mk^*$U=^oK@f5umV*y42G5_aZ-#Qp^EN~9jD5iT zgF~cK)uS%RX_ACC-hhvM7$2>bFC&qFdh9MMW zYeA=RTYS8g;XX4s8&W2}1!x$W+kt6z&kQ83c(vws%y}$4fUGoA4&Mq;q=$S1vL74F l^+fJhcGSV5|Hh!4{{UTs*ZG#21TFvo002ovPDHLkV1fn^+?N0V literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Misc/kudzu.rsi/kudzu_32.png b/Resources/Textures/Objects/Misc/kudzu.rsi/kudzu_32.png new file mode 100644 index 0000000000000000000000000000000000000000..6640797aad08b285299bebd7e2776021b24a2811 GIT binary patch literal 1634 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