Files
wwdpublic/Content.Shared/StepTrigger/Systems/StepTriggerSystem.cs
VMSolidus 91107d443c [Port] StepTriggerGroup From WhiteDream (#929)
# Description

This is a port of
https://github.com/WWhiteDreamProject/wwdpublic/pull/53 from White
Dream. This PR improves the StepTriggerImmune component by making it
operate on a more granular Blacklist system, such that StepTriggerImmune
entities can further clarify via prototypes which kinds of floor traps
they are immune to, such as landmines/mousetraps, and not have blanket
immunity to everything. Because it turns out things like Lava and Soap
also were caught by the immunity, when really we just wanted Harpies &
Felinids to not trigger landmines.

<details><summary><h1>Media</h1></summary>
<p>

> # Описание
> Необходимо настроить модификатор урона, чтобы IPC не получали урон от
осколков стекла.
>
> Иммунитет StepTriggerImmuneComponent доработан. Теперь имеются
несколько типов (types): Lava - тип тайла, наступив на который
появляется урон. Это собственно лава и LiquidPlasma Landmine - мины.
Chasm - дырка в карте, куда можно провалиться Mousetrap - Мышеловка
SlipTile - Все, что должно подскальзывать игроков, имеющее размер тайла
SlipEntity - Все, что должно подскальзывать игроков, имеющее развер
энтити. Разделено для баланса. Самые ловки могут игнорировать мелкие
предметы (энтити), т.е. уворачиваться от них. Но большие по площади вещи
(тайлы по типу разлитой воды, бананиума) просчитываются отдельно.
>
> # Изменения
> * [x]  Улучшить StepTriggerSystem (Immune)
> * [x] Добавлены типы триггера. - Lava Landmine Shard Chasm Mousetrap
SlipTile SlipEntity
> * [x]  Исправить осколки у IPC
> * [x] Исправить отсутствие урона от лавы и падение в дыры у фелинидов
и гарпий.
>
> 🆑 Hell_Cat
>
> * Feature: StepTriggerSystem is improved | Улучшена StepTriggerSystem
> * fix: IPC: Immunity for shards and SpiderWeb | Иммунитет осколкам.
> * fix: Felinid | Фелиниды : Immunity for Shard Landmine Mousetrap
SlipEntities | Иммунитет для осколков, жидкости, мин, мышеловок, мыла и
бананов.
> * fix: Harpy | Гарпия : Immunity for Shards Landmine Mousetrap |
Иммунитет для осколков, жидкости, мин и мышеловок.
> * fix: Mice | Мыши : Don't blow up on landmines | Мыши не подрываются
на минах.

</p>
</details>

# Changelog

🆑 Hell_Cat
Feature: StepTriggerSystem has been improved with new StepTriggerGroups.
Additionally, the StepTriggerImmune component now allows declaring for
specific StepTriggerGroups for a given entity to be immune to. Some
examples may be, Felinids, Mice, and Harpies being unable to set off
Landmines.

---------

Signed-off-by: VMSolidus <evilexecutive@gmail.com>
Co-authored-by: Ivan <126400932+HellCatten@users.noreply.github.com>
Co-authored-by: FoxxoTrystan <45297731+FoxxoTrystan@users.noreply.github.com>
# Conflicts:
#	Content.Shared/StepTrigger/Components/StepTriggerComponent.cs
#	Content.Shared/StepTrigger/Components/StepTriggerImmuneComponent.cs
#	Content.Shared/StepTrigger/Prototypes/StepTriggerGroup.cs
#	Content.Shared/StepTrigger/Prototypes/StepTriggerTypePrototype.cs
#	Content.Shared/StepTrigger/Systems/StepTriggerSystem.cs
#	Resources/Prototypes/Entities/Effects/chemistry_effects.yml
#	Resources/Prototypes/Entities/Mobs/NPCs/animals.yml
#	Resources/Prototypes/Entities/Mobs/Player/ipc.yml
#	Resources/Prototypes/Entities/Mobs/Species/harpy.yml
#	Resources/Prototypes/Entities/Objects/Devices/mousetrap.yml
#	Resources/Prototypes/Entities/Objects/Devices/pda.yml
#	Resources/Prototypes/Entities/Objects/Fun/dice.yml
#	Resources/Prototypes/Entities/Objects/Materials/shards.yml
#	Resources/Prototypes/Entities/Objects/Misc/land_mine.yml
#	Resources/Prototypes/Entities/Objects/Specific/Janitorial/soap.yml
#	Resources/Prototypes/Entities/Tiles/bananium.yml
#	Resources/Prototypes/Entities/Tiles/chasm.yml
#	Resources/Prototypes/Entities/Tiles/lava.yml
#	Resources/Prototypes/Entities/Tiles/liquid_plasma.yml
#	Resources/Prototypes/Nyanotrasen/Entities/Mobs/Species/felinid.yml
#	Resources/Prototypes/Traits/skills.yml
2024-10-19 13:07:45 +07:00

235 lines
8.5 KiB
C#

using Content.Shared.Gravity;
using Content.Shared.StepTrigger.Components;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Events;
namespace Content.Shared.StepTrigger.Systems;
public sealed class StepTriggerSystem : EntitySystem
{
[Dependency] private readonly EntityLookupSystem _entityLookup = default!;
[Dependency] private readonly SharedGravitySystem _gravity = default!;
[Dependency] private readonly SharedMapSystem _map = default!;
public override void Initialize()
{
UpdatesOutsidePrediction = true;
SubscribeLocalEvent<StepTriggerComponent, AfterAutoHandleStateEvent>(TriggerHandleState);
SubscribeLocalEvent<StepTriggerComponent, StartCollideEvent>(OnStartCollide);
SubscribeLocalEvent<StepTriggerComponent, EndCollideEvent>(OnEndCollide);
#if DEBUG
SubscribeLocalEvent<StepTriggerComponent, ComponentStartup>(OnStartup);
}
private void OnStartup(EntityUid uid, StepTriggerComponent component, ComponentStartup args)
{
if (!component.Active)
return;
if (!TryComp(uid, out FixturesComponent? fixtures) || fixtures.FixtureCount == 0)
Log.Warning($"{ToPrettyString(uid)} has an active step trigger without any fixtures.");
#endif
}
public override void Update(float frameTime)
{
var query = GetEntityQuery<PhysicsComponent>();
var enumerator = EntityQueryEnumerator<StepTriggerActiveComponent, StepTriggerComponent, TransformComponent>();
while (enumerator.MoveNext(out var uid, out var active, out var trigger, out var transform))
{
if (!Update(uid, trigger, transform, query))
continue;
RemCompDeferred(uid, active);
}
}
private bool Update(EntityUid uid, StepTriggerComponent component, TransformComponent transform, EntityQuery<PhysicsComponent> query)
{
if (!component.Active || component.Colliding.Count == 0)
return true;
if (component.Blacklist != null && TryComp<MapGridComponent>(transform.GridUid, out var grid))
{
var positon = _map.LocalToTile(transform.GridUid.Value, grid, transform.Coordinates);
var anch = _map.GetAnchoredEntitiesEnumerator(uid, grid, positon);
while (anch.MoveNext(out var ent))
{
if (ent == uid)
continue;
if (component.Blacklist.IsValid(ent.Value, EntityManager))
return false;
}
}
foreach (var otherUid in component.Colliding)
UpdateColliding(uid, component, transform, otherUid, query);
return false;
}
private void UpdateColliding(EntityUid uid, StepTriggerComponent component, TransformComponent ownerXform, EntityUid otherUid, EntityQuery<PhysicsComponent> query)
{
if (!query.TryGetComponent(otherUid, out var otherPhysics))
return;
var otherXform = Transform(otherUid);
// TODO: This shouldn't be calculating based on world AABBs.
var ourAabb = _entityLookup.GetAABBNoContainer(uid, ownerXform.LocalPosition, ownerXform.LocalRotation);
var otherAabb = _entityLookup.GetAABBNoContainer(otherUid, otherXform.LocalPosition, otherXform.LocalRotation);
if (!ourAabb.Intersects(otherAabb))
{
if (component.CurrentlySteppedOn.Remove(otherUid))
Dirty(uid, component);
return;
}
// max 'area of enclosure' between the two aabbs
// this is hard to explain
var intersect = Box2.Area(otherAabb.Intersect(ourAabb));
var ratio = Math.Max(intersect / Box2.Area(otherAabb), intersect / Box2.Area(ourAabb));
if (otherPhysics.LinearVelocity.Length() < component.RequiredTriggeredSpeed
|| component.CurrentlySteppedOn.Contains(otherUid)
|| ratio < component.IntersectRatio
|| !CanTrigger(uid, otherUid, component))
return;
if (component.StepOn)
{
var evStep = new StepTriggeredOnEvent(uid, otherUid);
RaiseLocalEvent(uid, ref evStep);
}
else
{
var evStep = new StepTriggeredOffEvent(uid, otherUid);
RaiseLocalEvent(uid, ref evStep);
}
component.CurrentlySteppedOn.Add(otherUid);
Dirty(uid, component);
}
private bool CanTrigger(EntityUid uid, EntityUid otherUid, StepTriggerComponent component)
{
if (!component.Active
|| component.CurrentlySteppedOn.Contains(otherUid))
return false;
// Immunity checks
if (TryComp<StepTriggerImmuneComponent>(otherUid, out var stepTriggerImmuneComponent)
&& component.TriggerGroups != null
&& component.TriggerGroups.IsValid(stepTriggerImmuneComponent))
return false;
// Can't trigger if we don't ignore weightless entities
// and the entity is flying or currently weightless
// Makes sense simulation wise to have this be part of steptrigger directly IMO
if (!component.IgnoreWeightless && TryComp<PhysicsComponent>(otherUid, out var physics) &&
(physics.BodyStatus == BodyStatus.InAir || _gravity.IsWeightless(otherUid, physics)))
return false;
var msg = new StepTriggerAttemptEvent { Source = uid, Tripper = otherUid };
RaiseLocalEvent(uid, ref msg);
return msg.Continue && !msg.Cancelled;
}
private void OnStartCollide(EntityUid uid, StepTriggerComponent component, ref StartCollideEvent args)
{
var otherUid = args.OtherEntity;
if (!args.OtherFixture.Hard
|| !CanTrigger(uid, otherUid, component))
return;
EnsureComp<StepTriggerActiveComponent>(uid);
if (component.Colliding.Add(otherUid))
Dirty(uid, component);
}
private void OnEndCollide(EntityUid uid, StepTriggerComponent component, ref EndCollideEvent args)
{
var otherUid = args.OtherEntity;
if (!component.Colliding.Remove(otherUid))
return;
component.CurrentlySteppedOn.Remove(otherUid);
Dirty(uid, component);
if (component.StepOn)
{
var evStepOff = new StepTriggeredOffEvent(uid, otherUid);
RaiseLocalEvent(uid, ref evStepOff);
}
if (component.Colliding.Count == 0)
RemCompDeferred<StepTriggerActiveComponent>(uid);
}
private void TriggerHandleState(EntityUid uid, StepTriggerComponent component, ref AfterAutoHandleStateEvent args)
{
if (component.Colliding.Count > 0)
EnsureComp<StepTriggerActiveComponent>(uid);
else
RemCompDeferred<StepTriggerActiveComponent>(uid);
}
public void SetIntersectRatio(EntityUid uid, float ratio, StepTriggerComponent? component = null)
{
if (!Resolve(uid, ref component)
|| MathHelper.CloseToPercent(component.IntersectRatio, ratio))
return;
component.IntersectRatio = ratio;
Dirty(uid, component);
}
public void SetRequiredTriggerSpeed(EntityUid uid, float speed, StepTriggerComponent? component = null)
{
if (!Resolve(uid, ref component)
|| MathHelper.CloseToPercent(component.RequiredTriggeredSpeed, speed))
return;
component.RequiredTriggeredSpeed = speed;
Dirty(uid, component);
}
public void SetActive(EntityUid uid, bool active, StepTriggerComponent? component = null)
{
if (!Resolve(uid, ref component)
|| active == component.Active)
return;
component.Active = active;
Dirty(uid, component);
}
}
/// <summary>
/// Raised at the beginning of a step trigger, and before entering the checks.
/// Allows for entities to end the steptrigger early via args.Cancelled.
/// </summary>
[ByRefEvent]
public record struct StepTriggerAttemptEvent(EntityUid Source, EntityUid Tripper, bool Continue, bool Cancelled);
/// <summary>
/// Raised when an entity stands on a steptrigger initially (assuming it has both on and off states).
/// </summary>
[ByRefEvent]
public readonly record struct StepTriggeredOnEvent(EntityUid Source, EntityUid Tripper);
/// <summary>
/// Raised when an entity leaves a steptrigger if it has on and off states OR when an entity intersects a steptrigger.
/// </summary>
[ByRefEvent]
public readonly record struct StepTriggeredOffEvent(EntityUid Source, EntityUid Tripper);