shuttle impacts port (#37422)
* initial * adjust densities and thruster hp * Fix evil hack * Last stuff * review, cleanup * admin RW * minor cleanup --------- Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
This commit is contained in:
@@ -5,7 +5,6 @@ using Content.Server.Shuttles.Components;
|
|||||||
using Content.Server.Shuttles.Events;
|
using Content.Server.Shuttles.Events;
|
||||||
using Content.Server.Station.Events;
|
using Content.Server.Station.Events;
|
||||||
using Content.Shared.Body.Components;
|
using Content.Shared.Body.Components;
|
||||||
using Content.Shared.Buckle.Components;
|
|
||||||
using Content.Shared.CCVar;
|
using Content.Shared.CCVar;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
using Content.Shared.Ghost;
|
using Content.Shared.Ghost;
|
||||||
@@ -73,11 +72,8 @@ public sealed partial class ShuttleSystem
|
|||||||
private readonly HashSet<Entity<NoFTLComponent>> _noFtls = new();
|
private readonly HashSet<Entity<NoFTLComponent>> _noFtls = new();
|
||||||
|
|
||||||
private EntityQuery<BodyComponent> _bodyQuery;
|
private EntityQuery<BodyComponent> _bodyQuery;
|
||||||
private EntityQuery<BuckleComponent> _buckleQuery;
|
|
||||||
private EntityQuery<FTLSmashImmuneComponent> _immuneQuery;
|
private EntityQuery<FTLSmashImmuneComponent> _immuneQuery;
|
||||||
private EntityQuery<PhysicsComponent> _physicsQuery;
|
|
||||||
private EntityQuery<StatusEffectsComponent> _statusQuery;
|
private EntityQuery<StatusEffectsComponent> _statusQuery;
|
||||||
private EntityQuery<TransformComponent> _xformQuery;
|
|
||||||
|
|
||||||
private void InitializeFTL()
|
private void InitializeFTL()
|
||||||
{
|
{
|
||||||
@@ -85,11 +81,8 @@ public sealed partial class ShuttleSystem
|
|||||||
SubscribeLocalEvent<FTLComponent, ComponentShutdown>(OnFtlShutdown);
|
SubscribeLocalEvent<FTLComponent, ComponentShutdown>(OnFtlShutdown);
|
||||||
|
|
||||||
_bodyQuery = GetEntityQuery<BodyComponent>();
|
_bodyQuery = GetEntityQuery<BodyComponent>();
|
||||||
_buckleQuery = GetEntityQuery<BuckleComponent>();
|
|
||||||
_immuneQuery = GetEntityQuery<FTLSmashImmuneComponent>();
|
_immuneQuery = GetEntityQuery<FTLSmashImmuneComponent>();
|
||||||
_physicsQuery = GetEntityQuery<PhysicsComponent>();
|
|
||||||
_statusQuery = GetEntityQuery<StatusEffectsComponent>();
|
_statusQuery = GetEntityQuery<StatusEffectsComponent>();
|
||||||
_xformQuery = GetEntityQuery<TransformComponent>();
|
|
||||||
|
|
||||||
_cfg.OnValueChanged(CCVars.FTLStartupTime, time => DefaultStartupTime = time, true);
|
_cfg.OnValueChanged(CCVars.FTLStartupTime, time => DefaultStartupTime = time, true);
|
||||||
_cfg.OnValueChanged(CCVars.FTLTravelTime, time => DefaultTravelTime = time, true);
|
_cfg.OnValueChanged(CCVars.FTLTravelTime, time => DefaultTravelTime = time, true);
|
||||||
|
|||||||
@@ -1,60 +1,435 @@
|
|||||||
using System.Numerics;
|
|
||||||
using Content.Server.Shuttles.Components;
|
using Content.Server.Shuttles.Components;
|
||||||
|
using Content.Shared.Atmos.Components;
|
||||||
using Content.Shared.Audio;
|
using Content.Shared.Audio;
|
||||||
|
using Content.Shared.CCVar;
|
||||||
|
using Content.Shared.Clothing;
|
||||||
|
using Content.Shared.Damage;
|
||||||
|
using Content.Shared.Database;
|
||||||
|
using Content.Shared.Item.ItemToggle.Components;
|
||||||
|
using Content.Shared.Maps;
|
||||||
|
using Content.Shared.Physics;
|
||||||
|
using Content.Shared.Projectiles;
|
||||||
|
using Content.Shared.Slippery;
|
||||||
using Robust.Shared.Audio;
|
using Robust.Shared.Audio;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Map.Components;
|
||||||
|
using Robust.Shared.Physics;
|
||||||
|
using Robust.Shared.Physics.Components;
|
||||||
using Robust.Shared.Physics.Dynamics;
|
using Robust.Shared.Physics.Dynamics;
|
||||||
using Robust.Shared.Physics.Events;
|
using Robust.Shared.Physics.Events;
|
||||||
using Robust.Shared.Player;
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Random;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
namespace Content.Server.Shuttles.Systems;
|
namespace Content.Server.Shuttles.Systems;
|
||||||
|
|
||||||
|
// shuttle impact damage ported from Goobstation (AGPLv3) with agreement of all coders involved
|
||||||
public sealed partial class ShuttleSystem
|
public sealed partial class ShuttleSystem
|
||||||
{
|
{
|
||||||
/// <summary>
|
private bool _enabled;
|
||||||
/// Minimum velocity difference between 2 bodies for a shuttle "impact" to occur.
|
private float _minimumImpactInertia;
|
||||||
/// </summary>
|
private float _minimumImpactVelocity;
|
||||||
private const int MinimumImpactVelocity = 10;
|
private float _tileBreakEnergyMultiplier;
|
||||||
|
private float _damageMultiplier;
|
||||||
|
private float _structuralDamage;
|
||||||
|
private float _sparkEnergy;
|
||||||
|
private float _impactRadius;
|
||||||
|
private float _impactSlowdown;
|
||||||
|
private float _minThrowVelocity;
|
||||||
|
private float _massBias;
|
||||||
|
private float _inertiaScaling;
|
||||||
|
// this doesn't update if plating mass is changed but edgecase
|
||||||
|
private float _platingMass;
|
||||||
|
|
||||||
|
private const float _sparkChance = 0.2f;
|
||||||
|
// shuttle mass to consider the neutral point for inertia scaling
|
||||||
|
private const float _baseShuttleMass = 50f;
|
||||||
|
// exists primarily for optimisation so not a cvar
|
||||||
|
private const float _minImpulseVelocity = 0.07f;
|
||||||
|
// high-speed collisions tend to be a series of increasingly smaller collisions so don't spam admin logs
|
||||||
|
private readonly TimeSpan _adminLogSpacing = TimeSpan.FromSeconds(3);
|
||||||
|
|
||||||
private readonly SoundCollectionSpecifier _shuttleImpactSound = new("ShuttleImpactSound");
|
private readonly SoundCollectionSpecifier _shuttleImpactSound = new("ShuttleImpactSound");
|
||||||
|
private readonly ProtoId<ContentTileDefinition> _platingId = "Plating";
|
||||||
|
private readonly EntProtoId _sparkEffect = "EffectSparks";
|
||||||
|
|
||||||
|
private EntityQuery<DamageableComponent> _dmgQuery;
|
||||||
|
private EntityQuery<ProjectileComponent> _projQuery;
|
||||||
|
|
||||||
|
private HashSet<EntityUid> _countedEnts = new();
|
||||||
|
private HashSet<EntityUid> _intersecting = new();
|
||||||
|
// for _adminLogSpacing
|
||||||
|
private Dictionary<EntityUid, TimeSpan> _impactedAt = new();
|
||||||
|
|
||||||
private void InitializeImpact()
|
private void InitializeImpact()
|
||||||
{
|
{
|
||||||
SubscribeLocalEvent<ShuttleComponent, StartCollideEvent>(OnShuttleCollide);
|
SubscribeLocalEvent<ShuttleComponent, StartCollideEvent>(OnShuttleCollide);
|
||||||
|
|
||||||
|
_dmgQuery = GetEntityQuery<DamageableComponent>();
|
||||||
|
_projQuery = GetEntityQuery<ProjectileComponent>();
|
||||||
|
|
||||||
|
Subs.CVar(_cfg, CCVars.ImpactEnabled, value => _enabled = value, true);
|
||||||
|
Subs.CVar(_cfg, CCVars.MinimumImpactInertia, value => _minimumImpactInertia = value, true);
|
||||||
|
Subs.CVar(_cfg, CCVars.MinimumImpactInertia, value => _minimumImpactInertia = value, true);
|
||||||
|
Subs.CVar(_cfg, CCVars.MinimumImpactVelocity, value => _minimumImpactVelocity = value, true);
|
||||||
|
Subs.CVar(_cfg, CCVars.TileBreakEnergyMultiplier, value => _tileBreakEnergyMultiplier = value, true);
|
||||||
|
Subs.CVar(_cfg, CCVars.ImpactDamageMultiplier, value => _damageMultiplier = value, true);
|
||||||
|
Subs.CVar(_cfg, CCVars.ImpactStructuralDamage, value => _structuralDamage = value, true);
|
||||||
|
Subs.CVar(_cfg, CCVars.SparkEnergy, value => _sparkEnergy = value, true);
|
||||||
|
Subs.CVar(_cfg, CCVars.ImpactRadius, value => _impactRadius = value, true);
|
||||||
|
Subs.CVar(_cfg, CCVars.ImpactSlowdown, value => _impactSlowdown = value, true);
|
||||||
|
Subs.CVar(_cfg, CCVars.ImpactMinThrowVelocity, value => _minThrowVelocity = value, true);
|
||||||
|
Subs.CVar(_cfg, CCVars.ImpactMassBias, value => _massBias = value, true);
|
||||||
|
Subs.CVar(_cfg, CCVars.ImpactInertiaScaling, value => _inertiaScaling = value, true);
|
||||||
|
|
||||||
|
_platingMass = _protoManager.Index(_platingId).Mass;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles collision between two shuttles, applying impact damage and effects.
|
||||||
|
/// </summary>
|
||||||
private void OnShuttleCollide(EntityUid uid, ShuttleComponent component, ref StartCollideEvent args)
|
private void OnShuttleCollide(EntityUid uid, ShuttleComponent component, ref StartCollideEvent args)
|
||||||
{
|
{
|
||||||
if (!HasComp<ShuttleComponent>(args.OtherEntity))
|
if (TerminatingOrDeleted(uid) || EntityManager.IsQueuedForDeletion(uid)
|
||||||
|
|| TerminatingOrDeleted(args.OtherEntity) || EntityManager.IsQueuedForDeletion(args.OtherEntity)
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!_gridQuery.TryComp(args.OurEntity, out var ourGrid) ||
|
||||||
|
!_gridQuery.TryComp(args.OtherEntity, out var otherGrid)
|
||||||
|
)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var ourBody = args.OurBody;
|
var ourBody = args.OurBody;
|
||||||
var otherBody = args.OtherBody;
|
var otherBody = args.OtherBody;
|
||||||
|
|
||||||
// TODO: Would also be nice to have a continuous sound for scraping.
|
// TODO: Would also be nice to have a continuous sound for scraping.
|
||||||
var ourXform = Transform(uid);
|
var ourXform = Transform(args.OurEntity);
|
||||||
|
|
||||||
if (ourXform.MapUid == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var otherXform = Transform(args.OtherEntity);
|
var otherXform = Transform(args.OtherEntity);
|
||||||
|
var worldPoints = args.WorldPoints;
|
||||||
|
|
||||||
var ourPoint = Vector2.Transform(args.WorldPoint, _transform.GetInvWorldMatrix(ourXform));
|
for (var i = 0; i < worldPoints.Length; i++)
|
||||||
var otherPoint = Vector2.Transform(args.WorldPoint, _transform.GetInvWorldMatrix(otherXform));
|
|
||||||
|
|
||||||
var ourVelocity = _physics.GetLinearVelocity(uid, ourPoint, ourBody, ourXform);
|
|
||||||
var otherVelocity = _physics.GetLinearVelocity(args.OtherEntity, otherPoint, otherBody, otherXform);
|
|
||||||
var jungleDiff = (ourVelocity - otherVelocity).Length();
|
|
||||||
|
|
||||||
if (jungleDiff < MinimumImpactVelocity)
|
|
||||||
{
|
{
|
||||||
return;
|
var worldPoint = worldPoints[i];
|
||||||
|
|
||||||
|
var ourPoint = _transform.ToCoordinates((args.OurEntity, ourXform), new MapCoordinates(worldPoint, ourXform.MapID));
|
||||||
|
var otherPoint = _transform.ToCoordinates((args.OtherEntity, otherXform), new MapCoordinates(worldPoint, otherXform.MapID));
|
||||||
|
|
||||||
|
var ourVelocity = _physics.GetLinearVelocity(args.OurEntity, ourPoint.Position, ourBody, ourXform);
|
||||||
|
var otherVelocity = _physics.GetLinearVelocity(args.OtherEntity, otherPoint.Position, otherBody, otherXform);
|
||||||
|
var jungleDiff = (ourVelocity - otherVelocity).Length();
|
||||||
|
|
||||||
|
// this is cursed but makes it so that collisions of small grid with large grid count the inertia as being approximately the small grid's
|
||||||
|
var effectiveInertiaMult = (ourBody.FixturesMass * otherBody.FixturesMass) / (ourBody.FixturesMass + otherBody.FixturesMass);
|
||||||
|
var effectiveInertia = jungleDiff * effectiveInertiaMult;
|
||||||
|
|
||||||
|
// TODO: squish damage so that a tiny splinter grid can't stop 2 big grids by being in the way
|
||||||
|
if (jungleDiff < _minimumImpactVelocity && effectiveInertia < _minimumImpactInertia
|
||||||
|
|| ourXform.MapUid == null
|
||||||
|
|| float.IsNaN(jungleDiff))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Play impact sound
|
||||||
|
var coordinates = new EntityCoordinates(ourXform.MapUid.Value, worldPoint);
|
||||||
|
|
||||||
|
var volume = MathF.Min(10f, MathF.Pow(jungleDiff, 0.5f) - 5f);
|
||||||
|
var audioParams = AudioParams.Default.WithVariation(SharedContentAudioSystem.DefaultVariation).WithVolume(volume);
|
||||||
|
_audio.PlayPvs(_shuttleImpactSound, coordinates, audioParams);
|
||||||
|
|
||||||
|
// if we're not enabled, stop after playing sound
|
||||||
|
if (!_enabled)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Convert the collision point directly to tile indices
|
||||||
|
var ourTile = new Vector2i((int)Math.Floor(ourPoint.X / ourGrid.TileSize), (int)Math.Floor(ourPoint.Y / ourGrid.TileSize));
|
||||||
|
var otherTile = new Vector2i((int)Math.Floor(otherPoint.X / otherGrid.TileSize), (int)Math.Floor(otherPoint.Y / otherGrid.TileSize));
|
||||||
|
|
||||||
|
var ourMass = GetRegionMass(args.OurEntity, ourGrid, ourTile, _impactRadius, out var ourTiles);
|
||||||
|
var otherMass = GetRegionMass(args.OtherEntity, otherGrid, otherTile, _impactRadius, out var otherTiles);
|
||||||
|
|
||||||
|
// just in case
|
||||||
|
if (ourTiles == 0 || otherTiles == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Log.Info($"Shuttle impact of {ToPrettyString(args.OurEntity)} with {ToPrettyString(args.OtherEntity)}; our mass: {ourMass}, other: {otherMass}, velocity {jungleDiff}, impact point {worldPoint}");
|
||||||
|
|
||||||
|
// E = MV^2/2
|
||||||
|
var energyMult = MathF.Pow(jungleDiff, 2) / 2;
|
||||||
|
// mass-based damage reduction to grid with more mass so that plastitanium block rammer doesn't die to lattice
|
||||||
|
var ourMassDR = MathF.Max(otherMass / ourMass, 1f);
|
||||||
|
var otherMassDR = MathF.Max(ourMass / otherMass, 1f);
|
||||||
|
// multiplier to make large grids not just bonk against each other
|
||||||
|
var inertiaMult = MathF.Pow(effectiveInertiaMult / _baseShuttleMass, _inertiaScaling);
|
||||||
|
var toUsEnergy = otherMass * energyMult * inertiaMult * ourMassDR;
|
||||||
|
var toOtherEnergy = ourMass * energyMult * inertiaMult * otherMassDR;
|
||||||
|
|
||||||
|
var impact = LogImpact.High;
|
||||||
|
// if impact isn't tiny, log it as extreme
|
||||||
|
if (toUsEnergy + toOtherEnergy > 2f * _tileBreakEnergyMultiplier * _platingMass)
|
||||||
|
impact = LogImpact.Extreme;
|
||||||
|
// TODO: would be nice for it to also log who is piloting the grid(s)
|
||||||
|
if (CheckShouldLog(args.OurEntity) && CheckShouldLog(args.OtherEntity))
|
||||||
|
_logger.Add(LogType.ShuttleImpact, impact, $"Shuttle impact of {ToPrettyString(args.OurEntity)} with {ToPrettyString(args.OtherEntity)} at {worldPoint}");
|
||||||
|
|
||||||
|
_impactedAt[args.OurEntity] = _gameTiming.CurTime;
|
||||||
|
_impactedAt[args.OtherEntity] = _gameTiming.CurTime;
|
||||||
|
|
||||||
|
// uses local region mass for slowdown calculation so lattice doesn't have same slowdown as wall block
|
||||||
|
var totalInertia = ourVelocity * ourMass + otherVelocity * otherMass;
|
||||||
|
var inelasticVel = totalInertia / (ourMass + otherMass);
|
||||||
|
|
||||||
|
DoGridImpact((args.OurEntity, ourGrid, ourXform, ourBody), args.OurFixture, inelasticVel, ourVelocity, ourTile, ourTiles, toUsEnergy);
|
||||||
|
DoGridImpact((args.OtherEntity, otherGrid, otherXform, otherBody), args.OtherFixture, inelasticVel, otherVelocity, otherTile, otherTiles, toOtherEnergy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DoGridImpact(Entity<MapGridComponent, TransformComponent, PhysicsComponent> ent,
|
||||||
|
Fixture fix,
|
||||||
|
Vector2 inelasticVelocity,
|
||||||
|
Vector2 velocity,
|
||||||
|
Vector2i tile,
|
||||||
|
int tiles,
|
||||||
|
float energy)
|
||||||
|
{
|
||||||
|
// for readability to not have .Comp1 .Comp2 for everything
|
||||||
|
var (_, grid, xform, body) = ent;
|
||||||
|
|
||||||
|
// radius in which to actually do things so we don't hurt person 4 tiles away on slow bump
|
||||||
|
var radius = Math.Min(_impactRadius, MathF.Sqrt(energy / _tileBreakEnergyMultiplier / _platingMass));
|
||||||
|
|
||||||
|
// slow us down since destroying impacting grid tiles prevents the collision
|
||||||
|
// without this impacts which destroy tiles just make grids slice straight through each other
|
||||||
|
var postImpactVelocity = Vector2.Lerp(velocity, inelasticVelocity, MathF.Min(1f, _impactSlowdown * tiles * fix.Density / body.FixturesMass));
|
||||||
|
var deltaV = -velocity + postImpactVelocity;
|
||||||
|
_physics.ApplyLinearImpulse(ent, deltaV * body.FixturesMass, body: body);
|
||||||
|
|
||||||
|
// process tile and entity damage
|
||||||
|
ProcessImpactZone(ent, grid, tile, energy, deltaV.Normalized(), radius);
|
||||||
|
|
||||||
|
// throw every entity on grid if the impulse is not negligible
|
||||||
|
if (deltaV.Length() > _minImpulseVelocity)
|
||||||
|
ThrowEntitiesOnGrid(ent, xform, -deltaV);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Knocks and throws all unbuckled entities on the specified grid.
|
||||||
|
/// </summary>
|
||||||
|
private void ThrowEntitiesOnGrid(EntityUid gridUid, TransformComponent xform, Vector2 direction)
|
||||||
|
{
|
||||||
|
var movedByPressureQuery = GetEntityQuery<MovedByPressureComponent>();
|
||||||
|
var knockdownTime = TimeSpan.FromSeconds(5);
|
||||||
|
|
||||||
|
var minsq = _minThrowVelocity * _minThrowVelocity;
|
||||||
|
// iterate all entities on the grid
|
||||||
|
// TODO: only iterate non-static entities
|
||||||
|
var childEnumerator = xform.ChildEnumerator;
|
||||||
|
while (childEnumerator.MoveNext(out var uid))
|
||||||
|
{
|
||||||
|
// don't throw static bodies
|
||||||
|
if (!_physicsQuery.TryGetComponent(uid, out var physics) || (physics.BodyType & BodyType.Static) != 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// don't throw if buckled
|
||||||
|
if (_buckle.IsBuckled(uid, _buckleQuery.CompOrNull(uid)))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// don't throw them if they have magboots
|
||||||
|
if (movedByPressureQuery.TryComp(uid, out var moved) && !moved.Enabled)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (direction.LengthSquared() > minsq)
|
||||||
|
{
|
||||||
|
_stuns.TryKnockdown(uid, knockdownTime, true);
|
||||||
|
_throwing.TryThrow(uid, direction, physics, Transform(uid), _projQuery, direction.Length(), playSound: false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_physics.ApplyLinearImpulse(uid, direction * physics.Mass, body: physics);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Structure to hold impact tile processing data for batch processing
|
||||||
|
/// </summary>
|
||||||
|
private record struct ImpactTileData(Vector2i Tile, float Energy, float DistanceFactor);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the total mass of all entities and tiles (using ContentTileDefinition.Mass) belonging to this grid in a circle
|
||||||
|
/// </summary>
|
||||||
|
private float GetRegionMass(EntityUid uid, MapGridComponent grid, Vector2i centerTile, float radius, out int tileCount)
|
||||||
|
{
|
||||||
|
tileCount = 0;
|
||||||
|
var mass = 0f;
|
||||||
|
_countedEnts.Clear();
|
||||||
|
|
||||||
|
foreach (var tileRef in _mapSystem.GetLocalTilesIntersecting(uid, grid, new Circle(centerTile, radius)))
|
||||||
|
{
|
||||||
|
var def = (ContentTileDefinition)_tileDefManager[tileRef.Tile.TypeId];
|
||||||
|
mass += def.Mass;
|
||||||
|
tileCount++;
|
||||||
|
|
||||||
|
_intersecting.Clear();
|
||||||
|
_lookup.GetLocalEntitiesIntersecting(uid, tileRef.GridIndices, _intersecting, gridComp: grid);
|
||||||
|
foreach (var localUid in _intersecting)
|
||||||
|
{
|
||||||
|
if (!_countedEnts.Add(localUid))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (_physicsQuery.TryComp(localUid, out var physics))
|
||||||
|
mass += physics.FixturesMass;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mass;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes a zone of tiles around the impact point
|
||||||
|
/// </summary>
|
||||||
|
private void ProcessImpactZone(EntityUid uid, MapGridComponent grid, Vector2i centerTile, float energy, Vector2 dir, float radius)
|
||||||
|
{
|
||||||
|
// Create a list of all tiles to process
|
||||||
|
var tilesToProcess = new List<ImpactTileData>();
|
||||||
|
|
||||||
|
// Pre-calculate all tiles that need processing
|
||||||
|
foreach (var tileRef in _mapSystem.GetLocalTilesIntersecting(uid, grid, new Circle(centerTile, radius)))
|
||||||
|
{
|
||||||
|
var distance = centerTile - tileRef.GridIndices;
|
||||||
|
// Calculate distance-based energy falloff
|
||||||
|
float distanceFactor = 1.0f - distance.Length / (radius + 1);
|
||||||
|
float tileEnergy = energy * distanceFactor;
|
||||||
|
|
||||||
|
tilesToProcess.Add(new ImpactTileData(tileRef.GridIndices, tileEnergy, distanceFactor));
|
||||||
}
|
}
|
||||||
|
|
||||||
var coordinates = new EntityCoordinates(ourXform.MapUid.Value, args.WorldPoint);
|
// Process tiles sequentially for safety
|
||||||
var volume = MathF.Min(10f, 1f * MathF.Pow(jungleDiff, 0.5f) - 5f);
|
var brokenTiles = new List<(Vector2i, Tile)>();
|
||||||
var audioParams = AudioParams.Default.WithVariation(SharedContentAudioSystem.DefaultVariation).WithVolume(volume);
|
var sparkTiles = new List<Vector2i>();
|
||||||
|
|
||||||
_audio.PlayPvs(_shuttleImpactSound, coordinates, audioParams);
|
ProcessTileBatch(uid, grid, tilesToProcess, dir, 0, tilesToProcess.Count, brokenTiles, sparkTiles);
|
||||||
|
|
||||||
|
// Only proceed with visual effects if the entity still exists
|
||||||
|
if (Exists(uid))
|
||||||
|
{
|
||||||
|
ProcessBrokenTilesAndSparks(uid, grid, brokenTiles, sparkTiles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Process a batch of tiles from the impact zone
|
||||||
|
/// </summary>
|
||||||
|
private void ProcessTileBatch(
|
||||||
|
EntityUid uid,
|
||||||
|
MapGridComponent grid,
|
||||||
|
List<ImpactTileData> tilesToProcess,
|
||||||
|
Vector2 throwDirection,
|
||||||
|
int startIndex,
|
||||||
|
int endIndex,
|
||||||
|
List<(Vector2i, Tile)> brokenTiles,
|
||||||
|
List<Vector2i> sparkTiles)
|
||||||
|
{
|
||||||
|
// here so we don't have to `new` it every iteration
|
||||||
|
var damageSpec = new DamageSpecifier()
|
||||||
|
{
|
||||||
|
DamageDict = { ["Blunt"] = 0, ["Structural"] = 0 }
|
||||||
|
};
|
||||||
|
|
||||||
|
var entitiesOnTile = new HashSet<Entity<TransformComponent>>();
|
||||||
|
var tileCenter = new Vector2(grid.TileSize / 2f, grid.TileSize / 2f);
|
||||||
|
|
||||||
|
for (var i = startIndex; i < endIndex; i++)
|
||||||
|
{
|
||||||
|
var tileData = tilesToProcess[i];
|
||||||
|
|
||||||
|
bool canBreakTile = true;
|
||||||
|
|
||||||
|
// Process entities on this tile
|
||||||
|
entitiesOnTile.Clear();
|
||||||
|
_lookup.GetLocalEntitiesIntersecting(uid, tileData.Tile, entitiesOnTile, gridComp: grid);
|
||||||
|
|
||||||
|
// this loop is a hotspot so tell if you know how to optimise it
|
||||||
|
foreach (var localEnt in entitiesOnTile)
|
||||||
|
{
|
||||||
|
// the query can ocassionally return entities barely touching this tile so check for that
|
||||||
|
var toCenter = tileData.Tile + tileCenter - localEnt.Comp.Coordinates.Position;
|
||||||
|
if (MathF.Abs(toCenter.X) > 0.5f || MathF.Abs(toCenter.Y) > 0.5f)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (_dmgQuery.TryComp(localEnt, out var damageable))
|
||||||
|
{
|
||||||
|
// Apply damage scaled by distance but capped to prevent gibbing
|
||||||
|
var scaledDamage = tileData.Energy * _damageMultiplier;
|
||||||
|
damageSpec.DamageDict["Blunt"] = scaledDamage;
|
||||||
|
damageSpec.DamageDict["Structural"] = scaledDamage * _structuralDamage;
|
||||||
|
|
||||||
|
_damageSys.TryChangeDamage(localEnt, damageSpec, damageable: damageable);
|
||||||
|
}
|
||||||
|
// might've been destroyed
|
||||||
|
if (TerminatingOrDeleted(localEnt) || EntityManager.IsQueuedForDeletion(localEnt))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!_physicsQuery.TryComp(localEnt, out var physics))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// no breaking tiles under walls that haven't been destroyed
|
||||||
|
if ((physics.BodyType & BodyType.Static) != 0
|
||||||
|
&& (physics.CollisionLayer & (int)CollisionGroup.Impassable) != 0)
|
||||||
|
{
|
||||||
|
canBreakTile = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var direction = throwDirection * tileData.DistanceFactor;
|
||||||
|
_throwing.TryThrow(localEnt, direction, physics, localEnt.Comp, _projQuery, direction.Length(), playSound: false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark tiles for spark effects
|
||||||
|
if (tileData.Energy > _sparkEnergy && tileData.DistanceFactor > 0.7f && _random.Prob(_sparkChance))
|
||||||
|
sparkTiles.Add(tileData.Tile);
|
||||||
|
|
||||||
|
if (!canBreakTile)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Mark tiles for breaking/effects
|
||||||
|
var def = (ContentTileDefinition)_tileDefManager[_mapSystem.GetTileRef(uid, grid, tileData.Tile).Tile.TypeId];
|
||||||
|
if (tileData.Energy > def.Mass * _tileBreakEnergyMultiplier)
|
||||||
|
brokenTiles.Add((tileData.Tile, Tile.Empty));
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Process visual effects and tile breaking after entity processing
|
||||||
|
/// </summary>
|
||||||
|
private void ProcessBrokenTilesAndSparks(
|
||||||
|
EntityUid uid,
|
||||||
|
MapGridComponent grid,
|
||||||
|
List<(Vector2i, Tile)> brokenTiles,
|
||||||
|
List<Vector2i> sparkTiles)
|
||||||
|
{
|
||||||
|
// Break tiles
|
||||||
|
_mapSystem.SetTiles(uid, grid, brokenTiles);
|
||||||
|
|
||||||
|
if (TerminatingOrDeleted(uid))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Spawn spark effects
|
||||||
|
foreach (var tile in sparkTiles)
|
||||||
|
{
|
||||||
|
var coords = _mapSystem.GridTileToLocal(uid, grid, tile);
|
||||||
|
Spawn(_sparkEffect, coords);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check whether this impact should be logged to admins.
|
||||||
|
/// Used to prevent spamming logs.
|
||||||
|
/// </summary>
|
||||||
|
private bool CheckShouldLog(EntityUid uid)
|
||||||
|
{
|
||||||
|
return !(_impactedAt.ContainsKey(uid) && _gameTiming.CurTime < _impactedAt[uid] + _adminLogSpacing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Content.Server.Administration.Logs;
|
using Content.Server.Administration.Logs;
|
||||||
using Content.Server.Body.Systems;
|
using Content.Server.Body.Systems;
|
||||||
|
using Content.Server.Buckle.Systems;
|
||||||
using Content.Server.Doors.Systems;
|
using Content.Server.Doors.Systems;
|
||||||
using Content.Server.Parallax;
|
using Content.Server.Parallax;
|
||||||
using Content.Server.Procedural;
|
using Content.Server.Procedural;
|
||||||
@@ -7,7 +8,10 @@ using Content.Server.Shuttles.Components;
|
|||||||
using Content.Server.Shuttles.Events;
|
using Content.Server.Shuttles.Events;
|
||||||
using Content.Server.Station.Systems;
|
using Content.Server.Station.Systems;
|
||||||
using Content.Server.Stunnable;
|
using Content.Server.Stunnable;
|
||||||
|
using Content.Shared.Buckle.Components;
|
||||||
|
using Content.Shared.Damage;
|
||||||
using Content.Shared.GameTicking;
|
using Content.Shared.GameTicking;
|
||||||
|
using Content.Shared.Inventory;
|
||||||
using Content.Shared.Mobs.Systems;
|
using Content.Shared.Mobs.Systems;
|
||||||
using Content.Shared.Movement.Events;
|
using Content.Shared.Movement.Events;
|
||||||
using Content.Shared.Salvage;
|
using Content.Shared.Salvage;
|
||||||
@@ -38,18 +42,21 @@ public sealed partial class ShuttleSystem : SharedShuttleSystem
|
|||||||
[Dependency] private readonly IComponentFactory _factory = default!;
|
[Dependency] private readonly IComponentFactory _factory = default!;
|
||||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||||
[Dependency] private readonly MapSystem _mapSystem = default!;
|
|
||||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||||
[Dependency] private readonly IRobustRandom _random = default!;
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
[Dependency] private readonly ITileDefinitionManager _tileDefManager = default!;
|
[Dependency] private readonly ITileDefinitionManager _tileDefManager = default!;
|
||||||
[Dependency] private readonly BiomeSystem _biomes = default!;
|
[Dependency] private readonly BiomeSystem _biomes = default!;
|
||||||
[Dependency] private readonly BodySystem _bobby = default!;
|
[Dependency] private readonly BodySystem _bobby = default!;
|
||||||
|
[Dependency] private readonly BuckleSystem _buckle = default!;
|
||||||
|
[Dependency] private readonly DamageableSystem _damageSys = default!;
|
||||||
[Dependency] private readonly DockingSystem _dockSystem = default!;
|
[Dependency] private readonly DockingSystem _dockSystem = default!;
|
||||||
[Dependency] private readonly DungeonSystem _dungeon = default!;
|
[Dependency] private readonly DungeonSystem _dungeon = default!;
|
||||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||||
[Dependency] private readonly FixtureSystem _fixtures = default!;
|
[Dependency] private readonly FixtureSystem _fixtures = default!;
|
||||||
|
[Dependency] private readonly InventorySystem _inventorySystem = default!;
|
||||||
[Dependency] private readonly MapLoaderSystem _loader = default!;
|
[Dependency] private readonly MapLoaderSystem _loader = default!;
|
||||||
|
[Dependency] private readonly MapSystem _mapSystem = default!;
|
||||||
[Dependency] private readonly MetaDataSystem _metadata = default!;
|
[Dependency] private readonly MetaDataSystem _metadata = default!;
|
||||||
[Dependency] private readonly PvsOverrideSystem _pvs = default!;
|
[Dependency] private readonly PvsOverrideSystem _pvs = default!;
|
||||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||||
@@ -63,7 +70,10 @@ public sealed partial class ShuttleSystem : SharedShuttleSystem
|
|||||||
[Dependency] private readonly ThrusterSystem _thruster = default!;
|
[Dependency] private readonly ThrusterSystem _thruster = default!;
|
||||||
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
|
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
|
||||||
|
|
||||||
|
private EntityQuery<BuckleComponent> _buckleQuery;
|
||||||
private EntityQuery<MapGridComponent> _gridQuery;
|
private EntityQuery<MapGridComponent> _gridQuery;
|
||||||
|
private EntityQuery<PhysicsComponent> _physicsQuery;
|
||||||
|
private EntityQuery<TransformComponent> _xformQuery;
|
||||||
|
|
||||||
public const float TileMassMultiplier = 0.5f;
|
public const float TileMassMultiplier = 0.5f;
|
||||||
|
|
||||||
@@ -71,7 +81,10 @@ public sealed partial class ShuttleSystem : SharedShuttleSystem
|
|||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
|
||||||
|
_buckleQuery = GetEntityQuery<BuckleComponent>();
|
||||||
_gridQuery = GetEntityQuery<MapGridComponent>();
|
_gridQuery = GetEntityQuery<MapGridComponent>();
|
||||||
|
_physicsQuery = GetEntityQuery<PhysicsComponent>();
|
||||||
|
_xformQuery = GetEntityQuery<TransformComponent>();
|
||||||
|
|
||||||
InitializeFTL();
|
InitializeFTL();
|
||||||
InitializeGridFills();
|
InitializeGridFills();
|
||||||
|
|||||||
@@ -467,5 +467,10 @@ public enum LogType
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Artifact node got activated.
|
/// Artifact node got activated.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ArtifactNode = 101
|
ArtifactNode = 101,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Damaging grid collision has occurred.
|
||||||
|
/// </summary>
|
||||||
|
ShuttleImpact = 102
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -194,4 +194,92 @@ public sealed partial class CCVars
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly CVarDef<float> GridImpulseMultiplier =
|
public static readonly CVarDef<float> GridImpulseMultiplier =
|
||||||
CVarDef.Create("shuttle.grid_impulse_multiplier", 0.01f, CVar.SERVERONLY);
|
CVarDef.Create("shuttle.grid_impulse_multiplier", 0.01f, CVar.SERVERONLY);
|
||||||
|
|
||||||
|
#region impacts
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether shuttle impacts should do anything beyond produce a sound.
|
||||||
|
/// </summary>
|
||||||
|
[CVarControl(AdminFlags.VarEdit)]
|
||||||
|
public static readonly CVarDef<bool> ImpactEnabled =
|
||||||
|
CVarDef.Create("shuttle.impact.enabled", true, CVar.SERVERONLY);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Minimum impact inertia to trigger special shuttle impact behaviors when impacting slower than MinimumImpactVelocity.
|
||||||
|
/// </summary>
|
||||||
|
[CVarControl(AdminFlags.VarEdit)]
|
||||||
|
public static readonly CVarDef<float> MinimumImpactInertia =
|
||||||
|
CVarDef.Create("shuttle.impact.minimum_inertia", 5f * 50f, CVar.SERVERONLY); // 100tile grid (cargo shuttle) going at 5 m/s
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Minimum velocity difference between 2 bodies for a shuttle impact to be guaranteed to trigger any special behaviors like damage.
|
||||||
|
/// </summary>
|
||||||
|
[CVarControl(AdminFlags.VarEdit)]
|
||||||
|
public static readonly CVarDef<float> MinimumImpactVelocity =
|
||||||
|
CVarDef.Create("shuttle.impact.minimum_velocity", 15f, CVar.SERVERONLY); // needed so that random space debris can be rammed
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Multiplier of Kinetic energy required to dismantle a single tile in relation to its mass
|
||||||
|
/// </summary>
|
||||||
|
[CVarControl(AdminFlags.VarEdit)]
|
||||||
|
public static readonly CVarDef<float> TileBreakEnergyMultiplier =
|
||||||
|
CVarDef.Create("shuttle.impact.tile_break_energy", 3000f, CVar.SERVERONLY);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Multiplier of damage done to entities on colliding areas
|
||||||
|
/// </summary>
|
||||||
|
[CVarControl(AdminFlags.VarEdit)]
|
||||||
|
public static readonly CVarDef<float> ImpactDamageMultiplier =
|
||||||
|
CVarDef.Create("shuttle.impact.damage_multiplier", 0.00005f, CVar.SERVERONLY);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Multiplier of additional structural damage to do
|
||||||
|
/// </summary>
|
||||||
|
[CVarControl(AdminFlags.VarEdit)]
|
||||||
|
public static readonly CVarDef<float> ImpactStructuralDamage =
|
||||||
|
CVarDef.Create("shuttle.impact.structural_damage", 5f, CVar.SERVERONLY);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Kinetic energy required to spawn sparks
|
||||||
|
/// </summary>
|
||||||
|
[CVarControl(AdminFlags.VarEdit)]
|
||||||
|
public static readonly CVarDef<float> SparkEnergy =
|
||||||
|
CVarDef.Create("shuttle.impact.spark_energy", 2000000f, CVar.SERVERONLY);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Area to consider for impact calculations
|
||||||
|
/// </summary>
|
||||||
|
[CVarControl(AdminFlags.VarEdit)]
|
||||||
|
public static readonly CVarDef<float> ImpactRadius =
|
||||||
|
CVarDef.Create("shuttle.impact.radius", 4f, CVar.SERVERONLY);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Affects slowdown on impact
|
||||||
|
/// </summary>
|
||||||
|
[CVarControl(AdminFlags.VarEdit)]
|
||||||
|
public static readonly CVarDef<float> ImpactSlowdown =
|
||||||
|
CVarDef.Create("shuttle.impact.slowdown", 0.8f, CVar.SERVERONLY);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Minimum velocity change from impact for special throw effects (e.g. stuns, beakers breaking) to occur
|
||||||
|
/// </summary>
|
||||||
|
[CVarControl(AdminFlags.VarEdit)]
|
||||||
|
public static readonly CVarDef<float> ImpactMinThrowVelocity =
|
||||||
|
CVarDef.Create("shuttle.impact.min_throw_velocity", 1f, CVar.SERVERONLY); // due to how it works this is about 16 m/s for cargo shuttle
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Affects how much damage reduction to give to grids with higher mass
|
||||||
|
/// </summary>
|
||||||
|
[CVarControl(AdminFlags.VarEdit)]
|
||||||
|
public static readonly CVarDef<float> ImpactMassBias =
|
||||||
|
CVarDef.Create("shuttle.impact.mass_bias", 0.65f, CVar.SERVERONLY);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How much should total grid inertia affect our collision damage
|
||||||
|
/// </summary>
|
||||||
|
[CVarControl(AdminFlags.VarEdit)]
|
||||||
|
public static readonly CVarDef<float> ImpactInertiaScaling =
|
||||||
|
CVarDef.Create("shuttle.impact.inertia_scaling", 0.5f, CVar.SERVERONLY);
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,12 @@ namespace Content.Shared.Maps
|
|||||||
[DataField]
|
[DataField]
|
||||||
public PrototypeFlags<ToolQualityPrototype> DeconstructTools { get; set; } = new();
|
public PrototypeFlags<ToolQualityPrototype> DeconstructTools { get; set; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Effective mass of this tile for grid impacts.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public float Mass = 800f;
|
||||||
|
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Legacy AF but nice to have.
|
/// Legacy AF but nice to have.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
thresholds:
|
thresholds:
|
||||||
- trigger:
|
- trigger:
|
||||||
!type:DamageTrigger
|
!type:DamageTrigger
|
||||||
damage: 100 # Considering we need a lot of thrusters didn't want to make an individual one too tanky
|
damage: 300 # Changed 100->300 because impact damage is real
|
||||||
behaviors:
|
behaviors:
|
||||||
- !type:DoActsBehavior
|
- !type:DoActsBehavior
|
||||||
acts: ["Destruction"]
|
acts: ["Destruction"]
|
||||||
@@ -72,13 +72,13 @@
|
|||||||
thresholds:
|
thresholds:
|
||||||
- trigger:
|
- trigger:
|
||||||
!type:DamageTrigger
|
!type:DamageTrigger
|
||||||
damage: 300
|
damage: 600
|
||||||
behaviors:
|
behaviors:
|
||||||
- !type:DoActsBehavior
|
- !type:DoActsBehavior
|
||||||
acts: ["Destruction"]
|
acts: ["Destruction"]
|
||||||
- trigger:
|
- trigger:
|
||||||
!type:DamageTrigger
|
!type:DamageTrigger
|
||||||
damage: 100
|
damage: 300
|
||||||
behaviors:
|
behaviors:
|
||||||
- !type:DoActsBehavior
|
- !type:DoActsBehavior
|
||||||
acts: ["Destruction"]
|
acts: ["Destruction"]
|
||||||
|
|||||||
@@ -48,6 +48,17 @@
|
|||||||
path: /Audio/Effects/break_stone.ogg
|
path: /Audio/Effects/break_stone.ogg
|
||||||
params:
|
params:
|
||||||
volume: -6
|
volume: -6
|
||||||
|
- type: Fixtures
|
||||||
|
fixtures:
|
||||||
|
fix1:
|
||||||
|
shape:
|
||||||
|
!type:PhysShapeAabb
|
||||||
|
bounds: "-0.5,-0.5,0.5,0.5"
|
||||||
|
mask:
|
||||||
|
- FullTileMask
|
||||||
|
layer:
|
||||||
|
- WallLayer
|
||||||
|
density: 2000
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
abstract: true
|
abstract: true
|
||||||
|
|||||||
@@ -183,6 +183,17 @@
|
|||||||
- type: IconSmooth
|
- type: IconSmooth
|
||||||
key: walls
|
key: walls
|
||||||
base: clown
|
base: clown
|
||||||
|
- type: Fixtures
|
||||||
|
fixtures:
|
||||||
|
fix1:
|
||||||
|
shape:
|
||||||
|
!type:PhysShapeAabb
|
||||||
|
bounds: "-0.5,-0.5,0.5,0.5"
|
||||||
|
mask:
|
||||||
|
- FullTileMask
|
||||||
|
layer:
|
||||||
|
- WallLayer
|
||||||
|
density: 8000 # really good ramming wall, bananium is rare so it's probably fine
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
parent: BaseWall
|
parent: BaseWall
|
||||||
@@ -343,6 +354,17 @@
|
|||||||
node: girder
|
node: girder
|
||||||
- !type:DoActsBehavior
|
- !type:DoActsBehavior
|
||||||
acts: ["Destruction"]
|
acts: ["Destruction"]
|
||||||
|
- type: Fixtures
|
||||||
|
fixtures:
|
||||||
|
fix1:
|
||||||
|
shape:
|
||||||
|
!type:PhysShapeAabb
|
||||||
|
bounds: "-0.5,-0.5,0.5,0.5"
|
||||||
|
mask:
|
||||||
|
- FullTileMask
|
||||||
|
layer:
|
||||||
|
- WallLayer
|
||||||
|
density: 4000
|
||||||
- type: IconSmooth
|
- type: IconSmooth
|
||||||
key: walls
|
key: walls
|
||||||
base: gold
|
base: gold
|
||||||
@@ -472,6 +494,17 @@
|
|||||||
- type: IconSmooth
|
- type: IconSmooth
|
||||||
key: walls
|
key: walls
|
||||||
base: plastitanium
|
base: plastitanium
|
||||||
|
- type: Fixtures
|
||||||
|
fixtures:
|
||||||
|
fix1:
|
||||||
|
shape:
|
||||||
|
!type:PhysShapeAabb
|
||||||
|
bounds: "-0.5,-0.5,0.5,0.5"
|
||||||
|
mask:
|
||||||
|
- FullTileMask
|
||||||
|
layer:
|
||||||
|
- WallLayer
|
||||||
|
density: 4000
|
||||||
- type: Damageable
|
- type: Damageable
|
||||||
damageContainer: StructuralInorganic
|
damageContainer: StructuralInorganic
|
||||||
damageModifierSet: StructuralMetallicStrong
|
damageModifierSet: StructuralMetallicStrong
|
||||||
@@ -605,6 +638,17 @@
|
|||||||
price: 250
|
price: 250
|
||||||
- type: RadiationBlocker
|
- type: RadiationBlocker
|
||||||
resistance: 5
|
resistance: 5
|
||||||
|
- type: Fixtures
|
||||||
|
fixtures:
|
||||||
|
fix1:
|
||||||
|
shape:
|
||||||
|
!type:PhysShapeAabb
|
||||||
|
bounds: "-0.5,-0.5,0.5,0.5"
|
||||||
|
mask:
|
||||||
|
- FullTileMask
|
||||||
|
layer:
|
||||||
|
- WallLayer
|
||||||
|
density: 2000
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
parent: WallReinforced
|
parent: WallReinforced
|
||||||
@@ -804,6 +848,26 @@
|
|||||||
state: state0
|
state: state0
|
||||||
- type: Reflect
|
- type: Reflect
|
||||||
reflectProb: 1
|
reflectProb: 1
|
||||||
|
- type: Pullable
|
||||||
|
- type: Airtight
|
||||||
|
noAirWhenFullyAirBlocked: false
|
||||||
|
airBlockedDirection:
|
||||||
|
- South
|
||||||
|
- East
|
||||||
|
- type: Fixtures
|
||||||
|
fixtures:
|
||||||
|
fix1:
|
||||||
|
shape:
|
||||||
|
!type:PolygonShape
|
||||||
|
vertices:
|
||||||
|
- "-0.5,-0.5"
|
||||||
|
- "0.5,0.5"
|
||||||
|
- "0.5,-0.5"
|
||||||
|
mask:
|
||||||
|
- FullTileMask
|
||||||
|
layer:
|
||||||
|
- WallLayer
|
||||||
|
density: 2000
|
||||||
- type: Damageable
|
- type: Damageable
|
||||||
damageContainer: StructuralInorganic
|
damageContainer: StructuralInorganic
|
||||||
damageModifierSet: StructuralMetallicStrong
|
damageModifierSet: StructuralMetallicStrong
|
||||||
@@ -896,6 +960,17 @@
|
|||||||
5: { state: shuttle_construct-5, visible: true}
|
5: { state: shuttle_construct-5, visible: true}
|
||||||
- type: Reflect
|
- type: Reflect
|
||||||
reflectProb: 1
|
reflectProb: 1
|
||||||
|
- type: Fixtures
|
||||||
|
fixtures:
|
||||||
|
fix1:
|
||||||
|
shape:
|
||||||
|
!type:PhysShapeAabb
|
||||||
|
bounds: "-0.5,-0.5,0.5,0.5"
|
||||||
|
mask:
|
||||||
|
- FullTileMask
|
||||||
|
layer:
|
||||||
|
- WallLayer
|
||||||
|
density: 2000
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
parent: BaseWall
|
parent: BaseWall
|
||||||
@@ -1048,6 +1123,17 @@
|
|||||||
node: girder
|
node: girder
|
||||||
- !type:DoActsBehavior
|
- !type:DoActsBehavior
|
||||||
acts: ["Destruction"]
|
acts: ["Destruction"]
|
||||||
|
- type: Fixtures
|
||||||
|
fixtures:
|
||||||
|
fix1:
|
||||||
|
shape:
|
||||||
|
!type:PhysShapeAabb
|
||||||
|
bounds: "-0.5,-0.5,0.5,0.5"
|
||||||
|
mask:
|
||||||
|
- FullTileMask
|
||||||
|
layer:
|
||||||
|
- WallLayer
|
||||||
|
density: 6000
|
||||||
- type: IconSmooth
|
- type: IconSmooth
|
||||||
key: walls
|
key: walls
|
||||||
base: uranium
|
base: uranium
|
||||||
|
|||||||
@@ -34,6 +34,17 @@
|
|||||||
- type: StaticPrice
|
- type: StaticPrice
|
||||||
price: 100
|
price: 100
|
||||||
- type: BlockWeather
|
- type: BlockWeather
|
||||||
|
- type: Fixtures
|
||||||
|
fixtures:
|
||||||
|
fix1:
|
||||||
|
shape:
|
||||||
|
!type:PhysShapeAabb
|
||||||
|
bounds: "-0.5,-0.5,0.5,0.5"
|
||||||
|
mask:
|
||||||
|
- FullTileMask
|
||||||
|
layer:
|
||||||
|
- WallLayer
|
||||||
|
density: 4000
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
id: PlastitaniumWindowSquareBase
|
id: PlastitaniumWindowSquareBase
|
||||||
@@ -144,6 +155,7 @@
|
|||||||
- FullTileMask
|
- FullTileMask
|
||||||
layer:
|
layer:
|
||||||
- GlassLayer
|
- GlassLayer
|
||||||
|
density: 4000
|
||||||
- type: Airtight
|
- type: Airtight
|
||||||
noAirWhenFullyAirBlocked: false
|
noAirWhenFullyAirBlocked: false
|
||||||
airBlockedDirection:
|
airBlockedDirection:
|
||||||
|
|||||||
@@ -55,6 +55,17 @@
|
|||||||
sprite: Structures/Windows/cracks.rsi
|
sprite: Structures/Windows/cracks.rsi
|
||||||
- type: StaticPrice
|
- type: StaticPrice
|
||||||
price: 132
|
price: 132
|
||||||
|
- type: Fixtures
|
||||||
|
fixtures:
|
||||||
|
fix1:
|
||||||
|
shape:
|
||||||
|
!type:PhysShapeAabb
|
||||||
|
bounds: "-0.5,-0.5,0.5,0.5"
|
||||||
|
mask:
|
||||||
|
- FullTileMask
|
||||||
|
layer:
|
||||||
|
- WallLayer
|
||||||
|
density: 2000
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
id: PlasmaReinforcedWindowDirectional
|
id: PlasmaReinforcedWindowDirectional
|
||||||
@@ -148,6 +159,7 @@
|
|||||||
- FullTileMask
|
- FullTileMask
|
||||||
layer:
|
layer:
|
||||||
- GlassLayer
|
- GlassLayer
|
||||||
|
density: 2000
|
||||||
- type: Airtight
|
- type: Airtight
|
||||||
noAirWhenFullyAirBlocked: false
|
noAirWhenFullyAirBlocked: false
|
||||||
airBlockedDirection:
|
airBlockedDirection:
|
||||||
|
|||||||
@@ -72,6 +72,7 @@
|
|||||||
isSpace: true
|
isSpace: true
|
||||||
itemDrop: PartRodMetal1
|
itemDrop: PartRodMetal1
|
||||||
heatCapacity: 10000
|
heatCapacity: 10000
|
||||||
|
mass: 200
|
||||||
|
|
||||||
- type: tile
|
- type: tile
|
||||||
id: TrainLattice
|
id: TrainLattice
|
||||||
@@ -87,3 +88,4 @@
|
|||||||
isSpace: true
|
isSpace: true
|
||||||
itemDrop: PartRodMetal1
|
itemDrop: PartRodMetal1
|
||||||
heatCapacity: 10000
|
heatCapacity: 10000
|
||||||
|
mass: 200
|
||||||
|
|||||||
Reference in New Issue
Block a user