| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469 |
- using Content.Server.Administration.Logs;
- using Content.Server.Atmos.Components;
- using Content.Server.IgnitionSource;
- using Content.Server.Stunnable;
- using Content.Server.Temperature.Components;
- using Content.Server.Temperature.Systems;
- using Content.Server.Damage.Components;
- using Content.Shared.ActionBlocker;
- using Content.Shared.Alert;
- using Content.Shared.Atmos;
- using Content.Shared.Atmos.Components;
- using Content.Shared.Damage;
- using Content.Shared.Database;
- using Content.Shared.Interaction;
- using Content.Shared.Inventory;
- using Content.Shared.Physics;
- using Content.Shared.Popups;
- using Content.Shared.Projectiles;
- using Content.Shared.Rejuvenate;
- using Content.Shared.Temperature;
- using Content.Shared.Throwing;
- using Content.Shared.Timing;
- using Content.Shared.Toggleable;
- using Content.Shared.Weapons.Melee.Events;
- using Content.Shared.FixedPoint;
- using Robust.Server.Audio;
- using Robust.Shared.Physics.Components;
- using Robust.Shared.Physics.Events;
- using Robust.Shared.Physics.Systems;
- using Robust.Shared.Random;
- namespace Content.Server.Atmos.EntitySystems
- {
- public sealed class FlammableSystem : EntitySystem
- {
- [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
- [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
- [Dependency] private readonly StunSystem _stunSystem = default!;
- [Dependency] private readonly TemperatureSystem _temperatureSystem = default!;
- [Dependency] private readonly IgnitionSourceSystem _ignitionSourceSystem = default!;
- [Dependency] private readonly DamageableSystem _damageableSystem = default!;
- [Dependency] private readonly AlertsSystem _alertsSystem = default!;
- [Dependency] private readonly FixtureSystem _fixture = default!;
- [Dependency] private readonly IAdminLogManager _adminLogger = default!;
- [Dependency] private readonly InventorySystem _inventory = default!;
- [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
- [Dependency] private readonly SharedPopupSystem _popup = default!;
- [Dependency] private readonly UseDelaySystem _useDelay = default!;
- [Dependency] private readonly AudioSystem _audio = default!;
- [Dependency] private readonly IRobustRandom _random = default!;
- private EntityQuery<InventoryComponent> _inventoryQuery;
- private EntityQuery<PhysicsComponent> _physicsQuery;
- // This should probably be moved to the component, requires a rewrite, all fires tick at the same time
- private const float UpdateTime = 1f;
- private float _timer;
- private readonly Dictionary<Entity<FlammableComponent>, float> _fireEvents = new();
- public override void Initialize()
- {
- UpdatesAfter.Add(typeof(AtmosphereSystem));
- _inventoryQuery = GetEntityQuery<InventoryComponent>();
- _physicsQuery = GetEntityQuery<PhysicsComponent>();
- SubscribeLocalEvent<FlammableComponent, MapInitEvent>(OnMapInit);
- SubscribeLocalEvent<FlammableComponent, InteractUsingEvent>(OnInteractUsing);
- SubscribeLocalEvent<FlammableComponent, StartCollideEvent>(OnCollide);
- SubscribeLocalEvent<FlammableComponent, IsHotEvent>(OnIsHot);
- SubscribeLocalEvent<FlammableComponent, TileFireEvent>(OnTileFire);
- SubscribeLocalEvent<FlammableComponent, RejuvenateEvent>(OnRejuvenate);
- SubscribeLocalEvent<FlammableComponent, ResistFireAlertEvent>(OnResistFireAlert);
- SubscribeLocalEvent<IgniteOnCollideComponent, StartCollideEvent>(IgniteOnCollide);
- SubscribeLocalEvent<IgniteOnCollideComponent, LandEvent>(OnIgniteLand);
- SubscribeLocalEvent<IgniteOnMeleeHitComponent, MeleeHitEvent>(OnMeleeHit);
- SubscribeLocalEvent<ExtinguishOnInteractComponent, ActivateInWorldEvent>(OnExtinguishActivateInWorld);
- SubscribeLocalEvent<IgniteOnHeatDamageComponent, DamageChangedEvent>(OnDamageChanged);
- }
- private void OnMeleeHit(EntityUid uid, IgniteOnMeleeHitComponent component, MeleeHitEvent args)
- {
- foreach (var entity in args.HitEntities)
- {
- if (!TryComp<FlammableComponent>(entity, out var flammable))
- continue;
- AdjustFireStacks(entity, component.FireStacks, flammable);
- if (component.FireStacks >= 0)
- Ignite(entity, args.Weapon, flammable, args.User);
- }
- }
- private void OnIgniteLand(EntityUid uid, IgniteOnCollideComponent component, ref LandEvent args)
- {
- RemCompDeferred<IgniteOnCollideComponent>(uid);
- }
- private void IgniteOnCollide(EntityUid uid, IgniteOnCollideComponent component, ref StartCollideEvent args)
- {
- if (!args.OtherFixture.Hard || component.Count == 0)
- return;
- var otherEnt = args.OtherEntity;
- if (!EntityManager.TryGetComponent(otherEnt, out FlammableComponent? flammable))
- return;
- //Only ignite when the colliding fixture is projectile or ignition.
- if (args.OurFixtureId != component.FixtureId && args.OurFixtureId != SharedProjectileSystem.ProjectileFixture)
- {
- return;
- }
- flammable.FireStacks += component.FireStacks;
- Ignite(otherEnt, uid, flammable);
- component.Count--;
- if (component.Count == 0)
- RemCompDeferred<IgniteOnCollideComponent>(uid);
- }
- private void OnMapInit(EntityUid uid, FlammableComponent component, MapInitEvent args)
- {
- // Sets up a fixture for flammable collisions.
- // TODO: Should this be generalized into a general non-hard 'effects' fixture or something? I can't think of other use cases for it.
- // This doesn't seem great either (lots more collisions generated) but there isn't a better way to solve it either that I can think of.
- if (!TryComp<PhysicsComponent>(uid, out var body))
- return;
- _fixture.TryCreateFixture(uid, component.FlammableCollisionShape, component.FlammableFixtureID, hard: false,
- collisionMask: (int) CollisionGroup.FullTileLayer, body: body);
- }
- private void OnInteractUsing(EntityUid uid, FlammableComponent flammable, InteractUsingEvent args)
- {
- if (args.Handled)
- return;
- var isHotEvent = new IsHotEvent();
- RaiseLocalEvent(args.Used, isHotEvent);
- if (!isHotEvent.IsHot)
- return;
- Ignite(uid, args.Used, flammable, args.User);
- args.Handled = true;
- }
- private void OnExtinguishActivateInWorld(EntityUid uid, ExtinguishOnInteractComponent component, ActivateInWorldEvent args)
- {
- if (args.Handled || !args.Complex)
- return;
- if (!TryComp(uid, out FlammableComponent? flammable))
- return;
- if (!flammable.OnFire)
- return;
- args.Handled = true;
- if (!TryComp(uid, out UseDelayComponent? useDelay) || !_useDelay.TryResetDelay((uid, useDelay), true))
- return;
- _audio.PlayPvs(component.ExtinguishAttemptSound, uid);
- if (_random.Prob(component.Probability))
- {
- AdjustFireStacks(uid, component.StackDelta, flammable);
- }
- else
- {
- _popup.PopupEntity(Loc.GetString(component.ExtinguishFailed), uid);
- }
- }
- private void OnCollide(EntityUid uid, FlammableComponent flammable, ref StartCollideEvent args)
- {
- var otherUid = args.OtherEntity;
- // Collisions cause events to get raised directed at both entities. We only want to handle this collision
- // once, hence the uid check.
- if (otherUid.Id < uid.Id)
- return;
- // Normal hard collisions, though this isn't generally possible since most flammable things are mobs
- // which don't collide with one another, shouldn't work here.
- if (args.OtherFixtureId != flammable.FlammableFixtureID && args.OurFixtureId != flammable.FlammableFixtureID)
- return;
- if (!flammable.FireSpread)
- return;
- if (!TryComp(otherUid, out FlammableComponent? otherFlammable) || !otherFlammable.FireSpread)
- return;
- if (!flammable.OnFire && !otherFlammable.OnFire)
- return; // Neither are on fire
- // Both are on fire -> equalize fire stacks.
- // Weight each thing's firestacks by its mass
- var mass1 = 1f;
- var mass2 = 1f;
- if (_physicsQuery.TryComp(uid, out var physics) && _physicsQuery.TryComp(otherUid, out var otherPhys))
- {
- mass1 = physics.Mass;
- mass2 = otherPhys.Mass;
- }
- // when the thing on fire is more massive than the other, the following happens:
- // - the thing on fire loses a small number of firestacks
- // - the other thing gains a large number of firestacks
- // so a person on fire engulfs a mouse, but an engulfed mouse barely does anything to a person
- var total = mass1 + mass2;
- var avg = (flammable.FireStacks + otherFlammable.FireStacks) / total;
- // swap the entity losing stacks depending on whichever has the most firestack kilos
- var (src, dest) = flammable.FireStacks * mass1 > otherFlammable.FireStacks * mass2
- ? (-1f, 1f)
- : (1f, -1f);
- // bring each entity to the same firestack mass, firestacks being scaled by the other's mass
- AdjustFireStacks(uid, src * avg * mass2, flammable, ignite: true);
- AdjustFireStacks(otherUid, dest * avg * mass1, otherFlammable, ignite: true);
- }
- private void OnIsHot(EntityUid uid, FlammableComponent flammable, IsHotEvent args)
- {
- args.IsHot = flammable.OnFire;
- }
- private void OnTileFire(Entity<FlammableComponent> ent, ref TileFireEvent args)
- {
- var tempDelta = args.Temperature - ent.Comp.MinIgnitionTemperature;
- _fireEvents.TryGetValue(ent, out var maxTemp);
- if (tempDelta > maxTemp)
- _fireEvents[ent] = tempDelta;
- }
- private void OnRejuvenate(EntityUid uid, FlammableComponent component, RejuvenateEvent args)
- {
- Extinguish(uid, component);
- }
- private void OnResistFireAlert(Entity<FlammableComponent> ent, ref ResistFireAlertEvent args)
- {
- if (args.Handled)
- return;
- Resist(ent, ent);
- args.Handled = true;
- }
- public void UpdateAppearance(EntityUid uid, FlammableComponent? flammable = null, AppearanceComponent? appearance = null)
- {
- if (!Resolve(uid, ref flammable, ref appearance))
- return;
- _appearance.SetData(uid, FireVisuals.OnFire, flammable.OnFire, appearance);
- _appearance.SetData(uid, FireVisuals.FireStacks, flammable.FireStacks, appearance);
- // Also enable toggleable-light visuals
- // This is intended so that matches & candles can re-use code for un-shaded layers on in-hand sprites.
- // However, this could cause conflicts if something is ACTUALLY both a toggleable light and flammable.
- // if that ever happens, then fire visuals will need to implement their own in-hand sprite management.
- _appearance.SetData(uid, ToggleableLightVisuals.Enabled, flammable.OnFire, appearance);
- }
- public void AdjustFireStacks(EntityUid uid, float relativeFireStacks, FlammableComponent? flammable = null, bool ignite = false)
- {
- if (!Resolve(uid, ref flammable))
- return;
- SetFireStacks(uid, flammable.FireStacks + relativeFireStacks, flammable, ignite);
- }
- public void SetFireStacks(EntityUid uid, float stacks, FlammableComponent? flammable = null, bool ignite = false)
- {
- if (!Resolve(uid, ref flammable))
- return;
- flammable.FireStacks = MathF.Min(MathF.Max(flammable.MinimumFireStacks, stacks), flammable.MaximumFireStacks);
- if (flammable.FireStacks <= 0)
- {
- Extinguish(uid, flammable);
- }
- else
- {
- flammable.OnFire |= ignite;
- UpdateAppearance(uid, flammable);
- }
- }
- public void Extinguish(EntityUid uid, FlammableComponent? flammable = null)
- {
- if (!Resolve(uid, ref flammable))
- return;
- if (!flammable.OnFire || !flammable.CanExtinguish)
- return;
- _adminLogger.Add(LogType.Flammable, $"{ToPrettyString(uid):entity} stopped being on fire damage");
- flammable.OnFire = false;
- flammable.FireStacks = 0;
- _ignitionSourceSystem.SetIgnited(uid, false);
- UpdateAppearance(uid, flammable);
- }
- public void Ignite(EntityUid uid, EntityUid ignitionSource, FlammableComponent? flammable = null,
- EntityUid? ignitionSourceUser = null)
- {
- if (!Resolve(uid, ref flammable))
- return;
- if (flammable.AlwaysCombustible)
- {
- flammable.FireStacks = Math.Max(flammable.FirestacksOnIgnite, flammable.FireStacks);
- }
- if (flammable.FireStacks > 0 && !flammable.OnFire)
- {
- if (ignitionSourceUser != null)
- _adminLogger.Add(LogType.Flammable, $"{ToPrettyString(uid):target} set on fire by {ToPrettyString(ignitionSourceUser.Value):actor} with {ToPrettyString(ignitionSource):tool}");
- else
- _adminLogger.Add(LogType.Flammable, $"{ToPrettyString(uid):target} set on fire by {ToPrettyString(ignitionSource):actor}");
- flammable.OnFire = true;
- }
- UpdateAppearance(uid, flammable);
- }
- private void OnDamageChanged(EntityUid uid, IgniteOnHeatDamageComponent component, DamageChangedEvent args)
- {
- // Make sure the entity is flammable
- if (!TryComp<FlammableComponent>(uid, out var flammable))
- return;
- // Make sure the damage delta isn't null
- if (args.DamageDelta == null)
- return;
- // Check if its' taken any heat damage, and give the value
- if (args.DamageDelta.DamageDict.TryGetValue("Heat", out FixedPoint2 value))
- {
- // Make sure the value is greater than the threshold
- if(value <= component.Threshold)
- return;
- // Ignite that sucker
- flammable.FireStacks += component.FireStacks;
- Ignite(uid, uid, flammable);
- }
- }
- public void Resist(EntityUid uid,
- FlammableComponent? flammable = null)
- {
- if (!Resolve(uid, ref flammable))
- return;
- if (!flammable.OnFire || !_actionBlockerSystem.CanInteract(uid, null) || flammable.Resisting)
- return;
- flammable.Resisting = true;
- _popup.PopupEntity(Loc.GetString("flammable-component-resist-message"), uid, uid);
- _stunSystem.TryParalyze(uid, TimeSpan.FromSeconds(2f), true);
- // TODO FLAMMABLE: Make this not use TimerComponent...
- uid.SpawnTimer(2000, () =>
- {
- flammable.Resisting = false;
- flammable.FireStacks -= 1f;
- UpdateAppearance(uid, flammable);
- });
- }
- public override void Update(float frameTime)
- {
- // process all fire events
- foreach (var (flammable, deltaTemp) in _fireEvents)
- {
- // 100 -> 1, 200 -> 2, 400 -> 3...
- var fireStackMod = Math.Max(MathF.Log2(deltaTemp / 100) + 1, 0);
- var fireStackDelta = fireStackMod - flammable.Comp.FireStacks;
- var flammableEntity = flammable.Owner;
- if (fireStackDelta > 0)
- {
- AdjustFireStacks(flammableEntity, fireStackDelta, flammable);
- }
- Ignite(flammableEntity, flammableEntity, flammable);
- }
- _fireEvents.Clear();
- _timer += frameTime;
- if (_timer < UpdateTime)
- return;
- _timer -= UpdateTime;
- // TODO: This needs cleanup to take off the crust from TemperatureComponent and shit.
- var query = EntityQueryEnumerator<FlammableComponent, TransformComponent>();
- while (query.MoveNext(out var uid, out var flammable, out _))
- {
- // Slowly dry ourselves off if wet.
- if (flammable.FireStacks < 0)
- {
- flammable.FireStacks = MathF.Min(0, flammable.FireStacks + 1);
- }
- if (!flammable.OnFire)
- {
- _alertsSystem.ClearAlert(uid, flammable.FireAlert);
- continue;
- }
- _alertsSystem.ShowAlert(uid, flammable.FireAlert);
- if (flammable.FireStacks > 0)
- {
- var air = _atmosphereSystem.GetContainingMixture(uid);
- // If we're in an oxygenless environment, put the fire out.
- if (air == null || air.GetMoles(Gas.Oxygen) < 1f)
- {
- Extinguish(uid, flammable);
- continue;
- }
- var source = EnsureComp<IgnitionSourceComponent>(uid);
- _ignitionSourceSystem.SetIgnited((uid, source));
- if (TryComp(uid, out TemperatureComponent? temp))
- _temperatureSystem.ChangeHeat(uid, 12500 * flammable.FireStacks, false, temp);
- var ev = new GetFireProtectionEvent();
- // let the thing on fire handle it
- RaiseLocalEvent(uid, ref ev);
- // and whatever it's wearing
- if (_inventoryQuery.TryComp(uid, out var inv))
- _inventory.RelayEvent((uid, inv), ref ev);
- _damageableSystem.TryChangeDamage(uid, flammable.Damage * flammable.FireStacks * ev.Multiplier, interruptsDoAfters: false);
- AdjustFireStacks(uid, flammable.FirestackFade * (flammable.Resisting ? 10f : 1f), flammable, flammable.OnFire);
- }
- else
- {
- Extinguish(uid, flammable);
- }
- }
- }
- }
- }
|