{
SubscribeLocalEvent<EntitySpawnAnomalyComponent, AnomalyPulseEvent>(OnPulse);
SubscribeLocalEvent<EntitySpawnAnomalyComponent, AnomalySupercriticalEvent>(OnSupercritical);
+ SubscribeLocalEvent<EntitySpawnAnomalyComponent, AnomalyStabilityChangedEvent>(OnStabilityChanged);
}
private void OnPulse(EntityUid uid, EntitySpawnAnomalyComponent component, ref AnomalyPulseEvent args)
{
+ if (!component.SpawnOnPulse)
+ return;
+
var range = component.SpawnRange * args.Stability;
var amount = (int) (component.MaxSpawnAmount * args.Severity + 0.5f);
var xform = Transform(uid);
- SpawnMonstersOnOpenTiles(component, xform, amount, range, component.Spawns);
+ SpawnEntitesOnOpenTiles(component, xform, amount, range, component.Spawns);
}
private void OnSupercritical(EntityUid uid, EntitySpawnAnomalyComponent component, ref AnomalySupercriticalEvent args)
{
+ if (!component.SpawnOnSuperCritical)
+ return;
+
var xform = Transform(uid);
- // A cluster of monsters
- SpawnMonstersOnOpenTiles(component, xform, component.MaxSpawnAmount, component.SpawnRange, component.Spawns);
+ // A cluster of entities
+ SpawnEntitesOnOpenTiles(component, xform, component.MaxSpawnAmount, component.SpawnRange, component.Spawns);
// And so much meat (for the meat anomaly at least)
- SpawnMonstersOnOpenTiles(component, xform, component.MaxSpawnAmount, component.SpawnRange, component.SuperCriticalSpawns);
+ SpawnEntitesOnOpenTiles(component, xform, component.MaxSpawnAmount, component.SpawnRange, component.SuperCriticalSpawns);
+ }
+
+ private void OnStabilityChanged(EntityUid uid, EntitySpawnAnomalyComponent component, ref AnomalyStabilityChangedEvent args)
+ {
+ if (!component.SpawnOnStabilityChanged)
+ return;
+
+ var range = component.SpawnRange * args.Stability;
+ var amount = (int) (component.MaxSpawnAmount * args.Stability + 0.5f);
+
+ var xform = Transform(uid);
+ SpawnEntitesOnOpenTiles(component, xform, amount, range, component.Spawns);
}
- private void SpawnMonstersOnOpenTiles(EntitySpawnAnomalyComponent component, TransformComponent xform, int amount, float radius, List<EntProtoId> spawns)
+ private void SpawnEntitesOnOpenTiles(EntitySpawnAnomalyComponent component, TransformComponent xform, int amount, float radius, List<EntProtoId> spawns)
{
if (!component.Spawns.Any())
return;
-using Content.Shared.Maps;
using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
namespace Content.Shared.Anomaly.Effects.Components;
public float SpawnRange = 5f;
/// <summary>
- /// The tile that is spawned by the anomaly's effect
+ /// Whether or not anomaly spawns entities on Pulse
/// </summary>
- [DataField("floorTileId", customTypeSerializer: typeof(PrototypeIdSerializer<ContentTileDefinition>)), ViewVariables(VVAccess.ReadWrite)]
- public string FloorTileId = "FloorFlesh";
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public bool SpawnOnPulse = true;
+
+ /// <summary>
+ /// Whether or not anomaly spawns entities on SuperCritical
+ /// </summary>
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public bool SpawnOnSuperCritical = true;
+
+ /// <summary>
+ /// Whether or not anomaly spawns entities on StabilityChanged
+ /// The idea was to spawn entities either on Pulse/Supercritical OR StabilityChanged
+ /// </summary>
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public bool SpawnOnStabilityChanged = false;
}
--- /dev/null
+using Content.Shared.Movement.Systems;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Movement.Components;
+
+[NetworkedComponent, RegisterComponent]
+[AutoGenerateComponentState]
+[Access(typeof(FrictionContactsSystem))]
+public sealed partial class FrictionContactsComponent : Component
+{
+ /// <summary>
+ /// Modified mob friction while on FrictionContactsComponent
+ /// </summary>
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ [AutoNetworkedField]
+ public float MobFriction = 0.5f;
+
+ /// <summary>
+ /// Modified mob friction without input while on FrictionContactsComponent
+ /// </summary>
+ [AutoNetworkedField]
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public float MobFrictionNoInput = 0.05f;
+
+ /// <summary>
+ /// Modified mob acceleration while on FrictionContactsComponent
+ /// </summary>
+ [AutoNetworkedField]
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public float MobAcceleration = 2.0f;
+}
/// <summary>
/// The acceleration applied to mobs when moving.
/// </summary>
- [ViewVariables(VVAccess.ReadWrite), DataField]
+ [AutoNetworkedField, ViewVariables(VVAccess.ReadWrite), DataField]
public float Acceleration = DefaultAcceleration;
/// <summary>
/// The negative velocity applied for friction.
/// </summary>
- [ViewVariables(VVAccess.ReadWrite), DataField]
+ [AutoNetworkedField, ViewVariables(VVAccess.ReadWrite), DataField]
public float Friction = DefaultFriction;
/// <summary>
/// The negative velocity applied for friction.
/// </summary>
- [ViewVariables(VVAccess.ReadWrite), DataField]
+ [AutoNetworkedField, ViewVariables(VVAccess.ReadWrite), DataField]
public float? FrictionNoInput;
[ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField]
--- /dev/null
+using Content.Shared.Movement.Components;
+using Robust.Shared.Physics.Components;
+using Robust.Shared.Physics.Events;
+using Robust.Shared.Physics.Systems;
+
+namespace Content.Shared.Movement.Systems;
+
+public sealed class FrictionContactsSystem : EntitySystem
+{
+ [Dependency] private readonly SharedPhysicsSystem _physics = default!;
+ [Dependency] private readonly MovementSpeedModifierSystem _speedModifierSystem = default!;
+
+ // Comment copied from "original" SlowContactsSystem.cs
+ // TODO full-game-save
+ // Either these need to be processed before a map is saved, or slowed/slowing entities need to update on init.
+ private HashSet<EntityUid> _toUpdate = new();
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent<FrictionContactsComponent, StartCollideEvent>(OnEntityEnter);
+ SubscribeLocalEvent<FrictionContactsComponent, EndCollideEvent>(OnEntityExit);
+ SubscribeLocalEvent<FrictionContactsComponent, ComponentShutdown>(OnShutdown);
+
+ UpdatesAfter.Add(typeof(SharedPhysicsSystem));
+ }
+
+ private void OnEntityEnter(EntityUid uid, FrictionContactsComponent component, ref StartCollideEvent args)
+ {
+ var otherUid = args.OtherEntity;
+
+ if (!HasComp(otherUid, typeof(MovementSpeedModifierComponent)))
+ return;
+
+ _toUpdate.Add(otherUid);
+ }
+
+ private void OnEntityExit(EntityUid uid, FrictionContactsComponent component, ref EndCollideEvent args)
+ {
+ var otherUid = args.OtherEntity;
+
+ if (!HasComp(otherUid, typeof(MovementSpeedModifierComponent)))
+ return;
+
+ _toUpdate.Add(otherUid);
+ }
+
+ private void OnShutdown(EntityUid uid, FrictionContactsComponent component, ComponentShutdown args)
+ {
+ if (!TryComp(uid, out PhysicsComponent? phys))
+ return;
+
+ _toUpdate.UnionWith(_physics.GetContactingEntities(uid, phys));
+ }
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ foreach (var uid in _toUpdate)
+ {
+ ApplyFrictionChange(uid);
+ }
+
+ _toUpdate.Clear();
+ }
+
+ private void ApplyFrictionChange(EntityUid uid)
+ {
+ if (!EntityManager.TryGetComponent<PhysicsComponent>(uid, out var physicsComponent))
+ return;
+
+ if (!TryComp(uid, out MovementSpeedModifierComponent? speedModifier))
+ return;
+
+ FrictionContactsComponent? frictionComponent = TouchesFrictionContactsComponent(uid, physicsComponent);
+
+ if (frictionComponent == null)
+ {
+ _speedModifierSystem.ChangeFriction(uid, MovementSpeedModifierComponent.DefaultFriction, null, MovementSpeedModifierComponent.DefaultAcceleration, speedModifier);
+ }
+ else
+ {
+ _speedModifierSystem.ChangeFriction(uid, frictionComponent.MobFriction, frictionComponent.MobFrictionNoInput, frictionComponent.MobAcceleration, speedModifier);
+ }
+ }
+
+ private FrictionContactsComponent? TouchesFrictionContactsComponent(EntityUid uid, PhysicsComponent physicsComponent)
+ {
+ foreach (var ent in _physics.GetContactingEntities(uid, physicsComponent))
+ {
+ if (!TryComp(ent, out FrictionContactsComponent? frictionContacts))
+ continue;
+
+ return frictionContacts;
+ }
+
+ return null;
+ }
+}
move.WalkSpeedModifier = ev.WalkSpeedModifier;
move.SprintSpeedModifier = ev.SprintSpeedModifier;
- Dirty(move);
+ Dirty(uid, move);
}
public void ChangeBaseSpeed(EntityUid uid, float baseWalkSpeed, float baseSprintSpeed, float acceleration, MovementSpeedModifierComponent? move = null)
move.BaseWalkSpeed = baseWalkSpeed;
move.BaseSprintSpeed = baseSprintSpeed;
move.Acceleration = acceleration;
- Dirty(move);
+ Dirty(uid, move);
+ }
+
+ // We might want to create separate RefreshMovementFrictionModifiersEvent and RefreshMovementFrictionModifiers function that will call it
+ public void ChangeFriction(EntityUid uid, float friction, float? frictionNoInput, float acceleration, MovementSpeedModifierComponent? move = null)
+ {
+ if (!Resolve(uid, ref move, false))
+ return;
+
+ move.Friction = friction;
+ move.FrictionNoInput = frictionNoInput;
+ move.Acceleration = acceleration;
+ Dirty(uid, move);
}
}
--- /dev/null
+- type: entity
+ id: IceCrust
+ name: ice crust
+ description: It's cold and slippery.
+ placement:
+ mode: SnapgridCenter
+ snap:
+ - Wall
+ components:
+ - type: MeleeSound
+ soundGroups:
+ Brute:
+ path:
+ "/Audio/Weapons/slash.ogg"
+ - type: Sprite
+ sprite: Objects/Misc/ice_crust.rsi
+ layers:
+ - state: ice
+ drawdepth: FloorObjects
+ color: "#ffffff44"
+ - type: Clickable
+ - type: Transform
+ anchored: true
+ - type: Physics
+ - type: Fixtures
+ fixtures:
+ fix1:
+ hard: false
+ density: 7
+ shape:
+ !type:PhysShapeAabb
+ bounds: "-0.4,-0.4,0.4,0.4"
+ layer:
+ - MidImpassable
+ - type: Damageable
+ damageModifierSet: Wood
+ - type: Destructible
+ thresholds:
+ - trigger:
+ !type:DamageTrigger
+ damage: 10
+ behaviors:
+ - !type:DoActsBehavior
+ acts: [ "Destruction" ]
+ - type: Temperature
+ heatDamage:
+ types:
+ Heat: 5
+ coldDamage: {}
+ ColdDamageThreshold: 0
+ - type: FrictionContacts
+
\ No newline at end of file
- type: ProjectileAnomaly
projectilePrototype: ProjectileIcicle
targetNonSentientChance: 0.1
+ - type: EntitySpawnAnomaly
+ spawns:
+ - IceCrust
+ maxSpawnAmount: 17
+ spawnOnPulse: false
+ spawnOnSuperCritical: false
+ spawnOnStabilityChanged: true
- type: TempAffectingAnomaly
tempChangePerSecond: -25
hotspotExposeTemperature: -1000
--- /dev/null
+{
+ "version": 1,
+ "license": "CC-BY-SA-3.0",
+ "copyright": "Taken from space-station-14+Resources+Textures+Tiles+Planet+Snow",
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "states": [
+ {
+ "name": "ice"
+ }
+ ]
+}
\ No newline at end of file