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 0000000000..68446ce656 Binary files /dev/null and b/Resources/Textures/Objects/Misc/kudzu.rsi/kudzu_11.png differ 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 0000000000..ded8782850 Binary files /dev/null and b/Resources/Textures/Objects/Misc/kudzu.rsi/kudzu_12.png differ diff --git a/Resources/Textures/Objects/Misc/kudzu.rsi/kudzu_13.png b/Resources/Textures/Objects/Misc/kudzu.rsi/kudzu_13.png new file mode 100644 index 0000000000..9c73d8487a Binary files /dev/null and b/Resources/Textures/Objects/Misc/kudzu.rsi/kudzu_13.png differ diff --git a/Resources/Textures/Objects/Misc/kudzu.rsi/kudzu_21.png b/Resources/Textures/Objects/Misc/kudzu.rsi/kudzu_21.png new file mode 100644 index 0000000000..6909b3c157 Binary files /dev/null and b/Resources/Textures/Objects/Misc/kudzu.rsi/kudzu_21.png differ 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 0000000000..dc862b11eb Binary files /dev/null and b/Resources/Textures/Objects/Misc/kudzu.rsi/kudzu_22.png differ diff --git a/Resources/Textures/Objects/Misc/kudzu.rsi/kudzu_23.png b/Resources/Textures/Objects/Misc/kudzu.rsi/kudzu_23.png new file mode 100644 index 0000000000..056ec9adfb Binary files /dev/null and b/Resources/Textures/Objects/Misc/kudzu.rsi/kudzu_23.png differ 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 0000000000..cc226a2721 Binary files /dev/null and b/Resources/Textures/Objects/Misc/kudzu.rsi/kudzu_31.png differ 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 0000000000..6640797aad Binary files /dev/null and b/Resources/Textures/Objects/Misc/kudzu.rsi/kudzu_32.png differ diff --git a/Resources/Textures/Objects/Misc/kudzu.rsi/kudzu_33.png b/Resources/Textures/Objects/Misc/kudzu.rsi/kudzu_33.png new file mode 100644 index 0000000000..67713bf54c Binary files /dev/null and b/Resources/Textures/Objects/Misc/kudzu.rsi/kudzu_33.png differ diff --git a/Resources/Textures/Objects/Misc/kudzu.rsi/meta.json b/Resources/Textures/Objects/Misc/kudzu.rsi/meta.json new file mode 100644 index 0000000000..33d2647c68 --- /dev/null +++ b/Resources/Textures/Objects/Misc/kudzu.rsi/meta.json @@ -0,0 +1,38 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "https://github.com/tgstation/tgstation/commit/15bf91049e33979a855995579b48592e34bcdd8c", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "kudzu_33" + }, + { + "name": "kudzu_32" + }, + { + "name": "kudzu_31" + }, + { + "name": "kudzu_23" + }, + { + "name": "kudzu_22" + }, + { + "name": "kudzu_21" + }, + { + "name": "kudzu_13" + }, + { + "name": "kudzu_12" + }, + { + "name": "kudzu_11" + } + ] +} \ No newline at end of file