mirror of
https://github.com/WWhiteDreamProject/wwdpublic.git
synced 2026-04-17 21:48:58 +03:00
# Description Adds the Plasmamen as a playable species. Plasmamen are a skeletal species who depend on Plasma to live, and oxygen is highly fatal to them. Being exposed to oxygen will set them on fire, unless they wear their envirosuits. ## Species Guidebook <img width=500px src="https://github.com/user-attachments/assets/a1ef91ef-87b2-4ae0-8b5c-922a0c34777f"> <img width=500px src="https://github.com/user-attachments/assets/110f0fa0-7dc4-410b-a2c0-a517f0311484"> **SPECIAL:** - Plasmamen speak the language Calcic, a language they share with Skeletons. ## Shitmed Integration Plasmamen are the first ever species designed with Shitmed in mind, with one of their core mechanics (self-ignition) powered entirely by Shitmed. Whether or not a Plasmaman ignites from oxygen exposure depends only on their body parts. A Plasmaman with only their head exposed will not burn as much as an entirely naked Plasmaman. You can **transfer** Plasmaman body parts to non-Plasmamen through **surgery** so that they also ignite from oxygen exposure. Meanwhile, a Plasmaman with a non-Plasmaman head can expose their head without self-igniting. https://github.com/user-attachments/assets/0aa33070-04be-4ded-b668-3afb9f4ddd7c ## Technical Details This also cherry-picks https://github.com/space-wizards/space-station-14/pull/28595 as a quality-of-life feature to ensure Plasmamen keep their internals on upon toggling their helmet with a breath mask on. ## TODO ### RELEASE-NECESSARY <details> - [x] Port more envirosuits / enviro helms (job-specific) and their sprites - [x] Remove breath masks from default plasmaman loadouts because the envirohelms already allow them to breathe internals - [x] Change default plasma tank to higher-capacity version - [x] Prevent plasmamen from buying jumpsuits and helmets other than envirosuits - ~~[ ] **Client UI update for loadout groups min/max items and default items**~~ - [x] Plasmaman-specific mask sprites from TG - [x] Disable too cold alert for plasmamen - [x] Create/port sprites for these jobs - [x] Courier - [x] Forensic Mantis - [x] Corpsman (Resprite security envirosuit) - [x] Prison Guard (Resprite security envirosuit) - [x] Magistrate (No Paradise envirosuit so use new colorable envirosuit) - [x] Blueshield (Port from Paradise and tg-ify?) - [x] NanoTrasen Representative (No Paradise envirosuit so use new colorable envirosuit) - [x] Martial Artist (use new colorable envirosuit and make pure white) - [x] Musician (use new colorable envirosuit) - [x] Reporter (use new colorable envirosuit) - [x] Zookeeper (use new colorable envirosuit) - [x] Service Worker (use new colorable envirosuit) - [x] Gladiator - [x] Technical Assistant - [x] Medical Intern - [x] Acolyte / Research Assistant - [x] Security Cadet - [x] Assistant - You know what. These intern jobs are fine. They can use their normal equivalent's envirosuits. - [x] Logistics Officer (use new colorable envirosuit) - [x] Adjust sprites to be closer to actual job - [x] Captain (Shift color to be closer to ss14 captain) - [x] ~~CMO (Remove yellow accents)~~ - [x] Port HoP envirogloves sprite - [x] unique sprite for self-extinguish verb - [x] Refactor conditional gear stuff to live only in StartingGearPrototype with `SubGear` `List<ProtoId<StartingGearPrototype>>` field and `List<Requirement>` field for sub-gear requirements - [x] Add starting gear for paradox anomaly, and antags and ghost roles - [x] Paradox - [x] Nukies - [x] Disaster victims - [x] Listening post operative - [x] Make all envirosuit helmets have a glowing (unshaded) visor - [x] Envirosuit extinguish visuals - [x] JobPrototype: AfterLoadoutSpecial - [x] Set prisoner envirohelm battery to potato, command/sec/dignitary to high-powered - [x] Set base envirosuit extinguishes to 4, sec 6 and command 8 - [x] Improve plasmaman organ extraction experience - [x] Body parts now give 1 plasma sheet each, while Torso gives 3 - [x] Organs can be juiced to get plasma - [x] Make envirohelm flashlights battery-powered - [x] Plasmamen visuals - [x] Grayscale sprites for color customization, and set default skintone color to Plasmaman classic skintone - [x] Plasmaman eye organ sprite - [x] Add basic loadouts - [x] Add way to refill envirosuit charges (refill at medical protolathe after some research) </details> ### Low Importance <details> - [x] Envirogloves - [ ] (SCOPE CREEP) Plasma tanks sprite (only normal emergency/extended, rather low priority) - [ ] (SCOPE CREEP) Modify envirosuit helmet sprites to have a transparent visor - [ ] Glowing eyes/mouth marking - [x] More cargo content with plasma tanks / envirosuits - [x] Plasmaman survival kit like slime - [x] Additional plasma tanks - [ ] (SCOPE CREEP) Plasmaman EVA suits - [x] ~~Add envirosuits to clothesmate~~ - [x] Add more plasma tanks to random lockers and job lockers - [x] Turn envirosuit auto-extinguish into extinguish action - [x] move self-extinguish verb stuff to shared for prediction of the verb - [x] move self-extinguisher stuff away from extinguisher namespace - [x] unique sprite for self-extinguish icon - [x] ~~IDEA: purple glowy fire extinguisher ~~ - [x] on self-extinguish, check for pressure immunity OR ignite from gas immunity properly - [x] See envirosuit extinguish charges in examine - [x] Milk heals on ingestion - [x] Plasma heals on ingestion - [x] Self-ignition doesn't occur on a stasis bed - [x] ~~Self-ignition doesn't occur when dead~~ - [x] Guidebook entry - [x] Make self-ignition ignore damage resistances from fire suits - [x] ~~Make self-ignition ignore damage resistances from armor~~ - [x] ~~Unable to rot?~~ - [x] Make the envirosuit helmet toggle on for the character dummy in lobby - [ ] (SCOPE CREEP) One additional Plasmaman trait - [x] ~~Showers extinguish water as well as water tiles~~ - Unnecessary as stasis beds now prevent ignition, allowing surgery on a plasmaman on stasis beds. - [x] Unique punch animations for Plasmafire Punch/Toxoplasmic Punch traits - [x] Actually remove toxoplasmic it's just slop filler tbh - [ ] Talk sounds - [ ] Normal - [ ] Question - [ ] Yell - [x] Positive moodlet for drinking milk / more positive moodlet for drinking plasma - [x] Increase moodlet bonus and also minimum reagent required for the plasma/milk moodlets - [x] Increase fire rate base stacks on ignite cause putting out your helmet for a few secs isn't that dangerous due to the fire stacks immediately decaying - [x] I think halving firestack fade from -0.1 to -0.05 might work to do the same thing too - [ ] (SCOPE CREEP) Get bone laugh sounds from monke 'monkestation/sound/voice/laugh/skeleton/skeleton_laugh.ogg' - [ ] (SCOPE CREEP) When EVA plasmaman suit is added, 25% caustic resist - [x] Envirosuit helmet - [x] Equivalent of 100% bio / 100% fire / 75% acid resist - [x] Envirosuit - [x] Equivalent of 100% bio / 100% fire / 75% acid resist - [x] Envirogloves - [x] Equivalent of 100% bio / 95% fire / 95% acid resist - [x] Put breath mask back on - [x] Refactor: put body parts covered data into component instead of being hardcoded </details> ## Media **Custom Plasmaman Outfits** All of these use the same **absolutely massive** [envirosuit RSI](0c3af432df/Resources/Textures/Clothing/Uniforms/Envirosuits/color.rsi) and [envirohelm RSI](0c3af432df/Resources/Textures/Clothing/Head/Envirohelms/color.rsi) to quickly create the envirosuits that didn't exist in SS13 where the envirosuit sprites were ported. From Left to Right: Magistrate, Prison Guard, Boxer, Reporter, Logistics Officer <img width=200px src="https://github.com/user-attachments/assets/bf990841-7d9e-4f4e-abae-8f29a3980ca1"> <img width=200px src="https://github.com/user-attachments/assets/07ca7af7-4f43-4504-9eac-4ca9188ae98e"> <img width=200px src="https://github.com/user-attachments/assets/0d20332c-826f-4fec-8396-74e84c23b074"> <img width=200px src="https://github.com/user-attachments/assets/1634364e-7cb3-457b-b638-e1b562b7c0c5"> <img width=200px src="https://github.com/user-attachments/assets/c2881764-f2fa-4e40-9fbf-35d1b717c432"> **Plasmaman Melee Attack** https://github.com/user-attachments/assets/6e694f2c-3e03-40bf-ae27-fc58a3e4cb6c **Chat bubble** <img width=240px src="https://github.com/user-attachments/assets/e3c17e6d-5050-410f-a42c-339f0bfa30a1"> **Plasmaman Body** <img width=140px src="https://github.com/user-attachments/assets/7ed90a47-9c33-487d-bd44-c50cec9f16dd"> With different colors: <img width=140px src="https://github.com/user-attachments/assets/0a28068e-7392-4062-950b-f60d2602da84"> <img width=140px src="https://github.com/user-attachments/assets/9b652311-0305-4ec0-be60-e404697617a2"> **Skeleton Language**  **(Bonus) Skeleton chat bubble** <img width=240px src="https://github.com/user-attachments/assets/a2e2be5c-f3ae-49d9-b655-8688de45b512"> **Self-Extinguish** https://github.com/user-attachments/assets/6c68e2ef-8010-4f00-8c24-dce8a8065be8 The self-extinguish is also accessible as a verb, which also means that others can activate your self-extinguish if they open the strip menu. <img width=200px src="https://github.com/user-attachments/assets/291ab86d-2250-46ec-ae0c-80084ab04407"> The self-extinguish action has different icons depending on the status of the self extinguish. Left to right: Ready, On Cooldown, Out Of Charges <img src="https://github.com/user-attachments/assets/0340de8a-9440-43b1-8bff-1c8f962faa0c"> <img src="https://github.com/user-attachments/assets/11f73558-6dc1-444d-b2ef-2f15f55174ca"> <img src="https://github.com/user-attachments/assets/030ed737-f178-4c60-ba0c-109659e7d9cb"> **Envirosuit Extinguisher Refill** <img width=300px src="https://github.com/user-attachments/assets/9379294b-e3f3-436d-81bc-2584631869ef"> <img width=300px src="https://github.com/user-attachments/assets/807b9e9e-7b4b-4593-aa1f-d9d24ac6985c"> **Loadouts** <img width=400px src="https://github.com/user-attachments/assets/55713b87-29bb-41b3-b7a3-88fbc6e5e797"> <img width=400px src="https://github.com/user-attachments/assets/ab1757fa-9b70-4a66-b5ae-20fd9cabe935"> <img width=400px src="https://github.com/user-attachments/assets/aacc4cf7-9ce1-4099-b8c7-108bef1f3bde"> <img width=400px src="https://github.com/user-attachments/assets/58604dc2-82ef-4d42-b9e2-639548c93f40"> **Plasma Envirosuit Crate** <img width=400px src="https://github.com/user-attachments/assets/fa362387-9c10-47c3-b1af-2c11e6b00163"> <img width=400px src="https://github.com/user-attachments/assets/bf773722-9034-4469-967d-e00dbf8c77a7"> **Internals Crate (Plasma)** <img width=400px src="https://github.com/user-attachments/assets/fcd4ff2e-09e9-423a-9b21-96817f6042a4"> <img width=400px src="https://github.com/user-attachments/assets/bf773722-9034-4469-967d-e00dbf8c77a7"> **Glow In The Dark**  ## Changelog 🆑 Skubman - add: The Plasmaman species has arrived! They need to breathe plasma to live, and a special jumpsuit to prevent oxygen from igniting them. In exchange, they deal formidable unarmed Heat damage, are never hungry nor thirsty, and are immune to cold and radiation damage. Read more about Plasmamen in their Guidebook entry. - tweak: Internals are no longer toggled off if you take your helmet off but still have a gas mask on and vice versa. - tweak: Paradox Anomalies will now spawn with the original person's Loadout items. - fix: Fixed prisoners not being able to have custom Loadout names and descriptions, and heirlooms if they didn't have a backpack when joining. --------- Signed-off-by: Skubman <ba.fallaria@gmail.com> Signed-off-by: VMSolidus <evilexecutive@gmail.com> Co-authored-by: Plykiya <58439124+Plykiya@users.noreply.github.com> Co-authored-by: VMSolidus <evilexecutive@gmail.com> (cherry picked from commit e68e0c3f4b9cf263e07efc888b32a091df62fb51)
480 lines
19 KiB
C#
480 lines
19 KiB
C#
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 Content.Shared.Mood;
|
|
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<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
|
|
|
|
if (flammable.OnFire && otherFlammable.OnFire)
|
|
{
|
|
// 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;
|
|
}
|
|
|
|
var total = mass1 + mass2;
|
|
var avg = (flammable.FireStacks * mass1 + otherFlammable.FireStacks * mass2) / total;
|
|
flammable.FireStacks = flammable.CanExtinguish ? avg : Math.Max(flammable.FireStacks, avg);
|
|
otherFlammable.FireStacks = otherFlammable.CanExtinguish ? avg : Math.Max(otherFlammable.FireStacks, avg);
|
|
UpdateAppearance(uid, flammable);
|
|
UpdateAppearance(otherUid, otherFlammable);
|
|
return;
|
|
}
|
|
|
|
// Only one is on fire -> attempt to spread the fire.
|
|
var (srcUid, srcFlammable, destUid, destFlammable) = flammable.OnFire
|
|
? (uid, flammable, otherUid, otherFlammable)
|
|
: (otherUid, otherFlammable, uid, flammable);
|
|
|
|
// if the thing on fire has less mass, spread less firestacks and vice versa
|
|
var ratio = 0.5f;
|
|
if (_physicsQuery.TryComp(srcUid, out var srcPhysics) && _physicsQuery.TryComp(destUid, out var destPhys))
|
|
{
|
|
ratio *= srcPhysics.Mass / destPhys.Mass;
|
|
}
|
|
|
|
var lost = srcFlammable.FireStacks * ratio;
|
|
destFlammable.FireStacks += lost;
|
|
Ignite(destUid, srcUid, destFlammable);
|
|
if (srcFlammable.CanExtinguish)
|
|
{
|
|
srcFlammable.FireStacks -= lost;
|
|
UpdateAppearance(srcUid, srcFlammable);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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)
|
|
{
|
|
if (!Resolve(uid, ref flammable))
|
|
return;
|
|
|
|
if (relativeFireStacks > 0)
|
|
relativeFireStacks *= flammable.FireStackIncreaseMultiplier;
|
|
|
|
flammable.FireStacks = MathF.Min(MathF.Max(flammable.MinimumFireStacks, flammable.FireStacks + relativeFireStacks), flammable.MaximumFireStacks);
|
|
|
|
if (flammable.OnFire && flammable.FireStacks <= 0)
|
|
Extinguish(uid, flammable);
|
|
else
|
|
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;
|
|
flammable.IgnoreFireProtection = false;
|
|
|
|
_ignitionSourceSystem.SetIgnited(uid, false);
|
|
|
|
UpdateAppearance(uid, flammable);
|
|
}
|
|
|
|
public void Ignite(EntityUid uid, EntityUid ignitionSource, FlammableComponent? flammable = null,
|
|
EntityUid? ignitionSourceUser = null, bool ignoreFireProtection = false)
|
|
{
|
|
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;
|
|
}
|
|
|
|
if (ignoreFireProtection)
|
|
flammable.IgnoreFireProtection = ignoreFireProtection;
|
|
|
|
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 -= flammable.FirestackFade * 10f;
|
|
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);
|
|
RaiseLocalEvent(uid, new MoodRemoveEffectEvent("OnFire"));
|
|
continue;
|
|
}
|
|
|
|
_alertsSystem.ShowAlert(uid, flammable.FireAlert);
|
|
RaiseLocalEvent(uid, new MoodEffectEvent("OnFire"));
|
|
|
|
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 multiplier = 1f;
|
|
if (!flammable.IgnoreFireProtection)
|
|
{
|
|
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);
|
|
|
|
multiplier = ev.Multiplier;
|
|
}
|
|
|
|
_damageableSystem.TryChangeDamage(uid, flammable.Damage * flammable.FireStacks * multiplier, interruptsDoAfters: false);
|
|
|
|
AdjustFireStacks(uid, flammable.FirestackFade * (flammable.Resisting ? 10f : 1f), flammable);
|
|
}
|
|
else
|
|
{
|
|
Extinguish(uid, flammable);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|