diff --git a/Content.Server/Explosion/EntitySystems/TriggerSystem.cs b/Content.Server/Explosion/EntitySystems/TriggerSystem.cs index 92e065bf4c..b643f207eb 100644 --- a/Content.Server/Explosion/EntitySystems/TriggerSystem.cs +++ b/Content.Server/Explosion/EntitySystems/TriggerSystem.cs @@ -208,7 +208,7 @@ namespace Content.Server.Explosion.EntitySystems private void OnTriggerCollide(EntityUid uid, TriggerOnCollideComponent component, ref StartCollideEvent args) { if (args.OurFixtureId == component.FixtureID && (!component.IgnoreOtherNonHard || args.OtherFixture.Hard)) - Trigger(uid); + Trigger(uid, args.OtherEntity); // CP14 Other Entity user } private void OnSpawnTriggered(EntityUid uid, TriggerOnSpawnComponent component, MapInitEvent args) diff --git a/Content.Server/_CP14/PVS/CP14ConstrainSpawnerSystem.cs b/Content.Server/_CP14/PVS/CP14ConstrainSpawnerSystem.cs new file mode 100644 index 0000000000..67989f3f16 --- /dev/null +++ b/Content.Server/_CP14/PVS/CP14ConstrainSpawnerSystem.cs @@ -0,0 +1,81 @@ +using System.Numerics; +using Content.Server.Explosion.EntitySystems; +using Content.Server.GameTicking; +using Content.Shared.Ghost; +using Content.Shared.Mind; +using Content.Shared.Mind.Components; +using MyNamespace; +using Robust.Shared.Random; + +namespace Content.Server._CP14.PVS; + +public sealed class CP14ConstrainSpawnerSystem : EntitySystem +{ + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly GameTicker _ticker = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnSpawnTrigger); + } + + private void OnSpawnTrigger(Entity spawner, ref TriggerEvent args) + { + if (HasComp(args.User))//TODO replace with Whitelist system? + return; + + if (!TryComp(args.User, out var mind)) + return; + + if (!mind.HasMind) + return; + + TrySpawn(spawner); + } + + private void UpdateSpawned(Entity spawner) + { + foreach (var spawned in spawner.Comp.Spawned) + { + if (!EntityManager.EntityExists(spawned)) + spawner.Comp.Spawned.Remove(spawned); + } + } + + private bool CanSpawn(Entity spawner) + { + UpdateSpawned(spawner); + return spawner.Comp.Spawned.Count < spawner.Comp.MaxCount; + } + + private void TrySpawn(Entity spawner) + { + if (CanSpawn(spawner)) + Spawn(spawner); + } + + private void Spawn(Entity spawner) + { + if (!_random.Prob(spawner.Comp.Chance)) + return; + + if (spawner.Comp.Prototypes.Count == 0) + { + Log.Warning($"Prototype list in ConditionalSpawnComponent is empty! Entity: {ToPrettyString(spawner)}"); + return; + } + + if (Deleted(spawner)) + return; + + var offset = spawner.Comp.Offset; + var xOffset = _random.NextFloat(-offset, offset); + var yOffset = _random.NextFloat(-offset, offset); + + var coordinates = Transform(spawner).Coordinates.Offset(new Vector2(xOffset, yOffset)); + + spawner.Comp.Spawned.Add(EntityManager.SpawnEntity(_random.Pick(spawner.Comp.Prototypes), coordinates)); + } +} diff --git a/Content.Server/_CP14/PVS/CP14ConstrainedSpawnerOnTriggerComponent.cs b/Content.Server/_CP14/PVS/CP14ConstrainedSpawnerOnTriggerComponent.cs new file mode 100644 index 0000000000..6e40dac87a --- /dev/null +++ b/Content.Server/_CP14/PVS/CP14ConstrainedSpawnerOnTriggerComponent.cs @@ -0,0 +1,25 @@ +using Content.Server._CP14.PVS; +using Robust.Shared.Prototypes; + +namespace MyNamespace; + +/// +/// A spawner that clearly controls how many entities it can spawn. +/// +[RegisterComponent, Access(typeof(CP14ConstrainSpawnerSystem))] +public sealed partial class CP14ConstrainedSpawnerOnTriggerComponent : Component +{ + [DataField] + public List Prototypes { get; set; } = new(); + + public HashSet Spawned = new(); + + [DataField] + public float Chance { get; set; } = 1.0f; + + [DataField] + public float Offset { get; set; } = 0.2f; + + [DataField] + public int MaxCount = 1; +} diff --git a/Content.Server/_CP14/PVS/CP14HelperPvsSystem.cs b/Content.Server/_CP14/PVS/CP14HelperPvsSystem.cs new file mode 100644 index 0000000000..22891d7f78 --- /dev/null +++ b/Content.Server/_CP14/PVS/CP14HelperPvsSystem.cs @@ -0,0 +1,63 @@ +using Content.Server.Administration.Logs; +using Content.Shared.Database; +using Content.Shared.Ghost; +using Content.Shared.Mind.Components; +using Robust.Shared; +using Robust.Shared.Timing; + +namespace Content.Server._CP14.PVS; + +public sealed partial class CP14HelperPvsSystem : EntitySystem +{ + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly IAdminLogManager _adminLogger = default!; + + private static readonly TimeSpan UpdateFrequency = TimeSpan.FromSeconds(5f); + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var pvs)) + { + if (_timing.CurTime <= pvs.NextUpdate) + continue; + + pvs.NextUpdate = _timing.CurTime + UpdateFrequency; + + if (InPvs(uid)) + { + pvs.DespawnAttempt = pvs.MaxDespawnAttempt; + } + else + { + pvs.DespawnAttempt--; + } + + if (pvs.DespawnAttempt <= 0) + { + _adminLogger.Add(LogType.EntityDelete, LogImpact.Medium, $"{ToPrettyString(uid):entity} was out of the players' pvs for too long and was deleted"); + QueueDel(uid); + } + } + } + + public bool InPvs(EntityUid uid) + { + var nearMinds = _lookup.GetEntitiesInRange(Transform(uid).Coordinates, CVars.NetMaxUpdateRange.DefaultValue / 2); + foreach (var mind in nearMinds) + { + if (HasComp(mind)) + continue; + + if (!mind.Comp.HasMind) + continue; + + return true; + } + + return false; + } +} diff --git a/Content.Server/_CP14/PVS/CP14OutPVSDespawnComponent.cs b/Content.Server/_CP14/PVS/CP14OutPVSDespawnComponent.cs new file mode 100644 index 0000000000..f0238afa5e --- /dev/null +++ b/Content.Server/_CP14/PVS/CP14OutPVSDespawnComponent.cs @@ -0,0 +1,17 @@ +namespace Content.Server._CP14.PVS; + +/// +/// Deletes an entity if it stays out of the players' pvs for too long +/// +[RegisterComponent, Access(typeof(CP14HelperPvsSystem)), AutoGenerateComponentPause] +public sealed partial class CP14OutPVSDespawnComponent : Component +{ + [DataField] + public int MaxDespawnAttempt = 3; + + [DataField] + public int DespawnAttempt = 3; + + [DataField, AutoPausedField] + public TimeSpan NextUpdate = TimeSpan.Zero; +} diff --git a/Resources/Prototypes/_CP14/Entities/Markers/Spawners/Constrained/test_xeno.yml b/Resources/Prototypes/_CP14/Entities/Markers/Spawners/Constrained/test_xeno.yml new file mode 100644 index 0000000000..28907bf8fb --- /dev/null +++ b/Resources/Prototypes/_CP14/Entities/Markers/Spawners/Constrained/test_xeno.yml @@ -0,0 +1,37 @@ +- type: entity + id: CP14ConstrainedSpawnerBase + abstract: true + parent: MarkerBase + components: + - type: Sprite + layers: + - state: red + - type: Physics + bodyType: Static + fixedRotation: true + - type: Fixtures + fixtures: + spawn: + shape: + !type:PhysShapeCircle + radius: 15 + hard: false + layer: + - AllMask + - type: TriggerOnCollide + fixtureID: spawn + - type: StepTrigger + requiredTriggeredSpeed: 0 + stepOn: true + +- type: entity + parent: CP14ConstrainedSpawnerBase + id: CP14ConstrainedSpawnerXeno + components: + - type: TriggerOnCollide + fixtureID: spawn + - type: CP14ConstrainedSpawnerOnTrigger + prototypes: + - CP14MobXeno + - CP14MobXenoPraetorian + - CP14MobXenoDrone \ No newline at end of file diff --git a/Resources/Prototypes/_CP14/Entities/Mobs/NPC/xeno_test.yml b/Resources/Prototypes/_CP14/Entities/Mobs/NPC/xeno_test.yml new file mode 100644 index 0000000000..26ffe557d4 --- /dev/null +++ b/Resources/Prototypes/_CP14/Entities/Mobs/NPC/xeno_test.yml @@ -0,0 +1,17 @@ +- type: entity + id: CP14MobXeno + parent: MobXeno + components: + - type: CP14OutPVSDespawn + +- type: entity + id: CP14MobXenoPraetorian + parent: MobXenoPraetorian + components: + - type: CP14OutPVSDespawn + +- type: entity + id: CP14MobXenoDrone + parent: MobXenoDrone + components: + - type: CP14OutPVSDespawn \ No newline at end of file