2021-11-29 02:34:44 +13:00
using Content.Server.Administration.Logs ;
2021-07-21 20:32:00 +10:00
using Content.Server.Atmos.Components ;
2021-10-10 12:47:26 +02:00
using Content.Server.Stunnable ;
2022-03-04 16:25:32 +01:00
using Content.Server.Temperature.Components ;
2021-10-29 01:18:43 -07:00
using Content.Server.Temperature.Systems ;
2021-09-22 11:05:33 +02:00
using Content.Shared.ActionBlocker ;
using Content.Shared.Alert ;
using Content.Shared.Atmos ;
using Content.Shared.Damage ;
2021-11-28 14:56:53 +01:00
using Content.Shared.Database ;
2021-09-22 11:05:33 +02:00
using Content.Shared.Interaction ;
2022-07-04 18:30:45 -07:00
using Content.Shared.Physics ;
2021-09-26 15:18:45 +02:00
using Content.Shared.Popups ;
2022-09-14 19:30:56 +02:00
using Content.Shared.Rejuvenate ;
2021-09-22 11:05:33 +02:00
using Content.Shared.Temperature ;
2023-05-04 12:46:02 +10:00
using Content.Shared.Throwing ;
2022-11-09 07:34:07 +11:00
using Content.Shared.Weapons.Melee.Events ;
2022-07-04 16:51:34 +02:00
using Robust.Server.GameObjects ;
2021-09-22 11:05:33 +02:00
using Robust.Shared.Physics ;
2022-09-14 17:26:26 +10:00
using Robust.Shared.Physics.Components ;
2021-07-21 20:32:00 +10:00
using Robust.Shared.Physics.Dynamics ;
2022-09-14 17:26:26 +10:00
using Robust.Shared.Physics.Events ;
using Robust.Shared.Physics.Systems ;
2021-07-21 20:32:00 +10:00
namespace Content.Server.Atmos.EntitySystems
{
2022-12-12 16:35:33 +13:00
public sealed class FlammableSystem : EntitySystem
2021-07-21 20:32:00 +10:00
{
2021-09-22 11:05:33 +02:00
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default ! ;
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default ! ;
2021-10-10 12:47:26 +02:00
[Dependency] private readonly StunSystem _stunSystem = default ! ;
2021-10-29 01:18:43 -07:00
[Dependency] private readonly TemperatureSystem _temperatureSystem = default ! ;
2021-11-19 17:54:01 +01:00
[Dependency] private readonly DamageableSystem _damageableSystem = default ! ;
2022-01-05 00:19:23 -08:00
[Dependency] private readonly AlertsSystem _alertsSystem = default ! ;
2022-07-04 16:51:34 +02:00
[Dependency] private readonly TransformSystem _transformSystem = default ! ;
2022-07-04 18:30:45 -07:00
[Dependency] private readonly FixtureSystem _fixture = default ! ;
2023-01-15 15:38:59 +11:00
[Dependency] private readonly EntityLookupSystem _lookup = default ! ;
2022-05-28 23:41:17 -07:00
[Dependency] private readonly IAdminLogManager _adminLogger = default ! ;
2023-02-02 17:34:53 +01:00
[Dependency] private readonly SharedAppearanceSystem _appearance = default ! ;
2023-10-15 22:56:09 -07:00
[Dependency] private readonly SharedPopupSystem _popup = default ! ;
2021-09-22 11:05:33 +02:00
2022-12-12 16:35:33 +13:00
public const float MinimumFireStacks = - 10f ;
public const float MaximumFireStacks = 20f ;
2021-09-22 11:05:33 +02:00
private const float UpdateTime = 1f ;
2022-12-12 16:35:33 +13:00
public const float MinIgnitionTemperature = 373.15f ;
2022-07-04 18:30:45 -07:00
public const string FlammableFixtureID = "flammable" ;
2021-11-19 17:54:01 +01:00
2023-10-15 22:56:09 -07:00
private float _timer ;
2021-09-22 11:05:33 +02:00
2021-11-19 17:54:01 +01:00
private Dictionary < FlammableComponent , float > _fireEvents = new ( ) ;
2021-07-21 20:32:00 +10:00
public override void Initialize ( )
{
2021-11-19 17:54:01 +01:00
UpdatesAfter . Add ( typeof ( AtmosphereSystem ) ) ;
2022-07-04 18:30:45 -07:00
SubscribeLocalEvent < FlammableComponent , MapInitEvent > ( OnMapInit ) ;
2022-09-14 19:30:56 +02:00
SubscribeLocalEvent < FlammableComponent , InteractUsingEvent > ( OnInteractUsing ) ;
SubscribeLocalEvent < FlammableComponent , StartCollideEvent > ( OnCollide ) ;
SubscribeLocalEvent < FlammableComponent , IsHotEvent > ( OnIsHot ) ;
SubscribeLocalEvent < FlammableComponent , TileFireEvent > ( OnTileFire ) ;
SubscribeLocalEvent < FlammableComponent , RejuvenateEvent > ( OnRejuvenate ) ;
2023-05-04 12:46:02 +10:00
2022-01-30 13:44:45 +00:00
SubscribeLocalEvent < IgniteOnCollideComponent , StartCollideEvent > ( IgniteOnCollide ) ;
2023-05-04 12:46:02 +10:00
SubscribeLocalEvent < IgniteOnCollideComponent , LandEvent > ( OnIgniteLand ) ;
2022-06-23 21:27:28 -04:00
SubscribeLocalEvent < IgniteOnMeleeHitComponent , MeleeHitEvent > ( OnMeleeHit ) ;
}
private void OnMeleeHit ( EntityUid uid , IgniteOnMeleeHitComponent component , MeleeHitEvent args )
{
foreach ( var entity in args . HitEntities )
{
if ( ! TryComp < FlammableComponent > ( entity , out var flammable ) )
continue ;
flammable . FireStacks + = component . FireStacks ;
2023-08-04 21:18:09 -05:00
Ignite ( entity , args . Weapon , flammable , args . User ) ;
2022-06-23 21:27:28 -04:00
}
2022-01-30 13:44:45 +00:00
}
2023-05-04 12:46:02 +10:00
private void OnIgniteLand ( EntityUid uid , IgniteOnCollideComponent component , ref LandEvent args )
{
RemCompDeferred < IgniteOnCollideComponent > ( uid ) ;
}
2022-09-14 17:26:26 +10:00
private void IgniteOnCollide ( EntityUid uid , IgniteOnCollideComponent component , ref StartCollideEvent args )
2022-01-30 13:44:45 +00:00
{
2023-05-04 12:46:02 +10:00
if ( ! args . OtherFixture . Hard | | component . Count = = 0 )
return ;
2022-03-03 11:48:27 +01:00
2023-05-04 12:46:02 +10:00
var otherEnt = args . OtherEntity ;
if ( ! EntityManager . TryGetComponent ( otherEnt , out FlammableComponent ? flammable ) )
2022-03-03 11:48:27 +01:00
return ;
2021-09-22 11:05:33 +02:00
2022-03-03 11:48:27 +01:00
flammable . FireStacks + = component . FireStacks ;
2023-08-04 21:18:09 -05:00
Ignite ( otherEnt , uid , flammable ) ;
2023-05-04 12:46:02 +10:00
component . Count - - ;
if ( component . Count = = 0 )
RemCompDeferred < IgniteOnCollideComponent > ( uid ) ;
2022-03-03 11:48:27 +01:00
}
2022-03-04 16:25:32 +01:00
2022-07-04 18:30:45 -07:00
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 ;
2023-01-15 15:38:59 +11:00
_fixture . TryCreateFixture ( uid , component . FlammableCollisionShape , FlammableFixtureID , hard : false ,
collisionMask : ( int ) CollisionGroup . FullTileLayer , body : body ) ;
2022-07-04 18:30:45 -07:00
}
2022-09-14 19:30:56 +02:00
private void OnInteractUsing ( EntityUid uid , FlammableComponent flammable , InteractUsingEvent args )
2021-09-22 11:05:33 +02:00
{
if ( args . Handled )
return ;
var isHotEvent = new IsHotEvent ( ) ;
2022-07-04 18:30:45 -07:00
RaiseLocalEvent ( args . Used , isHotEvent ) ;
2021-09-22 11:05:33 +02:00
if ( ! isHotEvent . IsHot )
return ;
2023-08-04 21:18:09 -05:00
Ignite ( uid , args . Used , flammable , args . User ) ;
2021-09-22 11:05:33 +02:00
args . Handled = true ;
2021-07-21 20:32:00 +10:00
}
2022-09-14 19:30:56 +02:00
private void OnCollide ( EntityUid uid , FlammableComponent flammable , ref StartCollideEvent args )
2021-07-21 20:32:00 +10:00
{
2023-05-09 19:21:26 +12:00
var otherUid = args . OtherEntity ;
2022-07-04 18:30:45 -07:00
// 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.
2023-08-23 18:55:58 +10:00
if ( args . OtherFixtureId ! = FlammableFixtureID & & args . OurFixtureId ! = FlammableFixtureID )
2022-07-04 18:30:45 -07:00
return ;
2021-09-28 13:35:29 +02:00
if ( ! EntityManager . TryGetComponent ( otherUid , out FlammableComponent ? otherFlammable ) )
2021-07-21 20:32:00 +10:00
return ;
2021-09-22 11:05:33 +02:00
if ( ! flammable . FireSpread | | ! otherFlammable . FireSpread )
2021-07-21 20:32:00 +10:00
return ;
2021-09-22 11:05:33 +02:00
if ( flammable . OnFire )
2021-07-21 20:32:00 +10:00
{
if ( otherFlammable . OnFire )
{
2023-08-15 02:45:55 +07:00
if ( flammable . CanExtinguish )
{
var fireSplit = ( flammable . FireStacks + otherFlammable . FireStacks ) / 2 ;
flammable . FireStacks = fireSplit ;
otherFlammable . FireStacks = fireSplit ;
}
else
{
otherFlammable . FireStacks = flammable . FireStacks / 2 ;
}
2021-07-21 20:32:00 +10:00
}
else
{
2023-08-15 02:45:55 +07:00
if ( ! flammable . CanExtinguish )
{
otherFlammable . FireStacks + = flammable . FireStacks / 2 ;
Ignite ( otherUid , uid , otherFlammable ) ;
}
else
{
flammable . FireStacks / = 2 ;
otherFlammable . FireStacks + = flammable . FireStacks ;
Ignite ( otherUid , uid , otherFlammable ) ;
}
2021-07-21 20:32:00 +10:00
}
2022-07-04 18:30:45 -07:00
}
else if ( otherFlammable . OnFire )
2021-07-21 20:32:00 +10:00
{
otherFlammable . FireStacks / = 2 ;
2021-09-22 11:05:33 +02:00
flammable . FireStacks + = otherFlammable . FireStacks ;
2023-08-04 21:18:09 -05:00
Ignite ( uid , otherUid , flammable ) ;
2021-07-21 20:32:00 +10:00
}
}
2022-09-14 19:30:56 +02:00
private void OnIsHot ( EntityUid uid , FlammableComponent flammable , IsHotEvent args )
2021-09-22 11:05:33 +02:00
{
args . IsHot = flammable . OnFire ;
}
2022-09-14 19:30:56 +02:00
private void OnTileFire ( EntityUid uid , FlammableComponent flammable , ref TileFireEvent args )
2021-09-22 11:05:33 +02:00
{
2021-11-19 17:54:01 +01:00
var tempDelta = args . Temperature - MinIgnitionTemperature ;
2023-10-15 22:56:09 -07:00
_fireEvents . TryGetValue ( flammable , out var maxTemp ) ;
2021-11-19 17:54:01 +01:00
if ( tempDelta > maxTemp )
_fireEvents [ flammable ] = tempDelta ;
2021-09-22 11:05:33 +02:00
}
2022-09-14 19:30:56 +02:00
private void OnRejuvenate ( EntityUid uid , FlammableComponent component , RejuvenateEvent args )
{
Extinguish ( uid , component ) ;
}
2021-09-22 11:05:33 +02:00
public void UpdateAppearance ( EntityUid uid , FlammableComponent ? flammable = null , AppearanceComponent ? appearance = null )
{
if ( ! Resolve ( uid , ref flammable , ref appearance ) )
return ;
2023-02-02 17:34:53 +01:00
_appearance . SetData ( uid , FireVisuals . OnFire , flammable . OnFire , appearance ) ;
_appearance . SetData ( uid , FireVisuals . FireStacks , flammable . FireStacks , appearance ) ;
2021-09-22 11:05:33 +02:00
}
public void AdjustFireStacks ( EntityUid uid , float relativeFireStacks , FlammableComponent ? flammable = null )
2021-07-21 20:32:00 +10:00
{
2021-09-22 11:05:33 +02:00
if ( ! Resolve ( uid , ref flammable ) )
return ;
flammable . FireStacks = MathF . Min ( MathF . Max ( MinimumFireStacks , flammable . FireStacks + relativeFireStacks ) , MaximumFireStacks ) ;
if ( flammable . OnFire & & flammable . FireStacks < = 0 )
Extinguish ( uid , flammable ) ;
UpdateAppearance ( uid , flammable ) ;
}
public void Extinguish ( EntityUid uid , FlammableComponent ? flammable = null )
{
if ( ! Resolve ( uid , ref flammable ) )
return ;
2023-08-15 02:45:55 +07:00
if ( ! flammable . OnFire | | ! flammable . CanExtinguish )
2021-09-22 11:05:33 +02:00
return ;
2023-10-15 22:56:09 -07:00
_adminLogger . Add ( LogType . Flammable , $"{ToPrettyString(uid):entity} stopped being on fire damage" ) ;
2021-09-22 11:05:33 +02:00
flammable . OnFire = false ;
flammable . FireStacks = 0 ;
flammable . Collided . Clear ( ) ;
UpdateAppearance ( uid , flammable ) ;
}
2023-08-04 21:18:09 -05:00
public void Ignite ( EntityUid uid , EntityUid ignitionSource , FlammableComponent ? flammable = null ,
EntityUid ? ignitionSourceUser = null )
2021-09-22 11:05:33 +02:00
{
if ( ! Resolve ( uid , ref flammable ) )
return ;
2023-08-15 02:45:55 +07:00
if ( flammable . AlwaysCombustible )
{
flammable . FireStacks = Math . Max ( flammable . FirestacksOnIgnite , flammable . FireStacks ) ;
}
2021-09-22 11:05:33 +02:00
if ( flammable . FireStacks > 0 & & ! flammable . OnFire )
2021-07-21 20:32:00 +10:00
{
2023-08-04 21:18:09 -05:00
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}" ) ;
2021-09-22 11:05:33 +02:00
flammable . OnFire = true ;
2021-07-21 20:32:00 +10:00
}
2021-09-22 11:05:33 +02:00
UpdateAppearance ( uid , flammable ) ;
}
2021-10-10 12:47:26 +02:00
public void Resist ( EntityUid uid ,
2022-01-05 00:19:23 -08:00
FlammableComponent ? flammable = null )
2021-09-22 11:05:33 +02:00
{
2022-01-05 00:19:23 -08:00
if ( ! Resolve ( uid , ref flammable ) )
2021-09-22 11:05:33 +02:00
return ;
2023-10-15 22:56:09 -07:00
if ( ! flammable . OnFire | | ! _actionBlockerSystem . CanInteract ( uid , null ) | | flammable . Resisting )
2021-09-22 11:05:33 +02:00
return ;
flammable . Resisting = true ;
2023-10-15 22:56:09 -07:00
_popup . PopupEntity ( Loc . GetString ( "flammable-component-resist-message" ) , uid , uid ) ;
2022-01-05 00:19:23 -08:00
_stunSystem . TryParalyze ( uid , TimeSpan . FromSeconds ( 2f ) , true ) ;
2021-09-22 11:05:33 +02:00
2021-10-10 12:47:26 +02:00
// TODO FLAMMABLE: Make this not use TimerComponent...
2023-10-15 22:56:09 -07:00
uid . SpawnTimer ( 2000 , ( ) = >
2021-09-22 11:05:33 +02:00
{
flammable . Resisting = false ;
2021-11-19 17:54:01 +01:00
flammable . FireStacks - = 1f ;
2021-09-22 11:05:33 +02:00
UpdateAppearance ( uid , flammable ) ;
} ) ;
}
public override void Update ( float frameTime )
{
2021-11-19 17:54:01 +01:00
// 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 . FireStacks ;
2023-08-04 21:18:09 -05:00
var flammableEntity = flammable . Owner ;
2021-11-19 17:54:01 +01:00
if ( fireStackDelta > 0 )
{
2023-08-04 21:18:09 -05:00
AdjustFireStacks ( flammableEntity , fireStackDelta , flammable ) ;
2021-11-19 17:54:01 +01:00
}
2023-08-04 21:18:09 -05:00
Ignite ( flammableEntity , flammableEntity , flammable ) ;
2021-11-19 17:54:01 +01:00
}
_fireEvents . Clear ( ) ;
2021-09-22 11:05:33 +02:00
_timer + = frameTime ;
if ( _timer < UpdateTime )
return ;
_timer - = UpdateTime ;
// TODO: This needs cleanup to take off the crust from TemperatureComponent and shit.
2023-01-15 15:38:59 +11:00
foreach ( var ( flammable , transform ) in EntityManager . EntityQuery < FlammableComponent , TransformComponent > ( ) )
2021-09-22 11:05:33 +02:00
{
2021-12-05 18:09:01 +01:00
var uid = flammable . Owner ;
2021-09-22 11:05:33 +02:00
// Slowly dry ourselves off if wet.
if ( flammable . FireStacks < 0 )
{
flammable . FireStacks = MathF . Min ( 0 , flammable . FireStacks + 1 ) ;
}
if ( ! flammable . OnFire )
{
2022-01-05 00:19:23 -08:00
_alertsSystem . ClearAlert ( uid , AlertType . Fire ) ;
2021-10-21 23:07:42 +02:00
continue ;
2021-09-22 11:05:33 +02:00
}
2023-10-15 22:56:09 -07:00
_alertsSystem . ShowAlert ( uid , AlertType . Fire ) ;
2021-09-22 11:05:33 +02:00
if ( flammable . FireStacks > 0 )
{
2021-11-19 17:54:01 +01:00
// TODO FLAMMABLE: further balancing
var damageScale = Math . Min ( ( int ) flammable . FireStacks , 5 ) ;
2022-05-28 23:41:17 -07:00
2022-03-04 16:25:32 +01:00
if ( TryComp ( uid , out TemperatureComponent ? temp ) )
_temperatureSystem . ChangeHeat ( uid , 12500 * damageScale , false , temp ) ;
2021-11-19 17:54:01 +01:00
_damageableSystem . TryChangeDamage ( uid , flammable . Damage * damageScale ) ;
2021-09-22 11:05:33 +02:00
AdjustFireStacks ( uid , - 0.1f * ( flammable . Resisting ? 10f : 1f ) , flammable ) ;
}
else
{
Extinguish ( uid , flammable ) ;
2021-10-21 23:07:42 +02:00
continue ;
2021-09-22 11:05:33 +02:00
}
2022-07-04 16:51:34 +02:00
var air = _atmosphereSystem . GetContainingMixture ( uid ) ;
2021-09-22 11:05:33 +02:00
// If we're in an oxygenless environment, put the fire out.
if ( air = = null | | air . GetMoles ( Gas . Oxygen ) < 1f )
{
Extinguish ( uid , flammable ) ;
2021-10-21 23:07:42 +02:00
continue ;
2021-09-22 11:05:33 +02:00
}
2022-07-04 16:51:34 +02:00
if ( transform . GridUid ! = null )
{
_atmosphereSystem . HotspotExpose ( transform . GridUid . Value ,
_transformSystem . GetGridOrMapTilePosition ( uid , transform ) ,
2023-02-28 14:43:24 -06:00
700f , 50f , uid , true ) ;
2022-07-04 16:51:34 +02:00
}
2021-09-22 11:05:33 +02:00
2023-01-15 15:38:59 +11:00
for ( var i = flammable . Collided . Count - 1 ; i > = 0 ; i - - )
2021-09-22 11:05:33 +02:00
{
2023-01-15 15:38:59 +11:00
var otherUid = flammable . Collided [ i ] ;
2021-09-22 11:05:33 +02:00
if ( ! otherUid . IsValid ( ) | | ! EntityManager . EntityExists ( otherUid ) )
{
2023-01-15 15:38:59 +11:00
flammable . Collided . RemoveAt ( i ) ;
2021-09-22 11:05:33 +02:00
continue ;
}
// TODO: Sloth, please save our souls!
2023-01-15 15:38:59 +11:00
// no
if ( ! _lookup . GetWorldAABB ( uid , transform ) . Intersects ( _lookup . GetWorldAABB ( otherUid ) ) )
2021-09-22 11:05:33 +02:00
{
2023-01-15 15:38:59 +11:00
flammable . Collided . RemoveAt ( i ) ;
2021-09-22 11:05:33 +02:00
}
}
}
2021-07-21 20:32:00 +10:00
}
}
}