Merge branch 'master' into 2024/04/21-loadouts
|
|
@ -23,22 +23,29 @@ public sealed partial class UltraVisionOverlay : Overlay
|
|||
_ultraVisionShader = _prototypeManager.Index<ShaderPrototype>("UltraVision").Instance().Duplicate();
|
||||
}
|
||||
|
||||
protected override bool BeforeDraw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (_playerManager.LocalEntity is not { Valid: true } player
|
||||
|| !_entityManager.HasComponent<UltraVisionComponent>(player))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return base.BeforeDraw(in args);
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (ScreenTexture == null)
|
||||
return;
|
||||
if (_playerManager.LocalPlayer?.ControlledEntity is not {Valid: true} player)
|
||||
return;
|
||||
if (!_entityManager.HasComponent<UltraVisionComponent>(player))
|
||||
if (ScreenTexture is null)
|
||||
return;
|
||||
|
||||
_ultraVisionShader?.SetParameter("SCREEN_TEXTURE", ScreenTexture);
|
||||
|
||||
_ultraVisionShader.SetParameter("SCREEN_TEXTURE", ScreenTexture);
|
||||
|
||||
var worldHandle = args.WorldHandle;
|
||||
var viewport = args.WorldBounds;
|
||||
worldHandle.SetTransform(Matrix3.Identity);
|
||||
worldHandle.UseShader(_ultraVisionShader);
|
||||
worldHandle.DrawRect(viewport, Color.White);
|
||||
worldHandle.UseShader(null); // important - as of writing, construction overlay breaks without this
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ using Content.Shared.Abilities;
|
|||
using Content.Shared.DeltaV.CCVars;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Client.DeltaV.Overlays;
|
||||
|
||||
|
|
@ -9,6 +10,7 @@ public sealed partial class UltraVisionSystem : EntitySystem
|
|||
{
|
||||
[Dependency] private readonly IOverlayManager _overlayMan = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly ISharedPlayerManager _playerMan = default!;
|
||||
|
||||
private UltraVisionOverlay _overlay = default!;
|
||||
|
||||
|
|
@ -18,6 +20,8 @@ public sealed partial class UltraVisionSystem : EntitySystem
|
|||
|
||||
SubscribeLocalEvent<UltraVisionComponent, ComponentInit>(OnUltraVisionInit);
|
||||
SubscribeLocalEvent<UltraVisionComponent, ComponentShutdown>(OnUltraVisionShutdown);
|
||||
SubscribeLocalEvent<UltraVisionComponent, LocalPlayerAttachedEvent>(OnPlayerAttached);
|
||||
SubscribeLocalEvent<UltraVisionComponent, LocalPlayerDetachedEvent>(OnPlayerDetached);
|
||||
|
||||
Subs.CVar(_cfg, DCCVars.NoVisionFilters, OnNoVisionFiltersChanged);
|
||||
|
||||
|
|
@ -26,11 +30,23 @@ public sealed partial class UltraVisionSystem : EntitySystem
|
|||
|
||||
private void OnUltraVisionInit(EntityUid uid, UltraVisionComponent component, ComponentInit args)
|
||||
{
|
||||
if (!_cfg.GetCVar(DCCVars.NoVisionFilters))
|
||||
if (uid == _playerMan.LocalEntity && !_cfg.GetCVar(DCCVars.NoVisionFilters))
|
||||
_overlayMan.AddOverlay(_overlay);
|
||||
}
|
||||
|
||||
private void OnUltraVisionShutdown(EntityUid uid, UltraVisionComponent component, ComponentShutdown args)
|
||||
{
|
||||
if (uid == _playerMan.LocalEntity)
|
||||
_overlayMan.RemoveOverlay(_overlay);
|
||||
}
|
||||
|
||||
private void OnPlayerAttached(EntityUid uid, UltraVisionComponent component, LocalPlayerAttachedEvent args)
|
||||
{
|
||||
if (!_cfg.GetCVar(DCCVars.NoVisionFilters))
|
||||
_overlayMan.AddOverlay(_overlay);
|
||||
}
|
||||
|
||||
private void OnPlayerDetached(EntityUid uid, UltraVisionComponent component, LocalPlayerDetachedEvent args)
|
||||
{
|
||||
_overlayMan.RemoveOverlay(_overlay);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,22 +23,29 @@ public sealed partial class DogVisionOverlay : Overlay
|
|||
_dogVisionShader = _prototypeManager.Index<ShaderPrototype>("DogVision").Instance().Duplicate();
|
||||
}
|
||||
|
||||
protected override bool BeforeDraw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (_playerManager.LocalEntity is not { Valid: true } player
|
||||
|| !_entityManager.HasComponent<DogVisionComponent>(player))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return base.BeforeDraw(in args);
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (ScreenTexture == null)
|
||||
return;
|
||||
if (_playerManager.LocalPlayer?.ControlledEntity is not {Valid: true} player)
|
||||
return;
|
||||
if (!_entityManager.HasComponent<DogVisionComponent>(player))
|
||||
if (ScreenTexture is null)
|
||||
return;
|
||||
|
||||
_dogVisionShader?.SetParameter("SCREEN_TEXTURE", ScreenTexture);
|
||||
|
||||
_dogVisionShader.SetParameter("SCREEN_TEXTURE", ScreenTexture);
|
||||
|
||||
var worldHandle = args.WorldHandle;
|
||||
var viewport = args.WorldBounds;
|
||||
worldHandle.SetTransform(Matrix3.Identity);
|
||||
worldHandle.UseShader(_dogVisionShader);
|
||||
worldHandle.DrawRect(viewport, Color.White);
|
||||
worldHandle.UseShader(null); // important - as of writing, construction overlay breaks without this
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ using Content.Shared.Abilities;
|
|||
using Content.Shared.DeltaV.CCVars;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Client.Nyanotrasen.Overlays;
|
||||
|
||||
|
|
@ -9,6 +10,7 @@ public sealed partial class DogVisionSystem : EntitySystem
|
|||
{
|
||||
[Dependency] private readonly IOverlayManager _overlayMan = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly ISharedPlayerManager _playerMan = default!;
|
||||
|
||||
private DogVisionOverlay _overlay = default!;
|
||||
|
||||
|
|
@ -18,6 +20,8 @@ public sealed partial class DogVisionSystem : EntitySystem
|
|||
|
||||
SubscribeLocalEvent<DogVisionComponent, ComponentInit>(OnDogVisionInit);
|
||||
SubscribeLocalEvent<DogVisionComponent, ComponentShutdown>(OnDogVisionShutdown);
|
||||
SubscribeLocalEvent<DogVisionComponent, LocalPlayerAttachedEvent>(OnPlayerAttached);
|
||||
SubscribeLocalEvent<DogVisionComponent, LocalPlayerDetachedEvent>(OnPlayerDetached);
|
||||
|
||||
Subs.CVar(_cfg, DCCVars.NoVisionFilters, OnNoVisionFiltersChanged);
|
||||
|
||||
|
|
@ -26,11 +30,23 @@ public sealed partial class DogVisionSystem : EntitySystem
|
|||
|
||||
private void OnDogVisionInit(EntityUid uid, DogVisionComponent component, ComponentInit args)
|
||||
{
|
||||
if (!_cfg.GetCVar(DCCVars.NoVisionFilters))
|
||||
if (uid == _playerMan.LocalEntity && !_cfg.GetCVar(DCCVars.NoVisionFilters))
|
||||
_overlayMan.AddOverlay(_overlay);
|
||||
}
|
||||
|
||||
private void OnDogVisionShutdown(EntityUid uid, DogVisionComponent component, ComponentShutdown args)
|
||||
{
|
||||
if (uid == _playerMan.LocalEntity)
|
||||
_overlayMan.RemoveOverlay(_overlay);
|
||||
}
|
||||
|
||||
private void OnPlayerAttached(EntityUid uid, DogVisionComponent component, LocalPlayerAttachedEvent args)
|
||||
{
|
||||
if (!_cfg.GetCVar(DCCVars.NoVisionFilters))
|
||||
_overlayMan.AddOverlay(_overlay);
|
||||
}
|
||||
|
||||
private void OnPlayerDetached(EntityUid uid, DogVisionComponent component, LocalPlayerDetachedEvent args)
|
||||
{
|
||||
_overlayMan.RemoveOverlay(_overlay);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,14 +24,7 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
|
|||
if (!state.AllChunks!.Contains(index))
|
||||
component.Chunks.Remove(index);
|
||||
}
|
||||
|
||||
foreach (var beacon in component.Beacons)
|
||||
{
|
||||
if (!state.AllBeacons!.Contains(beacon))
|
||||
component.Beacons.Remove(beacon);
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
foreach (var index in component.Chunks.Keys)
|
||||
|
|
@ -39,25 +32,19 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
|
|||
if (!state.Chunks.ContainsKey(index))
|
||||
component.Chunks.Remove(index);
|
||||
}
|
||||
|
||||
foreach (var beacon in component.Beacons)
|
||||
{
|
||||
if (!state.Beacons.Contains(beacon))
|
||||
component.Beacons.Remove(beacon);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var ((category, origin), chunk) in state.Chunks)
|
||||
foreach (var (origin, chunk) in state.Chunks)
|
||||
{
|
||||
var newChunk = new NavMapChunk(origin);
|
||||
|
||||
foreach (var (atmosDirection, value) in chunk)
|
||||
newChunk.TileData[atmosDirection] = value;
|
||||
|
||||
component.Chunks[(category, origin)] = newChunk;
|
||||
Array.Copy(chunk, newChunk.TileData, chunk.Length);
|
||||
component.Chunks[origin] = newChunk;
|
||||
}
|
||||
|
||||
foreach (var beacon in state.Beacons)
|
||||
component.Beacons.Add(beacon);
|
||||
component.Beacons.Clear();
|
||||
foreach (var (nuid, beacon) in state.Beacons)
|
||||
{
|
||||
component.Beacons[nuid] = beacon;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ using System.Numerics;
|
|||
using JetBrains.Annotations;
|
||||
using Content.Shared.Atmos;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Pinpointer.UI;
|
||||
|
||||
|
|
@ -71,10 +72,10 @@ public partial class NavMapControl : MapGridControl
|
|||
protected float BackgroundOpacity = 0.9f;
|
||||
private int _targetFontsize = 8;
|
||||
|
||||
protected Dictionary<(int, Vector2i), (int, Vector2i)> HorizLinesLookup = new();
|
||||
protected Dictionary<(int, Vector2i), (int, Vector2i)> HorizLinesLookupReversed = new();
|
||||
protected Dictionary<(int, Vector2i), (int, Vector2i)> VertLinesLookup = new();
|
||||
protected Dictionary<(int, Vector2i), (int, Vector2i)> VertLinesLookupReversed = new();
|
||||
private Dictionary<Vector2i, Vector2i> _horizLines = new();
|
||||
private Dictionary<Vector2i, Vector2i> _horizLinesReversed = new();
|
||||
private Dictionary<Vector2i, Vector2i> _vertLines = new();
|
||||
private Dictionary<Vector2i, Vector2i> _vertLinesReversed = new();
|
||||
|
||||
// Components
|
||||
private NavMapComponent? _navMap;
|
||||
|
|
@ -376,7 +377,7 @@ public partial class NavMapControl : MapGridControl
|
|||
var fontSize = (int) Math.Round(1 / WorldRange * DefaultDisplayedRange * UIScale * _targetFontsize, 0);
|
||||
var font = new VectorFont(_cache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Bold.ttf"), fontSize);
|
||||
|
||||
foreach (var beacon in _navMap.Beacons)
|
||||
foreach (var beacon in _navMap.Beacons.Values)
|
||||
{
|
||||
var position = beacon.Position - offset;
|
||||
position = ScalePosition(position with { Y = -position.Y });
|
||||
|
|
@ -485,113 +486,106 @@ public partial class NavMapControl : MapGridControl
|
|||
return;
|
||||
|
||||
// We'll use the following dictionaries to combine collinear wall lines
|
||||
HorizLinesLookup.Clear();
|
||||
HorizLinesLookupReversed.Clear();
|
||||
VertLinesLookup.Clear();
|
||||
VertLinesLookupReversed.Clear();
|
||||
_horizLines.Clear();
|
||||
_horizLinesReversed.Clear();
|
||||
_vertLines.Clear();
|
||||
_vertLinesReversed.Clear();
|
||||
|
||||
foreach ((var (category, chunkOrigin), var chunk) in _navMap.Chunks)
|
||||
const int southMask = (int) AtmosDirection.South << (int) NavMapChunkType.Wall;
|
||||
const int eastMask = (int) AtmosDirection.East << (int) NavMapChunkType.Wall;
|
||||
const int westMask = (int) AtmosDirection.West << (int) NavMapChunkType.Wall;
|
||||
const int northMask = (int) AtmosDirection.North << (int) NavMapChunkType.Wall;
|
||||
|
||||
foreach (var (chunkOrigin, chunk) in _navMap.Chunks)
|
||||
{
|
||||
if (category != NavMapChunkType.Wall)
|
||||
continue;
|
||||
|
||||
for (var i = 0; i < SharedNavMapSystem.ChunkSize * SharedNavMapSystem.ChunkSize; i++)
|
||||
for (var i = 0; i < SharedNavMapSystem.ArraySize; i++)
|
||||
{
|
||||
var value = (ushort) Math.Pow(2, i);
|
||||
var mask = _navMapSystem.GetCombinedEdgesForChunk(chunk.TileData) & value;
|
||||
|
||||
if (mask == 0x0)
|
||||
var tileData = chunk.TileData[i] & SharedNavMapSystem.WallMask;
|
||||
if (tileData == 0)
|
||||
continue;
|
||||
|
||||
var relativeTile = SharedNavMapSystem.GetTile(mask);
|
||||
tileData >>= (int) NavMapChunkType.Wall;
|
||||
|
||||
var relativeTile = SharedNavMapSystem.GetTileFromIndex(i);
|
||||
var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relativeTile) * _grid.TileSize;
|
||||
|
||||
if (!_navMapSystem.AllTileEdgesAreOccupied(chunk.TileData, relativeTile))
|
||||
if (tileData != SharedNavMapSystem.AllDirMask)
|
||||
{
|
||||
AddRectForThinWall(chunk.TileData, tile);
|
||||
AddRectForThinWall(tileData, tile);
|
||||
continue;
|
||||
}
|
||||
|
||||
tile = tile with { Y = -tile.Y };
|
||||
|
||||
NavMapChunk? neighborChunk;
|
||||
bool neighbor;
|
||||
|
||||
// North edge
|
||||
if (relativeTile.Y == SharedNavMapSystem.ChunkSize - 1)
|
||||
{
|
||||
neighbor = _navMap.Chunks.TryGetValue((NavMapChunkType.Wall, chunkOrigin + new Vector2i(0, 1)), out neighborChunk) &&
|
||||
(neighborChunk.TileData[AtmosDirection.South] &
|
||||
SharedNavMapSystem.GetFlag(new Vector2i(relativeTile.X, 0))) != 0x0;
|
||||
}
|
||||
else
|
||||
{
|
||||
var flag = SharedNavMapSystem.GetFlag(relativeTile + new Vector2i(0, 1));
|
||||
neighbor = (chunk.TileData[AtmosDirection.South] & flag) != 0x0;
|
||||
}
|
||||
var neighborData = 0;
|
||||
if (relativeTile.Y != SharedNavMapSystem.ChunkSize - 1)
|
||||
neighborData = chunk.TileData[i+1];
|
||||
else if (_navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Up, out neighborChunk))
|
||||
neighborData = neighborChunk.TileData[i + 1 - SharedNavMapSystem.ChunkSize];
|
||||
|
||||
if (!neighbor)
|
||||
AddOrUpdateNavMapLine(tile + new Vector2i(0, -_grid.TileSize), tile + new Vector2i(_grid.TileSize, -_grid.TileSize), HorizLinesLookup, HorizLinesLookupReversed);
|
||||
if ((neighborData & southMask) == 0)
|
||||
{
|
||||
AddOrUpdateNavMapLine(tile + new Vector2i(0, -_grid.TileSize),
|
||||
tile + new Vector2i(_grid.TileSize, -_grid.TileSize), _horizLines,
|
||||
_horizLinesReversed);
|
||||
}
|
||||
|
||||
// East edge
|
||||
if (relativeTile.X == SharedNavMapSystem.ChunkSize - 1)
|
||||
{
|
||||
neighbor = _navMap.Chunks.TryGetValue((NavMapChunkType.Wall, chunkOrigin + new Vector2i(1, 0)), out neighborChunk) &&
|
||||
(neighborChunk.TileData[AtmosDirection.West] &
|
||||
SharedNavMapSystem.GetFlag(new Vector2i(0, relativeTile.Y))) != 0x0;
|
||||
}
|
||||
else
|
||||
{
|
||||
var flag = SharedNavMapSystem.GetFlag(relativeTile + new Vector2i(1, 0));
|
||||
neighbor = (chunk.TileData[AtmosDirection.West] & flag) != 0x0;
|
||||
}
|
||||
neighborData = 0;
|
||||
if (relativeTile.X != SharedNavMapSystem.ChunkSize - 1)
|
||||
neighborData = chunk.TileData[i+SharedNavMapSystem.ChunkSize];
|
||||
else if (_navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Right, out neighborChunk))
|
||||
neighborData = neighborChunk.TileData[i + SharedNavMapSystem.ChunkSize - SharedNavMapSystem.ArraySize];
|
||||
|
||||
if (!neighbor)
|
||||
AddOrUpdateNavMapLine(tile + new Vector2i(_grid.TileSize, -_grid.TileSize), tile + new Vector2i(_grid.TileSize, 0), VertLinesLookup, VertLinesLookupReversed);
|
||||
if ((neighborData & westMask) == 0)
|
||||
{
|
||||
AddOrUpdateNavMapLine(tile + new Vector2i(_grid.TileSize, -_grid.TileSize),
|
||||
tile + new Vector2i(_grid.TileSize, 0), _vertLines, _vertLinesReversed);
|
||||
}
|
||||
|
||||
// South edge
|
||||
if (relativeTile.Y == 0)
|
||||
{
|
||||
neighbor = _navMap.Chunks.TryGetValue((NavMapChunkType.Wall, chunkOrigin + new Vector2i(0, -1)), out neighborChunk) &&
|
||||
(neighborChunk.TileData[AtmosDirection.North] &
|
||||
SharedNavMapSystem.GetFlag(new Vector2i(relativeTile.X, SharedNavMapSystem.ChunkSize - 1))) != 0x0;
|
||||
}
|
||||
else
|
||||
{
|
||||
var flag = SharedNavMapSystem.GetFlag(relativeTile + new Vector2i(0, -1));
|
||||
neighbor = (chunk.TileData[AtmosDirection.North] & flag) != 0x0;
|
||||
}
|
||||
neighborData = 0;
|
||||
if (relativeTile.Y != 0)
|
||||
neighborData = chunk.TileData[i-1];
|
||||
else if (_navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Down, out neighborChunk))
|
||||
neighborData = neighborChunk.TileData[i - 1 + SharedNavMapSystem.ChunkSize];
|
||||
|
||||
if (!neighbor)
|
||||
AddOrUpdateNavMapLine(tile, tile + new Vector2i(_grid.TileSize, 0), HorizLinesLookup, HorizLinesLookupReversed);
|
||||
if ((neighborData & northMask) == 0)
|
||||
{
|
||||
AddOrUpdateNavMapLine(tile, tile + new Vector2i(_grid.TileSize, 0), _horizLines,
|
||||
_horizLinesReversed);
|
||||
}
|
||||
|
||||
// West edge
|
||||
if (relativeTile.X == 0)
|
||||
{
|
||||
neighbor = _navMap.Chunks.TryGetValue((NavMapChunkType.Wall, chunkOrigin + new Vector2i(-1, 0)), out neighborChunk) &&
|
||||
(neighborChunk.TileData[AtmosDirection.East] &
|
||||
SharedNavMapSystem.GetFlag(new Vector2i(SharedNavMapSystem.ChunkSize - 1, relativeTile.Y))) != 0x0;
|
||||
}
|
||||
else
|
||||
{
|
||||
var flag = SharedNavMapSystem.GetFlag(relativeTile + new Vector2i(-1, 0));
|
||||
neighbor = (chunk.TileData[AtmosDirection.East] & flag) != 0x0;
|
||||
}
|
||||
neighborData = 0;
|
||||
if (relativeTile.X != 0)
|
||||
neighborData = chunk.TileData[i-SharedNavMapSystem.ChunkSize];
|
||||
else if (_navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Left, out neighborChunk))
|
||||
neighborData = neighborChunk.TileData[i - SharedNavMapSystem.ChunkSize + SharedNavMapSystem.ArraySize];
|
||||
|
||||
if (!neighbor)
|
||||
AddOrUpdateNavMapLine(tile + new Vector2i(0, -_grid.TileSize), tile, VertLinesLookup, VertLinesLookupReversed);
|
||||
if ((neighborData & eastMask) == 0)
|
||||
{
|
||||
AddOrUpdateNavMapLine(tile + new Vector2i(0, -_grid.TileSize), tile, _vertLines,
|
||||
_vertLinesReversed);
|
||||
}
|
||||
|
||||
// Add a diagonal line for interiors. Unless there are a lot of double walls, there is no point combining these
|
||||
TileLines.Add((tile + new Vector2(0, -_grid.TileSize), tile + new Vector2(_grid.TileSize, 0)));
|
||||
}
|
||||
}
|
||||
|
||||
// Record the combined lines
|
||||
foreach (var (origin, terminal) in HorizLinesLookup)
|
||||
TileLines.Add((origin.Item2, terminal.Item2));
|
||||
// Record the combined lines
|
||||
foreach (var (origin, terminal) in _horizLines)
|
||||
{
|
||||
TileLines.Add((origin, terminal));
|
||||
}
|
||||
|
||||
foreach (var (origin, terminal) in VertLinesLookup)
|
||||
TileLines.Add((origin.Item2, terminal.Item2));
|
||||
foreach (var (origin, terminal) in _vertLines)
|
||||
{
|
||||
TileLines.Add((origin, terminal));
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateNavMapAirlocks()
|
||||
|
|
@ -599,26 +593,23 @@ public partial class NavMapControl : MapGridControl
|
|||
if (_navMap == null || _grid == null)
|
||||
return;
|
||||
|
||||
foreach (var ((category, _), chunk) in _navMap.Chunks)
|
||||
foreach (var chunk in _navMap.Chunks.Values)
|
||||
{
|
||||
if (category != NavMapChunkType.Airlock)
|
||||
continue;
|
||||
|
||||
for (var i = 0; i < SharedNavMapSystem.ChunkSize * SharedNavMapSystem.ChunkSize; i++)
|
||||
for (var i = 0; i < SharedNavMapSystem.ArraySize; i++)
|
||||
{
|
||||
var value = (int) Math.Pow(2, i);
|
||||
var mask = _navMapSystem.GetCombinedEdgesForChunk(chunk.TileData) & value;
|
||||
|
||||
if (mask == 0x0)
|
||||
var tileData = chunk.TileData[i] & SharedNavMapSystem.AirlockMask;
|
||||
if (tileData == 0)
|
||||
continue;
|
||||
|
||||
var relative = SharedNavMapSystem.GetTile(mask);
|
||||
tileData >>= (int) NavMapChunkType.Airlock;
|
||||
|
||||
var relative = SharedNavMapSystem.GetTileFromIndex(i);
|
||||
var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relative) * _grid.TileSize;
|
||||
|
||||
// If the edges of an airlock tile are not all occupied, draw a thin airlock for each edge
|
||||
if (!_navMapSystem.AllTileEdgesAreOccupied(chunk.TileData, relative))
|
||||
if (tileData != SharedNavMapSystem.AllDirMask)
|
||||
{
|
||||
AddRectForThinAirlock(chunk.TileData, tile);
|
||||
AddRectForThinAirlock(tileData, tile);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -632,108 +623,90 @@ public partial class NavMapControl : MapGridControl
|
|||
}
|
||||
}
|
||||
|
||||
private void AddRectForThinWall(Dictionary<AtmosDirection, ushort> tileData, Vector2i tile)
|
||||
private void AddRectForThinWall(int tileData, Vector2i tile)
|
||||
{
|
||||
if (_navMapSystem == null || _grid == null)
|
||||
return;
|
||||
var leftTop = new Vector2(-0.5f, 0.5f - ThinWallThickness);
|
||||
var rightBottom = new Vector2(0.5f, 0.5f);
|
||||
|
||||
var leftTop = new Vector2(-0.5f, -0.5f + ThinWallThickness);
|
||||
var rightBottom = new Vector2(0.5f, -0.5f);
|
||||
|
||||
foreach (var (direction, mask) in tileData)
|
||||
for (var i = 0; i < SharedNavMapSystem.Directions; i++)
|
||||
{
|
||||
var relative = SharedMapSystem.GetChunkRelative(tile, SharedNavMapSystem.ChunkSize);
|
||||
var flag = (ushort) SharedNavMapSystem.GetFlag(relative);
|
||||
|
||||
if ((mask & flag) == 0)
|
||||
var dirMask = 1 << i;
|
||||
if ((tileData & dirMask) == 0)
|
||||
continue;
|
||||
|
||||
var tilePosition = new Vector2(tile.X + 0.5f, -tile.Y - 0.5f);
|
||||
var angle = new Angle(0);
|
||||
|
||||
switch (direction)
|
||||
{
|
||||
case AtmosDirection.East: angle = new Angle(MathF.PI * 0.5f); break;
|
||||
case AtmosDirection.South: angle = new Angle(MathF.PI); break;
|
||||
case AtmosDirection.West: angle = new Angle(MathF.PI * -0.5f); break;
|
||||
}
|
||||
|
||||
// TODO NAVMAP
|
||||
// Consider using faster rotation operations, given that these are always 90 degree increments
|
||||
var angle = -((AtmosDirection) dirMask).ToAngle();
|
||||
TileRects.Add((angle.RotateVec(leftTop) + tilePosition, angle.RotateVec(rightBottom) + tilePosition));
|
||||
}
|
||||
}
|
||||
|
||||
private void AddRectForThinAirlock(Dictionary<AtmosDirection, ushort> tileData, Vector2i tile)
|
||||
private void AddRectForThinAirlock(int tileData, Vector2i tile)
|
||||
{
|
||||
if (_navMapSystem == null || _grid == null)
|
||||
return;
|
||||
var leftTop = new Vector2(-0.5f + FullWallInstep, 0.5f - FullWallInstep - ThinDoorThickness);
|
||||
var rightBottom = new Vector2(0.5f - FullWallInstep, 0.5f - FullWallInstep);
|
||||
var centreTop = new Vector2(0f, 0.5f - FullWallInstep - ThinDoorThickness);
|
||||
var centreBottom = new Vector2(0f, 0.5f - FullWallInstep);
|
||||
|
||||
var leftTop = new Vector2(-0.5f + FullWallInstep, -0.5f + FullWallInstep + ThinDoorThickness);
|
||||
var rightBottom = new Vector2(0.5f - FullWallInstep, -0.5f + FullWallInstep);
|
||||
var centreTop = new Vector2(0f, -0.5f + FullWallInstep + ThinDoorThickness);
|
||||
var centreBottom = new Vector2(0f, -0.5f + FullWallInstep);
|
||||
|
||||
foreach (var (direction, mask) in tileData)
|
||||
for (var i = 0; i < SharedNavMapSystem.Directions; i++)
|
||||
{
|
||||
var relative = SharedMapSystem.GetChunkRelative(tile, SharedNavMapSystem.ChunkSize);
|
||||
var flag = (ushort) SharedNavMapSystem.GetFlag(relative);
|
||||
|
||||
if ((mask & flag) == 0)
|
||||
var dirMask = 1 << i;
|
||||
if ((tileData & dirMask) == 0)
|
||||
continue;
|
||||
|
||||
var tilePosition = new Vector2(tile.X + 0.5f, -tile.Y - 0.5f);
|
||||
var angle = new Angle(0);
|
||||
|
||||
switch (direction)
|
||||
{
|
||||
case AtmosDirection.East: angle = new Angle(MathF.PI * 0.5f);break;
|
||||
case AtmosDirection.South: angle = new Angle(MathF.PI); break;
|
||||
case AtmosDirection.West: angle = new Angle(MathF.PI * -0.5f); break;
|
||||
}
|
||||
|
||||
var angle = -((AtmosDirection) dirMask).ToAngle();
|
||||
TileRects.Add((angle.RotateVec(leftTop) + tilePosition, angle.RotateVec(rightBottom) + tilePosition));
|
||||
TileLines.Add((angle.RotateVec(centreTop) + tilePosition, angle.RotateVec(centreBottom) + tilePosition));
|
||||
}
|
||||
}
|
||||
|
||||
protected void AddOrUpdateNavMapLine
|
||||
(Vector2i origin,
|
||||
protected void AddOrUpdateNavMapLine(
|
||||
Vector2i origin,
|
||||
Vector2i terminus,
|
||||
Dictionary<(int, Vector2i), (int, Vector2i)> lookup,
|
||||
Dictionary<(int, Vector2i), (int, Vector2i)> lookupReversed,
|
||||
int index = 0)
|
||||
Dictionary<Vector2i, Vector2i> lookup,
|
||||
Dictionary<Vector2i, Vector2i> lookupReversed)
|
||||
{
|
||||
(int, Vector2i) foundTermiusTuple;
|
||||
(int, Vector2i) foundOriginTuple;
|
||||
Vector2i foundTermius;
|
||||
Vector2i foundOrigin;
|
||||
|
||||
if (lookup.TryGetValue((index, terminus), out foundTermiusTuple) &&
|
||||
lookupReversed.TryGetValue((index, origin), out foundOriginTuple))
|
||||
// Does our new line end at the beginning of an existing line?
|
||||
if (lookup.Remove(terminus, out foundTermius))
|
||||
{
|
||||
lookup[foundOriginTuple] = foundTermiusTuple;
|
||||
lookupReversed[foundTermiusTuple] = foundOriginTuple;
|
||||
DebugTools.Assert(lookupReversed[foundTermius] == terminus);
|
||||
|
||||
lookup.Remove((index, terminus));
|
||||
lookupReversed.Remove((index, origin));
|
||||
// Does our new line start at the end of an existing line?
|
||||
if (lookupReversed.Remove(origin, out foundOrigin))
|
||||
{
|
||||
// Our new line just connects two existing lines
|
||||
DebugTools.Assert(lookup[foundOrigin] == origin);
|
||||
lookup[foundOrigin] = foundTermius;
|
||||
lookupReversed[foundTermius] = foundOrigin;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Our new line precedes an existing line, extending it further to the left
|
||||
lookup[origin] = foundTermius;
|
||||
lookupReversed[foundTermius] = origin;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
else if (lookup.TryGetValue((index, terminus), out foundTermiusTuple))
|
||||
// Does our new line start at the end of an existing line?
|
||||
if (lookupReversed.Remove(origin, out foundOrigin))
|
||||
{
|
||||
lookup[(index, origin)] = foundTermiusTuple;
|
||||
lookup.Remove((index, terminus));
|
||||
lookupReversed[foundTermiusTuple] = (index, origin);
|
||||
// Our new line just extends an existing line further to the right
|
||||
DebugTools.Assert(lookup[foundOrigin] == origin);
|
||||
lookup[foundOrigin] = terminus;
|
||||
lookupReversed[terminus] = foundOrigin;
|
||||
return;
|
||||
}
|
||||
|
||||
else if (lookupReversed.TryGetValue((index, origin), out foundOriginTuple))
|
||||
{
|
||||
lookupReversed[(index, terminus)] = foundOriginTuple;
|
||||
lookupReversed.Remove(foundOriginTuple);
|
||||
lookup[foundOriginTuple] = (index, terminus);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
lookup.Add((index, origin), (index, terminus));
|
||||
lookupReversed.Add((index, terminus), (index, origin));
|
||||
}
|
||||
// Completely disconnected line segment.
|
||||
lookup.Add(origin, terminus);
|
||||
lookupReversed.Add(terminus, origin);
|
||||
}
|
||||
|
||||
protected Vector2 GetOffset()
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using Content.Client.Smoking;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Polymorph.Components;
|
||||
using Content.Shared.Polymorph.Systems;
|
||||
|
|
@ -10,14 +11,19 @@ public sealed class ChameleonProjectorSystem : SharedChameleonProjectorSystem
|
|||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
|
||||
private EntityQuery<AppearanceComponent> _appearanceQuery;
|
||||
private EntityQuery<SpriteComponent> _spriteQuery;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_appearanceQuery = GetEntityQuery<AppearanceComponent>();
|
||||
_spriteQuery = GetEntityQuery<SpriteComponent>();
|
||||
|
||||
SubscribeLocalEvent<ChameleonDisguiseComponent, AfterAutoHandleStateEvent>(OnHandleState);
|
||||
|
||||
SubscribeLocalEvent<ChameleonDisguisedComponent, ComponentStartup>(OnStartup);
|
||||
SubscribeLocalEvent<ChameleonDisguisedComponent, ComponentShutdown>(OnShutdown);
|
||||
}
|
||||
|
||||
private void OnHandleState(Entity<ChameleonDisguiseComponent> ent, ref AfterAutoHandleStateEvent args)
|
||||
|
|
@ -25,9 +31,25 @@ public sealed class ChameleonProjectorSystem : SharedChameleonProjectorSystem
|
|||
CopyComp<SpriteComponent>(ent);
|
||||
CopyComp<GenericVisualizerComponent>(ent);
|
||||
CopyComp<SolutionContainerVisualsComponent>(ent);
|
||||
CopyComp<BurnStateVisualsComponent>(ent);
|
||||
|
||||
// reload appearance to hopefully prevent any invisible layers
|
||||
if (_appearanceQuery.TryComp(ent, out var appearance))
|
||||
_appearance.QueueUpdate(ent, appearance);
|
||||
}
|
||||
|
||||
private void OnStartup(Entity<ChameleonDisguisedComponent> ent, ref ComponentStartup args)
|
||||
{
|
||||
if (!_spriteQuery.TryComp(ent, out var sprite))
|
||||
return;
|
||||
|
||||
ent.Comp.WasVisible = sprite.Visible;
|
||||
sprite.Visible = false;
|
||||
}
|
||||
|
||||
private void OnShutdown(Entity<ChameleonDisguisedComponent> ent, ref ComponentShutdown args)
|
||||
{
|
||||
if (_spriteQuery.TryComp(ent, out var sprite))
|
||||
sprite.Visible = ent.Comp.WasVisible;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using Robust.Client.Graphics;
|
|||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Map.Components;
|
||||
using System.Numerics;
|
||||
using static Content.Shared.Power.SharedPowerMonitoringConsoleSystem;
|
||||
|
||||
namespace Content.Client.Power;
|
||||
|
||||
|
|
@ -26,6 +27,11 @@ public sealed partial class PowerMonitoringConsoleNavMapControl : NavMapControl
|
|||
public List<PowerMonitoringConsoleLine> PowerCableNetwork = new();
|
||||
public List<PowerMonitoringConsoleLine> FocusCableNetwork = new();
|
||||
|
||||
private Dictionary<Vector2i, Vector2i>[] _horizLines = [new(), new(), new()];
|
||||
private Dictionary<Vector2i, Vector2i>[] _horizLinesReversed = [new(), new(), new()];
|
||||
private Dictionary<Vector2i, Vector2i>[] _vertLines = [new(), new(), new()];
|
||||
private Dictionary<Vector2i, Vector2i>[] _vertLinesReversed = [new(), new(), new()];
|
||||
|
||||
private MapGridComponent? _grid;
|
||||
|
||||
public PowerMonitoringConsoleNavMapControl() : base()
|
||||
|
|
@ -182,28 +188,32 @@ public sealed partial class PowerMonitoringConsoleNavMapControl : NavMapControl
|
|||
if (chunks == null)
|
||||
return decodedOutput;
|
||||
|
||||
// We'll use the following dictionaries to combine collinear power cable lines
|
||||
HorizLinesLookup.Clear();
|
||||
HorizLinesLookupReversed.Clear();
|
||||
VertLinesLookup.Clear();
|
||||
VertLinesLookupReversed.Clear();
|
||||
Array.ForEach(_horizLines, x=> x.Clear());
|
||||
Array.ForEach(_horizLinesReversed, x=> x.Clear());
|
||||
Array.ForEach(_vertLines, x=> x.Clear());
|
||||
Array.ForEach(_vertLinesReversed, x=> x.Clear());
|
||||
|
||||
foreach ((var chunkOrigin, var chunk) in chunks)
|
||||
foreach (var (chunkOrigin, chunk) in chunks)
|
||||
{
|
||||
for (int cableIdx = 0; cableIdx < chunk.PowerCableData.Length; cableIdx++)
|
||||
for (var cableIdx = 0; cableIdx < 3; cableIdx++)
|
||||
{
|
||||
var horizLines = _horizLines[cableIdx];
|
||||
var horizLinesReversed = _horizLinesReversed[cableIdx];
|
||||
var vertLines = _vertLines[cableIdx];
|
||||
var vertLinesReversed = _vertLinesReversed[cableIdx];
|
||||
|
||||
var chunkMask = chunk.PowerCableData[cableIdx];
|
||||
|
||||
for (var chunkIdx = 0; chunkIdx < SharedNavMapSystem.ChunkSize * SharedNavMapSystem.ChunkSize; chunkIdx++)
|
||||
for (var chunkIdx = 0; chunkIdx < ChunkSize * ChunkSize; chunkIdx++)
|
||||
{
|
||||
var value = (int) Math.Pow(2, chunkIdx);
|
||||
var value = 1 << chunkIdx;
|
||||
var mask = chunkMask & value;
|
||||
|
||||
if (mask == 0x0)
|
||||
continue;
|
||||
|
||||
var relativeTile = SharedNavMapSystem.GetTile(mask);
|
||||
var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relativeTile) * _grid.TileSize;
|
||||
var relativeTile = GetTileFromIndex(chunkIdx);
|
||||
var tile = (chunk.Origin * ChunkSize + relativeTile) * _grid.TileSize;
|
||||
tile = tile with { Y = -tile.Y };
|
||||
|
||||
PowerCableChunk neighborChunk;
|
||||
|
|
@ -212,39 +222,39 @@ public sealed partial class PowerMonitoringConsoleNavMapControl : NavMapControl
|
|||
// Note: we only check the north and east neighbors
|
||||
|
||||
// East
|
||||
if (relativeTile.X == SharedNavMapSystem.ChunkSize - 1)
|
||||
if (relativeTile.X == ChunkSize - 1)
|
||||
{
|
||||
neighbor = chunks.TryGetValue(chunkOrigin + new Vector2i(1, 0), out neighborChunk) &&
|
||||
(neighborChunk.PowerCableData[cableIdx] & SharedNavMapSystem.GetFlag(new Vector2i(0, relativeTile.Y))) != 0x0;
|
||||
(neighborChunk.PowerCableData[cableIdx] & GetFlag(new Vector2i(0, relativeTile.Y))) != 0x0;
|
||||
}
|
||||
else
|
||||
{
|
||||
var flag = SharedNavMapSystem.GetFlag(relativeTile + new Vector2i(1, 0));
|
||||
var flag = GetFlag(relativeTile + new Vector2i(1, 0));
|
||||
neighbor = (chunkMask & flag) != 0x0;
|
||||
}
|
||||
|
||||
if (neighbor)
|
||||
{
|
||||
// Add points
|
||||
AddOrUpdateNavMapLine(tile, tile + new Vector2i(_grid.TileSize, 0), HorizLinesLookup, HorizLinesLookupReversed, cableIdx);
|
||||
AddOrUpdateNavMapLine(tile, tile + new Vector2i(_grid.TileSize, 0), horizLines, horizLinesReversed);
|
||||
}
|
||||
|
||||
// North
|
||||
if (relativeTile.Y == SharedNavMapSystem.ChunkSize - 1)
|
||||
if (relativeTile.Y == ChunkSize - 1)
|
||||
{
|
||||
neighbor = chunks.TryGetValue(chunkOrigin + new Vector2i(0, 1), out neighborChunk) &&
|
||||
(neighborChunk.PowerCableData[cableIdx] & SharedNavMapSystem.GetFlag(new Vector2i(relativeTile.X, 0))) != 0x0;
|
||||
(neighborChunk.PowerCableData[cableIdx] & GetFlag(new Vector2i(relativeTile.X, 0))) != 0x0;
|
||||
}
|
||||
else
|
||||
{
|
||||
var flag = SharedNavMapSystem.GetFlag(relativeTile + new Vector2i(0, 1));
|
||||
var flag = GetFlag(relativeTile + new Vector2i(0, 1));
|
||||
neighbor = (chunkMask & flag) != 0x0;
|
||||
}
|
||||
|
||||
if (neighbor)
|
||||
{
|
||||
// Add points
|
||||
AddOrUpdateNavMapLine(tile + new Vector2i(0, -_grid.TileSize), tile, VertLinesLookup, VertLinesLookupReversed, cableIdx);
|
||||
AddOrUpdateNavMapLine(tile + new Vector2i(0, -_grid.TileSize), tile, vertLines, vertLinesReversed);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -253,11 +263,25 @@ public sealed partial class PowerMonitoringConsoleNavMapControl : NavMapControl
|
|||
|
||||
var gridOffset = new Vector2(_grid.TileSize * 0.5f, -_grid.TileSize * 0.5f);
|
||||
|
||||
foreach (var (origin, terminal) in HorizLinesLookup)
|
||||
decodedOutput.Add(new PowerMonitoringConsoleLine(origin.Item2 + gridOffset, terminal.Item2 + gridOffset, (PowerMonitoringConsoleLineGroup) origin.Item1));
|
||||
for (var index = 0; index < _horizLines.Length; index++)
|
||||
{
|
||||
var horizLines = _horizLines[index];
|
||||
foreach (var (origin, terminal) in horizLines)
|
||||
{
|
||||
decodedOutput.Add(new PowerMonitoringConsoleLine(origin + gridOffset, terminal + gridOffset,
|
||||
(PowerMonitoringConsoleLineGroup) index));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (origin, terminal) in VertLinesLookup)
|
||||
decodedOutput.Add(new PowerMonitoringConsoleLine(origin.Item2 + gridOffset, terminal.Item2 + gridOffset, (PowerMonitoringConsoleLineGroup) origin.Item1));
|
||||
for (var index = 0; index < _vertLines.Length; index++)
|
||||
{
|
||||
var vertLines = _vertLines[index];
|
||||
foreach (var (origin, terminal) in vertLines)
|
||||
{
|
||||
decodedOutput.Add(new PowerMonitoringConsoleLine(origin + gridOffset, terminal + gridOffset,
|
||||
(PowerMonitoringConsoleLineGroup) index));
|
||||
}
|
||||
}
|
||||
|
||||
return decodedOutput;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ namespace Content.Server.Atmos.EntitySystems
|
|||
airtight.LastPosition = (gridId.Value, tilePos);
|
||||
InvalidatePosition(gridId.Value, tilePos);
|
||||
|
||||
var airtightEv = new AirtightChanged(uid, airtight, (gridId.Value, tilePos));
|
||||
var airtightEv = new AirtightChanged(uid, airtight, false, (gridId.Value, tilePos));
|
||||
RaiseLocalEvent(uid, ref airtightEv, true);
|
||||
}
|
||||
|
||||
|
|
@ -78,7 +78,7 @@ namespace Content.Server.Atmos.EntitySystems
|
|||
airtight.LastPosition = (gridId, args.TilePos);
|
||||
InvalidatePosition(gridId, args.TilePos);
|
||||
|
||||
var airtightEv = new AirtightChanged(uid, airtight, (gridId, args.TilePos));
|
||||
var airtightEv = new AirtightChanged(uid, airtight, false, (gridId, args.TilePos));
|
||||
RaiseLocalEvent(uid, ref airtightEv, true);
|
||||
}
|
||||
}
|
||||
|
|
@ -95,7 +95,7 @@ namespace Content.Server.Atmos.EntitySystems
|
|||
airtight.CurrentAirBlockedDirection = (int) Rotate((AtmosDirection)airtight.InitialAirBlockedDirection, ev.NewRotation);
|
||||
var pos = airtight.LastPosition;
|
||||
UpdatePosition(ent, ev.Component);
|
||||
var airtightEv = new AirtightChanged(owner, airtight, pos);
|
||||
var airtightEv = new AirtightChanged(owner, airtight, false, pos);
|
||||
RaiseLocalEvent(owner, ref airtightEv, true);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -111,7 +111,7 @@ namespace Content.Server.Atmos.EntitySystems
|
|||
var pos = airtight.Comp.LastPosition;
|
||||
airtight.Comp.AirBlocked = airblocked;
|
||||
UpdatePosition(airtight, xform);
|
||||
var airtightEv = new AirtightChanged(airtight, airtight, pos);
|
||||
var airtightEv = new AirtightChanged(airtight, airtight, true, pos);
|
||||
RaiseLocalEvent(airtight, ref airtightEv, true);
|
||||
}
|
||||
|
||||
|
|
@ -158,6 +158,13 @@ namespace Content.Server.Atmos.EntitySystems
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised upon the airtight status being changed via anchoring, movement, etc.
|
||||
/// </summary>
|
||||
/// <param name="Entity"></param>
|
||||
/// <param name="Airtight"></param>
|
||||
/// <param name="AirBlockedChanged">Whether the <see cref="AirtightComponent.AirBlocked"/> changed</param>
|
||||
/// <param name="Position"></param>
|
||||
[ByRefEvent]
|
||||
public readonly record struct AirtightChanged(EntityUid Entity, AirtightComponent Airtight, (EntityUid Grid, Vector2i Tile) Position);
|
||||
public readonly record struct AirtightChanged(EntityUid Entity, AirtightComponent Airtight, bool AirBlockedChanged, (EntityUid Grid, Vector2i Tile) Position);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using System.Numerics;
|
||||
using System.Threading;
|
||||
using Content.Server.DoAfter;
|
||||
using Content.Server.Body.Systems;
|
||||
|
|
@ -5,6 +6,7 @@ using Content.Server.Hands.Systems;
|
|||
using Content.Server.Resist;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Inventory;
|
||||
using Content.Server.Nyanotrasen.Item.PseudoItem;
|
||||
using Content.Shared.Climbing; // Shared instead of Server
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.DoAfter;
|
||||
|
|
@ -22,11 +24,14 @@ using Content.Shared.Pulling;
|
|||
using Content.Shared.Standing;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Inventory.VirtualItem;
|
||||
using Content.Shared.Item;
|
||||
using Content.Shared.Throwing;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Movement.Pulling.Components;
|
||||
using Content.Shared.Movement.Pulling.Events;
|
||||
using Content.Shared.Movement.Pulling.Systems;
|
||||
using Content.Shared.Nyanotrasen.Item.PseudoItem;
|
||||
using Content.Shared.Storage;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Physics.Components;
|
||||
|
||||
|
|
@ -45,11 +50,13 @@ namespace Content.Server.Carrying
|
|||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly MovementSpeedModifierSystem _movementSpeed = default!;
|
||||
[Dependency] private readonly RespiratorSystem _respirator = default!;
|
||||
[Dependency] private readonly PseudoItemSystem _pseudoItem = default!; // Needed for fitting check
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<CarriableComponent, GetVerbsEvent<AlternativeVerb>>(AddCarryVerb);
|
||||
SubscribeLocalEvent<CarryingComponent, GetVerbsEvent<InnateVerb>>(AddInsertCarriedVerb);
|
||||
SubscribeLocalEvent<CarryingComponent, VirtualItemDeletedEvent>(OnVirtualItemDeleted);
|
||||
SubscribeLocalEvent<CarryingComponent, BeforeThrowEvent>(OnThrow);
|
||||
SubscribeLocalEvent<CarryingComponent, EntParentChangedMessage>(OnParentChanged);
|
||||
|
|
@ -65,7 +72,6 @@ namespace Content.Server.Carrying
|
|||
SubscribeLocalEvent<CarriableComponent, CarryDoAfterEvent>(OnDoAfter);
|
||||
}
|
||||
|
||||
|
||||
private void AddCarryVerb(EntityUid uid, CarriableComponent component, GetVerbsEvent<AlternativeVerb> args)
|
||||
{
|
||||
if (!args.CanInteract || !args.CanAccess)
|
||||
|
|
@ -98,6 +104,33 @@ namespace Content.Server.Carrying
|
|||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
private void AddInsertCarriedVerb(EntityUid uid, CarryingComponent component, GetVerbsEvent<InnateVerb> args)
|
||||
{
|
||||
// If the person is carrying someone, and the carried person is a pseudo-item, and the target entity is a storage,
|
||||
// then add an action to insert the carried entity into the target
|
||||
var toInsert = args.Using;
|
||||
if (toInsert is not { Valid: true } || !args.CanAccess || !TryComp<PseudoItemComponent>(toInsert, out var pseudoItem))
|
||||
return;
|
||||
|
||||
if (!TryComp<StorageComponent>(args.Target, out var storageComp))
|
||||
return;
|
||||
|
||||
if (!_pseudoItem.CheckItemFits((toInsert.Value, pseudoItem), (args.Target, storageComp)))
|
||||
return;
|
||||
|
||||
InnateVerb verb = new()
|
||||
{
|
||||
Act = () =>
|
||||
{
|
||||
DropCarried(uid, toInsert.Value);
|
||||
_pseudoItem.TryInsert(args.Target, toInsert.Value, pseudoItem, storageComp);
|
||||
},
|
||||
Text = Loc.GetString("action-name-insert-other", ("target", toInsert)),
|
||||
Priority = 2
|
||||
};
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Since the carried entity is stored as 2 virtual items, when deleted we want to drop them.
|
||||
/// </summary>
|
||||
|
|
@ -126,7 +159,12 @@ namespace Content.Server.Carrying
|
|||
|
||||
private void OnParentChanged(EntityUid uid, CarryingComponent component, ref EntParentChangedMessage args)
|
||||
{
|
||||
if (Transform(uid).MapID != args.OldMapId)
|
||||
var xform = Transform(uid);
|
||||
if (xform.MapID != args.OldMapId)
|
||||
return;
|
||||
|
||||
// Do not drop the carried entity if the new parent is a grid
|
||||
if (xform.ParentUid == xform.GridUid)
|
||||
return;
|
||||
|
||||
DropCarried(uid, component.Carried);
|
||||
|
|
@ -159,9 +197,13 @@ namespace Content.Server.Carrying
|
|||
if (!TryComp<CanEscapeInventoryComponent>(uid, out var escape))
|
||||
return;
|
||||
|
||||
if (!args.HasDirectionalMovement)
|
||||
return;
|
||||
|
||||
if (_actionBlockerSystem.CanInteract(uid, component.Carrier))
|
||||
{
|
||||
_escapeInventorySystem.AttemptEscape(uid, component.Carrier, escape, MassContest(uid, component.Carrier));
|
||||
// Note: the mass contest is inverted because weaker entities are supposed to take longer to escape
|
||||
_escapeInventorySystem.AttemptEscape(uid, component.Carrier, escape, MassContest(component.Carrier, uid));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -210,12 +252,7 @@ namespace Content.Server.Carrying
|
|||
}
|
||||
private void StartCarryDoAfter(EntityUid carrier, EntityUid carried, CarriableComponent component)
|
||||
{
|
||||
TimeSpan length = TimeSpan.FromSeconds(3);
|
||||
|
||||
var mod = MassContest(carrier, carried);
|
||||
|
||||
if (mod != 0)
|
||||
length /= mod;
|
||||
TimeSpan length = GetPickupDuration(carrier, carried);
|
||||
|
||||
if (length >= TimeSpan.FromSeconds(9))
|
||||
{
|
||||
|
|
@ -236,6 +273,9 @@ namespace Content.Server.Carrying
|
|||
};
|
||||
|
||||
_doAfterSystem.TryStartDoAfter(args);
|
||||
|
||||
// Show a popup to the person getting picked up
|
||||
_popupSystem.PopupEntity(Loc.GetString("carry-started", ("carrier", carrier)), carried, carried);
|
||||
}
|
||||
|
||||
private void Carry(EntityUid carrier, EntityUid carried)
|
||||
|
|
@ -260,6 +300,26 @@ namespace Content.Server.Carrying
|
|||
_actionBlockerSystem.UpdateCanMove(carried);
|
||||
}
|
||||
|
||||
public bool TryCarry(EntityUid carrier, EntityUid toCarry, CarriableComponent? carriedComp = null)
|
||||
{
|
||||
if (!Resolve(toCarry, ref carriedComp, false))
|
||||
return false;
|
||||
|
||||
if (!CanCarry(carrier, toCarry, carriedComp))
|
||||
return false;
|
||||
|
||||
// The second one means that carrier is a pseudo-item and is inside a bag.
|
||||
if (HasComp<BeingCarriedComponent>(carrier) || HasComp<ItemComponent>(carrier))
|
||||
return false;
|
||||
|
||||
if (GetPickupDuration(carrier, toCarry) > TimeSpan.FromSeconds(9))
|
||||
return false;
|
||||
|
||||
Carry(carrier, toCarry);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void DropCarried(EntityUid carrier, EntityUid carried)
|
||||
{
|
||||
RemComp<CarryingComponent>(carrier); // get rid of this first so we don't recusrively fire that event
|
||||
|
|
@ -323,5 +383,43 @@ namespace Content.Server.Carrying
|
|||
|
||||
return rollerPhysics.FixturesMass / targetPhysics.FixturesMass;
|
||||
}
|
||||
|
||||
private TimeSpan GetPickupDuration(EntityUid carrier, EntityUid carried)
|
||||
{
|
||||
var length = TimeSpan.FromSeconds(3);
|
||||
|
||||
var mod = MassContest(carrier, carried);
|
||||
if (mod != 0)
|
||||
length /= mod;
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
var query = EntityQueryEnumerator<BeingCarriedComponent>();
|
||||
while (query.MoveNext(out var carried, out var comp))
|
||||
{
|
||||
var carrier = comp.Carrier;
|
||||
if (carrier is not { Valid: true } || carried is not { Valid: true })
|
||||
continue;
|
||||
|
||||
// SOMETIMES - when an entity is inserted into disposals, or a cryosleep chamber - it can get re-parented without a proper reparent event
|
||||
// when this happens, it needs to be dropped because it leads to weird behavior
|
||||
if (Transform(carried).ParentUid != carrier)
|
||||
{
|
||||
DropCarried(carrier, carried);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Make sure the carried entity is always centered relative to the carrier, as gravity pulls can offset it otherwise
|
||||
var xform = Transform(carried);
|
||||
if (!xform.LocalPosition.Equals(Vector2.Zero))
|
||||
{
|
||||
xform.LocalPosition = Vector2.Zero;
|
||||
}
|
||||
}
|
||||
query.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
using Content.Server.DoAfter;
|
||||
using Content.Server.Carrying;
|
||||
using Content.Server.DoAfter;
|
||||
using Content.Server.Item;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Storage.EntitySystems;
|
||||
using Content.Shared.Bed.Sleep;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Item;
|
||||
|
|
@ -17,12 +20,14 @@ public sealed class PseudoItemSystem : SharedPseudoItemSystem
|
|||
[Dependency] private readonly StorageSystem _storage = default!;
|
||||
[Dependency] private readonly ItemSystem _item = default!;
|
||||
[Dependency] private readonly DoAfterSystem _doAfter = default!;
|
||||
|
||||
[Dependency] private readonly CarryingSystem _carrying = default!;
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<PseudoItemComponent, GetVerbsEvent<AlternativeVerb>>(AddInsertAltVerb);
|
||||
SubscribeLocalEvent<PseudoItemComponent, TryingToSleepEvent>(OnTrySleeping);
|
||||
}
|
||||
|
||||
private void AddInsertAltVerb(EntityUid uid, PseudoItemComponent component, GetVerbsEvent<AlternativeVerb> args)
|
||||
|
|
@ -53,4 +58,25 @@ public sealed class PseudoItemSystem : SharedPseudoItemSystem
|
|||
};
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
protected override void OnGettingPickedUpAttempt(EntityUid uid, PseudoItemComponent component, GettingPickedUpAttemptEvent args)
|
||||
{
|
||||
// Try to pick the entity up instead first
|
||||
if (args.User != args.Item && _carrying.TryCarry(args.User, uid))
|
||||
{
|
||||
args.Cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
// If could not pick up, just take it out onto the ground as per default
|
||||
base.OnGettingPickedUpAttempt(uid, component, args);
|
||||
}
|
||||
|
||||
// Show a popup when a pseudo-item falls asleep inside a bag.
|
||||
private void OnTrySleeping(EntityUid uid, PseudoItemComponent component, TryingToSleepEvent args)
|
||||
{
|
||||
var parent = Transform(uid).ParentUid;
|
||||
if (!HasComp<SleepingComponent>(uid) && parent is { Valid: true } && HasComp<AllowsSleepInsideComponent>(parent))
|
||||
_popup.PopupEntity(Loc.GetString("popup-sleep-in-bag", ("entity", uid)), uid);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ using JetBrains.Annotations;
|
|||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Content.Server.Pinpointer;
|
||||
|
|
@ -28,14 +27,27 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
|
|||
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly ITileDefinitionManager _tileDefManager = default!;
|
||||
|
||||
public const float CloseDistance = 15f;
|
||||
public const float FarDistance = 30f;
|
||||
|
||||
private EntityQuery<AirtightComponent> _airtightQuery;
|
||||
private EntityQuery<MapGridComponent> _gridQuery;
|
||||
private EntityQuery<NavMapComponent> _navQuery;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
var categories = Enum.GetNames(typeof(NavMapChunkType)).Length - 1; // -1 due to "Invalid" entry.
|
||||
if (Categories != categories)
|
||||
throw new Exception($"{nameof(Categories)} must be equal to the number of chunk types");
|
||||
|
||||
_airtightQuery = GetEntityQuery<AirtightComponent>();
|
||||
_gridQuery = GetEntityQuery<MapGridComponent>();
|
||||
_navQuery = GetEntityQuery<NavMapComponent>();
|
||||
|
||||
// Initialization events
|
||||
SubscribeLocalEvent<StationGridAddedEvent>(OnStationInit);
|
||||
|
||||
|
|
@ -43,8 +55,7 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
|
|||
SubscribeLocalEvent<GridSplitEvent>(OnNavMapSplit);
|
||||
SubscribeLocalEvent<TileChangedEvent>(OnTileChanged);
|
||||
|
||||
// Airtight structure change event
|
||||
SubscribeLocalEvent<AirtightChanged>(OnAirtightChanged);
|
||||
SubscribeLocalEvent<AirtightChanged>(OnAirtightChange);
|
||||
|
||||
// Beacon events
|
||||
SubscribeLocalEvent<NavMapBeaconComponent, MapInitEvent>(OnNavMapBeaconMapInit);
|
||||
|
|
@ -54,82 +65,96 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
|
|||
SubscribeLocalEvent<ConfigurableNavMapBeaconComponent, ExaminedEvent>(OnConfigurableExamined);
|
||||
}
|
||||
|
||||
#region: Initialization event handling
|
||||
private void OnStationInit(StationGridAddedEvent ev)
|
||||
{
|
||||
var comp = EnsureComp<NavMapComponent>(ev.GridId);
|
||||
RefreshGrid(ev.GridId, comp, Comp<MapGridComponent>(ev.GridId));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region: Grid change event handling
|
||||
|
||||
private void OnNavMapSplit(ref GridSplitEvent args)
|
||||
{
|
||||
if (!TryComp(args.Grid, out NavMapComponent? comp))
|
||||
if (!_navQuery.TryComp(args.Grid, out var comp))
|
||||
return;
|
||||
|
||||
var gridQuery = GetEntityQuery<MapGridComponent>();
|
||||
|
||||
foreach (var grid in args.NewGrids)
|
||||
{
|
||||
var newComp = EnsureComp<NavMapComponent>(grid);
|
||||
RefreshGrid(grid, newComp, gridQuery.GetComponent(grid));
|
||||
RefreshGrid(grid, newComp, _gridQuery.GetComponent(grid));
|
||||
}
|
||||
|
||||
RefreshGrid(args.Grid, comp, gridQuery.GetComponent(args.Grid));
|
||||
RefreshGrid(args.Grid, comp, _gridQuery.GetComponent(args.Grid));
|
||||
}
|
||||
|
||||
private NavMapChunk EnsureChunk(NavMapComponent component, Vector2i origin)
|
||||
{
|
||||
if (!component.Chunks.TryGetValue(origin, out var chunk))
|
||||
{
|
||||
chunk = new(origin);
|
||||
component.Chunks[origin] = chunk;
|
||||
}
|
||||
|
||||
return chunk;
|
||||
}
|
||||
|
||||
private void OnTileChanged(ref TileChangedEvent ev)
|
||||
{
|
||||
if (!TryComp<NavMapComponent>(ev.NewTile.GridUid, out var navMap))
|
||||
return;
|
||||
if (/*!ev.EmptyChanged || */!_navQuery.TryComp(ev.NewTile.GridUid, out var navMap))
|
||||
return; // Depends on Robust Toolbox V221.1.0+
|
||||
|
||||
var tile = ev.NewTile.GridIndices;
|
||||
var chunkOrigin = SharedMapSystem.GetChunkIndices(tile, ChunkSize);
|
||||
|
||||
if (!navMap.Chunks.TryGetValue((NavMapChunkType.Floor, chunkOrigin), out var chunk))
|
||||
chunk = new(chunkOrigin);
|
||||
var chunk = EnsureChunk(navMap, chunkOrigin);
|
||||
|
||||
// This could be easily replaced in the future to accommodate diagonal tiles
|
||||
if (ev.NewTile.IsSpace())
|
||||
chunk = UnsetAllEdgesForChunkTile(chunk, tile);
|
||||
var relative = SharedMapSystem.GetChunkRelative(tile, ChunkSize);
|
||||
ref var tileData = ref chunk.TileData[GetTileIndex(relative)];
|
||||
|
||||
else
|
||||
chunk = SetAllEdgesForChunkTile(chunk, tile);
|
||||
|
||||
chunk.LastUpdate = _gameTiming.CurTick;
|
||||
navMap.Chunks[(NavMapChunkType.Floor, chunkOrigin)] = chunk;
|
||||
|
||||
Dirty(ev.NewTile.GridUid, navMap);
|
||||
}
|
||||
|
||||
private void OnAirtightChanged(ref AirtightChanged ev)
|
||||
{
|
||||
var gridUid = ev.Position.Grid;
|
||||
|
||||
if (!TryComp<NavMapComponent>(gridUid, out var navMap) ||
|
||||
!TryComp<MapGridComponent>(gridUid, out var mapGrid))
|
||||
return;
|
||||
|
||||
// Refresh the affected tile
|
||||
var tile = ev.Position.Tile;
|
||||
var chunkOrigin = SharedMapSystem.GetChunkIndices(tile, ChunkSize);
|
||||
|
||||
RefreshTileEntityContents(gridUid, navMap, mapGrid, chunkOrigin, tile);
|
||||
|
||||
// Update potentially affected chunks
|
||||
foreach (var category in EntityChunkTypes)
|
||||
if (ev.NewTile.IsSpace(_tileDefManager))
|
||||
{
|
||||
if (!navMap.Chunks.TryGetValue((category, chunkOrigin), out var chunk))
|
||||
continue;
|
||||
|
||||
chunk.LastUpdate = _gameTiming.CurTick;
|
||||
navMap.Chunks[(category, chunkOrigin)] = chunk;
|
||||
tileData = 0;
|
||||
if (PruneEmpty((ev.NewTile.GridUid, navMap), chunk))
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
tileData = FloorMask;
|
||||
}
|
||||
|
||||
Dirty(gridUid, navMap);
|
||||
DirtyChunk((ev.NewTile.GridUid, navMap), chunk);
|
||||
}
|
||||
|
||||
private void DirtyChunk(Entity<NavMapComponent> entity, NavMapChunk chunk)
|
||||
{
|
||||
if (chunk.LastUpdate == _gameTiming.CurTick)
|
||||
return;
|
||||
|
||||
chunk.LastUpdate = _gameTiming.CurTick;
|
||||
Dirty(entity);
|
||||
}
|
||||
|
||||
private void OnAirtightChange(ref AirtightChanged args)
|
||||
{
|
||||
if (args.AirBlockedChanged)
|
||||
return;
|
||||
|
||||
var gridUid = args.Position.Grid;
|
||||
|
||||
if (!_navQuery.TryComp(gridUid, out var navMap) ||
|
||||
!_gridQuery.TryComp(gridUid, out var mapGrid))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var chunkOrigin = SharedMapSystem.GetChunkIndices(args.Position.Tile, ChunkSize);
|
||||
var (newValue, chunk) = RefreshTileEntityContents(gridUid, navMap, mapGrid, chunkOrigin, args.Position.Tile, setFloor: false);
|
||||
|
||||
if (newValue == 0 && PruneEmpty((gridUid, navMap), chunk))
|
||||
return;
|
||||
|
||||
DirtyChunk((gridUid, navMap), chunk);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
|
@ -223,76 +248,70 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
|
|||
var tile = tileRef.GridIndices;
|
||||
var chunkOrigin = SharedMapSystem.GetChunkIndices(tile, ChunkSize);
|
||||
|
||||
if (!component.Chunks.TryGetValue((NavMapChunkType.Floor, chunkOrigin), out var chunk))
|
||||
chunk = new(chunkOrigin);
|
||||
|
||||
var chunk = EnsureChunk(component, chunkOrigin);
|
||||
chunk.LastUpdate = _gameTiming.CurTick;
|
||||
|
||||
// Refresh the floor tile
|
||||
component.Chunks[(NavMapChunkType.Floor, chunkOrigin)] = SetAllEdgesForChunkTile(chunk, tile);
|
||||
|
||||
// Refresh the contents of the tile
|
||||
RefreshTileEntityContents(uid, component, mapGrid, chunkOrigin, tile);
|
||||
RefreshTileEntityContents(uid, component, mapGrid, chunkOrigin, tile, setFloor: true);
|
||||
}
|
||||
|
||||
Dirty(uid, component);
|
||||
}
|
||||
|
||||
private void RefreshTileEntityContents(EntityUid uid, NavMapComponent component, MapGridComponent mapGrid, Vector2i chunkOrigin, Vector2i tile)
|
||||
private (int NewVal, NavMapChunk Chunk) RefreshTileEntityContents(EntityUid uid,
|
||||
NavMapComponent component,
|
||||
MapGridComponent mapGrid,
|
||||
Vector2i chunkOrigin,
|
||||
Vector2i tile,
|
||||
bool setFloor)
|
||||
{
|
||||
var relative = SharedMapSystem.GetChunkRelative(tile, ChunkSize);
|
||||
var flag = (ushort) GetFlag(relative);
|
||||
var invFlag = (ushort) ~flag;
|
||||
var chunk = EnsureChunk(component, chunkOrigin);
|
||||
ref var tileData = ref chunk.TileData[GetTileIndex(relative)];
|
||||
|
||||
// Clear stale data from the tile across all entity associated chunks
|
||||
foreach (var category in EntityChunkTypes)
|
||||
{
|
||||
if (!component.Chunks.TryGetValue((category, chunkOrigin), out var chunk))
|
||||
chunk = new(chunkOrigin);
|
||||
// Clear all data except for floor bits
|
||||
if (setFloor)
|
||||
tileData = FloorMask;
|
||||
else
|
||||
tileData &= FloorMask;
|
||||
|
||||
foreach (var (direction, _) in chunk.TileData)
|
||||
chunk.TileData[direction] &= invFlag;
|
||||
|
||||
chunk.LastUpdate = _gameTiming.CurTick;
|
||||
component.Chunks[(category, chunkOrigin)] = chunk;
|
||||
}
|
||||
|
||||
// Update the tile data based on what entities are still anchored to the tile
|
||||
var enumerator = _mapSystem.GetAnchoredEntitiesEnumerator(uid, mapGrid, tile);
|
||||
|
||||
while (enumerator.MoveNext(out var ent))
|
||||
{
|
||||
if (!TryComp<AirtightComponent>(ent, out var entAirtight))
|
||||
if (!_airtightQuery.TryComp(ent, out var airtight))
|
||||
continue;
|
||||
|
||||
var category = GetAssociatedEntityChunkType(ent.Value);
|
||||
|
||||
if (!component.Chunks.TryGetValue((category, chunkOrigin), out var chunk))
|
||||
var category = GetEntityType(ent.Value);
|
||||
if (category == NavMapChunkType.Invalid)
|
||||
continue;
|
||||
|
||||
foreach (var (direction, _) in chunk.TileData)
|
||||
{
|
||||
if ((direction & entAirtight.AirBlockedDirection) > 0)
|
||||
chunk.TileData[direction] |= flag;
|
||||
}
|
||||
|
||||
chunk.LastUpdate = _gameTiming.CurTick;
|
||||
component.Chunks[(category, chunkOrigin)] = chunk;
|
||||
var directions = (int)airtight.AirBlockedDirection;
|
||||
tileData |= directions << (int) category;
|
||||
}
|
||||
|
||||
// Remove walls that intersect with doors (unless they can both physically fit on the same tile)
|
||||
if (component.Chunks.TryGetValue((NavMapChunkType.Wall, chunkOrigin), out var wallChunk) &&
|
||||
component.Chunks.TryGetValue((NavMapChunkType.Airlock, chunkOrigin), out var airlockChunk))
|
||||
{
|
||||
foreach (var (direction, _) in wallChunk.TileData)
|
||||
{
|
||||
var airlockInvFlag = (ushort) ~airlockChunk.TileData[direction];
|
||||
wallChunk.TileData[direction] &= airlockInvFlag;
|
||||
}
|
||||
// TODO NAVMAP why can this even happen?
|
||||
// Is this for blast-doors or something?
|
||||
|
||||
wallChunk.LastUpdate = _gameTiming.CurTick;
|
||||
component.Chunks[(NavMapChunkType.Wall, chunkOrigin)] = wallChunk;
|
||||
// Shift airlock bits over to the wall bits
|
||||
var shiftedAirlockBits = (tileData & AirlockMask) >> ((int) NavMapChunkType.Airlock - (int) NavMapChunkType.Wall);
|
||||
|
||||
// And then mask door bits
|
||||
tileData &= ~shiftedAirlockBits;
|
||||
|
||||
return (tileData, chunk);
|
||||
}
|
||||
|
||||
private bool PruneEmpty(Entity<NavMapComponent> entity, NavMapChunk chunk)
|
||||
{
|
||||
foreach (var val in chunk.TileData)
|
||||
{
|
||||
// TODO NAVMAP SIMD
|
||||
if (val != 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
entity.Comp.Chunks.Remove(chunk.Origin);
|
||||
Dirty(entity);
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
|
@ -307,22 +326,15 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
|
|||
if (xform.GridUid == null)
|
||||
return;
|
||||
|
||||
if (!TryComp<NavMapComponent>(xform.GridUid, out var navMap))
|
||||
if (!_navQuery.TryComp(xform.GridUid, out var navMap))
|
||||
return;
|
||||
|
||||
var netEnt = GetNetEntity(uid);
|
||||
var oldBeacon = navMap.Beacons.FirstOrNull(x => x.NetEnt == netEnt);
|
||||
var changed = false;
|
||||
var meta = MetaData(uid);
|
||||
var changed = navMap.Beacons.Remove(meta.NetEntity);
|
||||
|
||||
if (oldBeacon != null)
|
||||
if (TryCreateNavMapBeaconData(uid, component, xform, meta, out var beaconData))
|
||||
{
|
||||
navMap.Beacons.Remove(oldBeacon.Value);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (TryCreateNavMapBeaconData(uid, component, xform, out var beaconData))
|
||||
{
|
||||
navMap.Beacons.Add(beaconData.Value);
|
||||
navMap.Beacons.Add(meta.NetEntity, beaconData.Value);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,99 +1,5 @@
|
|||
using Content.Server.Polymorph.Components;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Construction.Components;
|
||||
using Content.Shared.Hands;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Polymorph;
|
||||
using Content.Shared.Polymorph.Components;
|
||||
using Content.Shared.Polymorph.Systems;
|
||||
using Content.Shared.StatusIcon.Components;
|
||||
using Robust.Shared.Physics.Components;
|
||||
|
||||
namespace Content.Server.Polymorph.Systems;
|
||||
|
||||
public sealed class ChameleonProjectorSystem : SharedChameleonProjectorSystem
|
||||
{
|
||||
[Dependency] private readonly MetaDataSystem _meta = default!;
|
||||
[Dependency] private readonly MobThresholdSystem _mobThreshold = default!;
|
||||
[Dependency] private readonly PolymorphSystem _polymorph = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actions = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _xform = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ChameleonDisguiseComponent, GotEquippedHandEvent>(OnEquippedHand);
|
||||
SubscribeLocalEvent<ChameleonDisguiseComponent, DisguiseToggleNoRotEvent>(OnToggleNoRot);
|
||||
SubscribeLocalEvent<ChameleonDisguiseComponent, DisguiseToggleAnchoredEvent>(OnToggleAnchored);
|
||||
}
|
||||
|
||||
private void OnEquippedHand(Entity<ChameleonDisguiseComponent> ent, ref GotEquippedHandEvent args)
|
||||
{
|
||||
if (!TryComp<PolymorphedEntityComponent>(ent, out var poly))
|
||||
return;
|
||||
|
||||
_polymorph.Revert((ent, poly));
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
public override void Disguise(ChameleonProjectorComponent proj, EntityUid user, EntityUid entity)
|
||||
{
|
||||
if (_polymorph.PolymorphEntity(user, proj.Polymorph) is not {} disguise)
|
||||
return;
|
||||
|
||||
// make disguise look real (for simple things at least)
|
||||
var meta = MetaData(entity);
|
||||
_meta.SetEntityName(disguise, meta.EntityName);
|
||||
_meta.SetEntityDescription(disguise, meta.EntityDescription);
|
||||
|
||||
var comp = EnsureComp<ChameleonDisguiseComponent>(disguise);
|
||||
comp.SourceEntity = entity;
|
||||
comp.SourceProto = Prototype(entity)?.ID;
|
||||
Dirty(disguise, comp);
|
||||
|
||||
// no sechud trolling
|
||||
RemComp<StatusIconComponent>(disguise);
|
||||
|
||||
_appearance.CopyData(entity, disguise);
|
||||
|
||||
var mass = CompOrNull<PhysicsComponent>(entity)?.Mass ?? 0f;
|
||||
|
||||
// let the disguise die when its taken enough damage, which then transfers to the player
|
||||
// health is proportional to mass, and capped to not be insane
|
||||
if (TryComp<MobThresholdsComponent>(disguise, out var thresholds))
|
||||
{
|
||||
// if the player is of flesh and blood, cap max health to theirs
|
||||
// so that when reverting damage scales 1:1 and not round removing
|
||||
var playerMax = _mobThreshold.GetThresholdForState(user, MobState.Dead).Float();
|
||||
var max = playerMax == 0f ? proj.MaxHealth : Math.Max(proj.MaxHealth, playerMax);
|
||||
|
||||
var health = Math.Clamp(mass, proj.MinHealth, proj.MaxHealth);
|
||||
_mobThreshold.SetMobStateThreshold(disguise, health, MobState.Critical, thresholds);
|
||||
_mobThreshold.SetMobStateThreshold(disguise, max, MobState.Dead, thresholds);
|
||||
}
|
||||
|
||||
// add actions for controlling transform aspects
|
||||
_actions.AddAction(disguise, proj.NoRotAction);
|
||||
_actions.AddAction(disguise, proj.AnchorAction);
|
||||
}
|
||||
|
||||
private void OnToggleNoRot(Entity<ChameleonDisguiseComponent> ent, ref DisguiseToggleNoRotEvent args)
|
||||
{
|
||||
var xform = Transform(ent);
|
||||
xform.NoLocalRotation = !xform.NoLocalRotation;
|
||||
}
|
||||
|
||||
private void OnToggleAnchored(Entity<ChameleonDisguiseComponent> ent, ref DisguiseToggleAnchoredEvent args)
|
||||
{
|
||||
var uid = ent.Owner;
|
||||
var xform = Transform(uid);
|
||||
if (xform.Anchored)
|
||||
_xform.Unanchor(uid, xform);
|
||||
else
|
||||
_xform.AnchorEntity((uid, xform));
|
||||
}
|
||||
}
|
||||
public sealed class ChameleonProjectorSystem : SharedChameleonProjectorSystem;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.NodeContainer;
|
||||
using Content.Server.NodeContainer.EntitySystems;
|
||||
using Content.Server.NodeContainer.NodeGroups;
|
||||
using Content.Server.NodeContainer.Nodes;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Power.Nodes;
|
||||
|
|
@ -13,10 +11,11 @@ using Content.Shared.Power;
|
|||
using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Linq;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Server.Power.EntitySystems;
|
||||
|
||||
|
|
@ -162,7 +161,7 @@ internal sealed partial class PowerMonitoringConsoleSystem : SharedPowerMonitori
|
|||
allChunks = new();
|
||||
|
||||
var tile = _sharedMapSystem.LocalToTile(xform.GridUid.Value, grid, xform.Coordinates);
|
||||
var chunkOrigin = SharedMapSystem.GetChunkIndices(tile, SharedNavMapSystem.ChunkSize);
|
||||
var chunkOrigin = SharedMapSystem.GetChunkIndices(tile, ChunkSize);
|
||||
|
||||
if (!allChunks.TryGetValue(chunkOrigin, out var chunk))
|
||||
{
|
||||
|
|
@ -170,8 +169,8 @@ internal sealed partial class PowerMonitoringConsoleSystem : SharedPowerMonitori
|
|||
allChunks[chunkOrigin] = chunk;
|
||||
}
|
||||
|
||||
var relative = SharedMapSystem.GetChunkRelative(tile, SharedNavMapSystem.ChunkSize);
|
||||
var flag = SharedNavMapSystem.GetFlag(relative);
|
||||
var relative = SharedMapSystem.GetChunkRelative(tile, ChunkSize);
|
||||
var flag = GetFlag(relative);
|
||||
|
||||
if (args.Anchored)
|
||||
chunk.PowerCableData[(int) component.CableType] |= flag;
|
||||
|
|
@ -723,8 +722,8 @@ internal sealed partial class PowerMonitoringConsoleSystem : SharedPowerMonitori
|
|||
}
|
||||
}
|
||||
|
||||
// Designates a supplied entity as a 'collection master'. Other entities which share this
|
||||
// entities collection name and are attached on the same load network are assigned this entity
|
||||
// Designates a supplied entity as a 'collection master'. Other entities which share this
|
||||
// entities collection name and are attached on the same load network are assigned this entity
|
||||
// as the master that represents them on the console UI. This way you can have one device
|
||||
// represent multiple connected devices
|
||||
private void AssignEntityAsCollectionMaster
|
||||
|
|
@ -886,7 +885,7 @@ internal sealed partial class PowerMonitoringConsoleSystem : SharedPowerMonitori
|
|||
continue;
|
||||
|
||||
var tile = _sharedMapSystem.GetTileRef(gridUid, grid, entXform.Coordinates);
|
||||
var chunkOrigin = SharedMapSystem.GetChunkIndices(tile.GridIndices, SharedNavMapSystem.ChunkSize);
|
||||
var chunkOrigin = SharedMapSystem.GetChunkIndices(tile.GridIndices, ChunkSize);
|
||||
|
||||
if (!allChunks.TryGetValue(chunkOrigin, out var chunk))
|
||||
{
|
||||
|
|
@ -894,8 +893,8 @@ internal sealed partial class PowerMonitoringConsoleSystem : SharedPowerMonitori
|
|||
allChunks[chunkOrigin] = chunk;
|
||||
}
|
||||
|
||||
var relative = SharedMapSystem.GetChunkRelative(tile.GridIndices, SharedNavMapSystem.ChunkSize);
|
||||
var flag = SharedNavMapSystem.GetFlag(relative);
|
||||
var relative = SharedMapSystem.GetChunkRelative(tile.GridIndices, ChunkSize);
|
||||
var flag = GetFlag(relative);
|
||||
|
||||
chunk.PowerCableData[(int) cable.CableType] |= flag;
|
||||
}
|
||||
|
|
@ -912,7 +911,7 @@ internal sealed partial class PowerMonitoringConsoleSystem : SharedPowerMonitori
|
|||
var xform = Transform(ent);
|
||||
var tile = _sharedMapSystem.GetTileRef(gridUid, grid, xform.Coordinates);
|
||||
var gridIndices = tile.GridIndices;
|
||||
var chunkOrigin = SharedMapSystem.GetChunkIndices(gridIndices, SharedNavMapSystem.ChunkSize);
|
||||
var chunkOrigin = SharedMapSystem.GetChunkIndices(gridIndices, ChunkSize);
|
||||
|
||||
if (!component.FocusChunks.TryGetValue(chunkOrigin, out var chunk))
|
||||
{
|
||||
|
|
@ -920,8 +919,8 @@ internal sealed partial class PowerMonitoringConsoleSystem : SharedPowerMonitori
|
|||
component.FocusChunks[chunkOrigin] = chunk;
|
||||
}
|
||||
|
||||
var relative = SharedMapSystem.GetChunkRelative(gridIndices, SharedNavMapSystem.ChunkSize);
|
||||
var flag = SharedNavMapSystem.GetFlag(relative);
|
||||
var relative = SharedMapSystem.GetChunkRelative(gridIndices, ChunkSize);
|
||||
var flag = GetFlag(relative);
|
||||
|
||||
if (TryComp<CableComponent>(ent, out var cable))
|
||||
chunk.PowerCableData[(int) cable.CableType] |= flag;
|
||||
|
|
|
|||
|
|
@ -15,4 +15,10 @@ public sealed partial class CanEscapeInventoryComponent : Component
|
|||
|
||||
[DataField("doAfter")]
|
||||
public DoAfterId? DoAfter;
|
||||
|
||||
/// <summary>
|
||||
/// DeltaV - action to cancel inventory escape. Added dynamically.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntityUid? EscapeCancelAction;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using Content.Shared.Inventory;
|
|||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Storage.Components;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Interaction.Events;
|
||||
|
|
@ -13,6 +14,7 @@ using Content.Shared.Movement.Events;
|
|||
using Content.Shared.Resist;
|
||||
using Content.Shared.Storage;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Resist;
|
||||
|
||||
|
|
@ -24,11 +26,17 @@ public sealed class EscapeInventorySystem : EntitySystem
|
|||
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
|
||||
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
|
||||
[Dependency] private readonly CarryingSystem _carryingSystem = default!; // Carrying system from Nyanotrasen.
|
||||
[Dependency] private readonly SharedActionsSystem _actions = default!; // DeltaV
|
||||
|
||||
/// <summary>
|
||||
/// You can't escape the hands of an entity this many times more massive than you.
|
||||
/// </summary>
|
||||
public const float MaximumMassDisadvantage = 6f;
|
||||
/// <summary>
|
||||
/// DeltaV - action to cancel inventory escape
|
||||
/// </summary>
|
||||
[ValidatePrototypeId<EntityPrototype>]
|
||||
private readonly string _escapeCancelAction = "ActionCancelEscape";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
|
|
@ -37,6 +45,7 @@ public sealed class EscapeInventorySystem : EntitySystem
|
|||
SubscribeLocalEvent<CanEscapeInventoryComponent, MoveInputEvent>(OnRelayMovement);
|
||||
SubscribeLocalEvent<CanEscapeInventoryComponent, EscapeInventoryEvent>(OnEscape);
|
||||
SubscribeLocalEvent<CanEscapeInventoryComponent, DroppedEvent>(OnDropped);
|
||||
SubscribeLocalEvent<CanEscapeInventoryComponent, EscapeInventoryCancelActionEvent>(OnCancelEscape); // DeltaV
|
||||
}
|
||||
|
||||
private void OnRelayMovement(EntityUid uid, CanEscapeInventoryComponent component, ref MoveInputEvent args)
|
||||
|
|
@ -83,12 +92,20 @@ public sealed class EscapeInventorySystem : EntitySystem
|
|||
|
||||
_popupSystem.PopupEntity(Loc.GetString("escape-inventory-component-start-resisting"), user, user);
|
||||
_popupSystem.PopupEntity(Loc.GetString("escape-inventory-component-start-resisting-target"), container, container);
|
||||
|
||||
// DeltaV - escape cancel action
|
||||
if (component.EscapeCancelAction is not { Valid: true })
|
||||
_actions.AddAction(user, ref component.EscapeCancelAction, _escapeCancelAction);
|
||||
}
|
||||
|
||||
private void OnEscape(EntityUid uid, CanEscapeInventoryComponent component, EscapeInventoryEvent args)
|
||||
{
|
||||
component.DoAfter = null;
|
||||
|
||||
// DeltaV - remove cancel action regardless of do-after result
|
||||
_actions.RemoveAction(uid, component.EscapeCancelAction);
|
||||
component.EscapeCancelAction = null;
|
||||
|
||||
if (args.Handled || args.Cancelled)
|
||||
return;
|
||||
|
||||
|
|
@ -108,4 +125,14 @@ public sealed class EscapeInventorySystem : EntitySystem
|
|||
if (component.DoAfter != null)
|
||||
_doAfterSystem.Cancel(component.DoAfter);
|
||||
}
|
||||
|
||||
// DeltaV
|
||||
private void OnCancelEscape(EntityUid uid, CanEscapeInventoryComponent component, EscapeInventoryCancelActionEvent args)
|
||||
{
|
||||
if (component.DoAfter != null)
|
||||
_doAfterSystem.Cancel(component.DoAfter);
|
||||
|
||||
_actions.RemoveAction(uid, component.EscapeCancelAction);
|
||||
component.EscapeCancelAction = null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -104,15 +104,14 @@ namespace Content.Shared.Atmos
|
|||
{
|
||||
return direction switch
|
||||
{
|
||||
AtmosDirection.East => Angle.FromDegrees(90),
|
||||
AtmosDirection.North => Angle.FromDegrees(180),
|
||||
AtmosDirection.West => Angle.FromDegrees(270),
|
||||
AtmosDirection.South => Angle.FromDegrees(0),
|
||||
|
||||
AtmosDirection.NorthEast => Angle.FromDegrees(135),
|
||||
AtmosDirection.NorthWest => Angle.FromDegrees(205),
|
||||
AtmosDirection.SouthWest => Angle.FromDegrees(315),
|
||||
AtmosDirection.SouthEast => Angle.FromDegrees(45),
|
||||
AtmosDirection.South => Angle.Zero,
|
||||
AtmosDirection.East => new Angle(MathHelper.PiOver2),
|
||||
AtmosDirection.North => new Angle(Math.PI),
|
||||
AtmosDirection.West => new Angle(-MathHelper.PiOver2),
|
||||
AtmosDirection.NorthEast => new Angle(Math.PI*3/4),
|
||||
AtmosDirection.NorthWest => new Angle(-Math.PI*3/4),
|
||||
AtmosDirection.SouthWest => new Angle(-MathHelper.PiOver4),
|
||||
AtmosDirection.SouthEast => new Angle(MathHelper.PiOver4),
|
||||
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(direction), $"It was {direction}."),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -45,10 +45,14 @@ public sealed class StealthClothingSystem : EntitySystem
|
|||
if (MetaData(user).EntityLifeStage >= EntityLifeStage.Terminating)
|
||||
return false;
|
||||
|
||||
// can't enable stealth if something else already enabled it, and vice versa
|
||||
var stealth = EnsureComp<StealthComponent>(user);
|
||||
if (stealth.Enabled == enabled)
|
||||
return false;
|
||||
|
||||
comp.Enabled = enabled;
|
||||
Dirty(uid, comp);
|
||||
|
||||
var stealth = EnsureComp<StealthComponent>(user);
|
||||
// slightly visible, but doesn't change when moving so it's ok
|
||||
var visibility = enabled ? stealth.MinVisibility + comp.Visibility : stealth.MaxVisibility;
|
||||
_stealth.SetVisibility(user, visibility, stealth);
|
||||
|
|
|
|||
|
|
@ -136,16 +136,11 @@ public static class SkinColor
|
|||
/// <param name="skinColor">The skin color to blend with</param>
|
||||
/// <param name="blendFactor">Blending factor (0.0 to 1.0)</param>
|
||||
/// <returns>Tinted hue color</returns>
|
||||
public static Color TintedHuesSkin(Color color, Color skinColor, float blendFactor = 0.5f)
|
||||
public static Color TintedHuesSkin(Color color, Color skinColor, float blendFactor = 0.75f)
|
||||
{
|
||||
blendFactor = MathHelper.Clamp(blendFactor, 0.0f, 1.0f);
|
||||
|
||||
var r = MathHelper.Lerp(skinColor.R, color.R, blendFactor);
|
||||
var g = MathHelper.Lerp(skinColor.G, color.G, blendFactor);
|
||||
var b = MathHelper.Lerp(skinColor.B, color.B, blendFactor);
|
||||
var a = color.A;
|
||||
|
||||
return new Color(r, g, b, a);
|
||||
return Color.InterpolateBetween(skinColor, color, blendFactor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
namespace Content.Shared.Nyanotrasen.Item.PseudoItem;
|
||||
|
||||
/// <summary>
|
||||
/// Signifies that pseudo-item creatures can sleep inside the container to which this component is applied.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class AllowsSleepInsideComponent : Component
|
||||
{
|
||||
}
|
||||
|
|
@ -3,10 +3,10 @@ using Robust.Shared.Prototypes;
|
|||
|
||||
namespace Content.Shared.Nyanotrasen.Item.PseudoItem;
|
||||
|
||||
/// <summary>
|
||||
/// For entities that behave like an item under certain conditions,
|
||||
/// but not under most conditions.
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// For entities that behave like an item under certain conditions,
|
||||
/// but not under most conditions.
|
||||
/// </summary>
|
||||
[RegisterComponent, AutoGenerateComponentState]
|
||||
public sealed partial class PseudoItemComponent : Component
|
||||
{
|
||||
|
|
@ -24,4 +24,10 @@ public sealed partial class PseudoItemComponent : Component
|
|||
public Vector2i StoredOffset;
|
||||
|
||||
public bool Active = false;
|
||||
|
||||
/// <summary>
|
||||
/// Action for sleeping while inside a container with <see cref="AllowsSleepInsideComponent"/>.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntityUid? SleepAction;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ namespace Content.Shared.Nyanotrasen.Item.PseudoItem;
|
|||
/// </summary>
|
||||
public partial class SharedPseudoItemSystem
|
||||
{
|
||||
protected bool CheckItemFits(Entity<PseudoItemComponent?> itemEnt, Entity<StorageComponent?> storageEnt)
|
||||
public bool CheckItemFits(Entity<PseudoItemComponent?> itemEnt, Entity<StorageComponent?> storageEnt)
|
||||
{
|
||||
if (!Resolve(itemEnt, ref itemEnt.Comp) || !Resolve(storageEnt, ref storageEnt.Comp))
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -1,14 +1,18 @@
|
|||
using Content.Shared.Actions;
|
||||
using Content.Shared.Bed.Sleep;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Hands;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.Item;
|
||||
using Content.Shared.Item.PseudoItem;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Storage;
|
||||
using Content.Shared.Storage.EntitySystems;
|
||||
using Content.Shared.Tag;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Nyanotrasen.Item.PseudoItem;
|
||||
|
||||
|
|
@ -18,9 +22,13 @@ public abstract partial class SharedPseudoItemSystem : EntitySystem
|
|||
[Dependency] private readonly SharedItemSystem _item = default!;
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
|
||||
[Dependency] private readonly TagSystem _tag = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actions = default!;
|
||||
|
||||
[ValidatePrototypeId<TagPrototype>]
|
||||
private const string PreventTag = "PreventLabel";
|
||||
[ValidatePrototypeId<EntityPrototype>]
|
||||
private const string SleepActionId = "ActionSleep"; // The action used for sleeping inside bags. Currently uses the default sleep action (same as beds)
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
|
|
@ -64,7 +72,7 @@ public abstract partial class SharedPseudoItemSystem : EntitySystem
|
|||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
private bool TryInsert(EntityUid storageUid, EntityUid toInsert, PseudoItemComponent component,
|
||||
public bool TryInsert(EntityUid storageUid, EntityUid toInsert, PseudoItemComponent component,
|
||||
StorageComponent? storage = null)
|
||||
{
|
||||
if (!Resolve(storageUid, ref storage))
|
||||
|
|
@ -87,6 +95,10 @@ public abstract partial class SharedPseudoItemSystem : EntitySystem
|
|||
return false;
|
||||
}
|
||||
|
||||
// If the storage allows sleeping inside, add the respective action
|
||||
if (HasComp<AllowsSleepInsideComponent>(storageUid))
|
||||
_actions.AddAction(toInsert, ref component.SleepAction, SleepActionId, toInsert);
|
||||
|
||||
component.Active = true;
|
||||
return true;
|
||||
}
|
||||
|
|
@ -98,9 +110,11 @@ public abstract partial class SharedPseudoItemSystem : EntitySystem
|
|||
|
||||
RemComp<ItemComponent>(uid);
|
||||
component.Active = false;
|
||||
|
||||
_actions.RemoveAction(uid, component.SleepAction); // Remove sleep action if it was added
|
||||
}
|
||||
|
||||
private void OnGettingPickedUpAttempt(EntityUid uid, PseudoItemComponent component,
|
||||
protected virtual void OnGettingPickedUpAttempt(EntityUid uid, PseudoItemComponent component,
|
||||
GettingPickedUpAttemptEvent args)
|
||||
{
|
||||
if (args.User == args.Item)
|
||||
|
|
@ -153,7 +167,11 @@ public abstract partial class SharedPseudoItemSystem : EntitySystem
|
|||
NeedHand = true
|
||||
};
|
||||
|
||||
_doAfter.TryStartDoAfter(args);
|
||||
if (_doAfter.TryStartDoAfter(args))
|
||||
{
|
||||
// Show a popup to the person getting picked up
|
||||
_popupSystem.PopupEntity(Loc.GetString("carry-started", ("carrier", inserter)), toInsert, toInsert);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAttackAttempt(EntityUid uid, PseudoItemComponent component, AttackAttemptEvent args)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using System.Linq;
|
||||
using Content.Shared.Atmos;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
|
|
@ -19,53 +20,43 @@ public sealed partial class NavMapComponent : Component
|
|||
/// Bitmasks that represent chunked tiles.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public Dictionary<(NavMapChunkType, Vector2i), NavMapChunk> Chunks = new();
|
||||
public Dictionary<Vector2i, NavMapChunk> Chunks = new();
|
||||
|
||||
/// <summary>
|
||||
/// List of station beacons.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public HashSet<SharedNavMapSystem.NavMapBeacon> Beacons = new();
|
||||
public Dictionary<NetEntity, SharedNavMapSystem.NavMapBeacon> Beacons = new();
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class NavMapChunk
|
||||
public sealed class NavMapChunk(Vector2i origin)
|
||||
{
|
||||
/// <summary>
|
||||
/// The chunk origin
|
||||
/// </summary>
|
||||
public readonly Vector2i Origin;
|
||||
[ViewVariables]
|
||||
public readonly Vector2i Origin = origin;
|
||||
|
||||
/// <summary>
|
||||
/// Bitmask for tiles, 1 for occupied and 0 for empty. There is a bitmask for each cardinal direction,
|
||||
/// representing each edge of the tile, in case the entities inside it do not entirely fill it
|
||||
/// Array containing the chunk's data. The
|
||||
/// </summary>
|
||||
public Dictionary<AtmosDirection, ushort> TileData;
|
||||
[ViewVariables]
|
||||
public int[] TileData = new int[SharedNavMapSystem.ArraySize];
|
||||
|
||||
/// <summary>
|
||||
/// The last game tick that the chunk was updated
|
||||
/// </summary>
|
||||
[NonSerialized]
|
||||
public GameTick LastUpdate;
|
||||
|
||||
public NavMapChunk(Vector2i origin)
|
||||
{
|
||||
Origin = origin;
|
||||
|
||||
TileData = new()
|
||||
{
|
||||
[AtmosDirection.North] = 0,
|
||||
[AtmosDirection.East] = 0,
|
||||
[AtmosDirection.South] = 0,
|
||||
[AtmosDirection.West] = 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public enum NavMapChunkType : byte
|
||||
{
|
||||
Invalid,
|
||||
Floor,
|
||||
Wall,
|
||||
Airlock,
|
||||
// Values represent bit shift offsets when retrieving data in the tile array.
|
||||
Invalid = byte.MaxValue,
|
||||
Floor = 0, // I believe floors have directional information for diagonal tiles?
|
||||
Wall = SharedNavMapSystem.Directions,
|
||||
Airlock = 2 * SharedNavMapSystem.Directions,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Numerics;
|
||||
using Content.Shared.Atmos;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Content.Shared.Tag;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
|
|
@ -11,19 +11,22 @@ namespace Content.Shared.Pinpointer;
|
|||
|
||||
public abstract class SharedNavMapSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly TagSystem _tagSystem = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
public const int Categories = 3;
|
||||
public const int Directions = 4; // Not directly tied to number of atmos directions
|
||||
|
||||
public const byte ChunkSize = 4;
|
||||
public const int ChunkSize = 8;
|
||||
public const int ArraySize = ChunkSize* ChunkSize;
|
||||
|
||||
public readonly NavMapChunkType[] EntityChunkTypes =
|
||||
{
|
||||
NavMapChunkType.Invalid,
|
||||
NavMapChunkType.Wall,
|
||||
NavMapChunkType.Airlock,
|
||||
};
|
||||
public const int AllDirMask = (1 << Directions) - 1;
|
||||
public const int AirlockMask = AllDirMask << (int) NavMapChunkType.Airlock;
|
||||
public const int WallMask = AllDirMask << (int) NavMapChunkType.Wall;
|
||||
public const int FloorMask = AllDirMask << (int) NavMapChunkType.Floor;
|
||||
|
||||
[Robust.Shared.IoC.Dependency] private readonly TagSystem _tagSystem = default!;
|
||||
[Robust.Shared.IoC.Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
|
||||
private readonly string[] _wallTags = ["Wall", "Window"];
|
||||
private EntityQuery<NavMapDoorComponent> _doorQuery;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
|
|
@ -31,107 +34,49 @@ public abstract class SharedNavMapSystem : EntitySystem
|
|||
|
||||
// Data handling events
|
||||
SubscribeLocalEvent<NavMapComponent, ComponentGetState>(OnGetState);
|
||||
_doorQuery = GetEntityQuery<NavMapDoorComponent>();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int GetTileIndex(Vector2i relativeTile)
|
||||
{
|
||||
return relativeTile.X * ChunkSize + relativeTile.Y;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the chunk's tile into a bitflag for the slot.
|
||||
/// Inverse of <see cref="GetTileIndex"/>
|
||||
/// </summary>
|
||||
public static int GetFlag(Vector2i relativeTile)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Vector2i GetTileFromIndex(int index)
|
||||
{
|
||||
return 1 << (relativeTile.X * ChunkSize + relativeTile.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the chunk's tile into a bitflag for the slot.
|
||||
/// </summary>
|
||||
public static Vector2i GetTile(int flag)
|
||||
{
|
||||
var value = Math.Log2(flag);
|
||||
var x = (int) value / ChunkSize;
|
||||
var y = (int) value % ChunkSize;
|
||||
var result = new Vector2i(x, y);
|
||||
|
||||
DebugTools.Assert(GetFlag(result) == flag);
|
||||
|
||||
var x = index / ChunkSize;
|
||||
var y = index % ChunkSize;
|
||||
return new Vector2i(x, y);
|
||||
}
|
||||
|
||||
public NavMapChunk SetAllEdgesForChunkTile(NavMapChunk chunk, Vector2i tile)
|
||||
public NavMapChunkType GetEntityType(EntityUid uid)
|
||||
{
|
||||
var relative = SharedMapSystem.GetChunkRelative(tile, ChunkSize);
|
||||
var flag = (ushort) GetFlag(relative);
|
||||
if (_doorQuery.HasComp(uid))
|
||||
return NavMapChunkType.Airlock;
|
||||
|
||||
foreach (var (direction, _) in chunk.TileData)
|
||||
chunk.TileData[direction] |= flag;
|
||||
if (_tagSystem.HasAnyTag(uid, _wallTags))
|
||||
return NavMapChunkType.Wall;
|
||||
|
||||
return chunk;
|
||||
return NavMapChunkType.Invalid;
|
||||
}
|
||||
|
||||
public NavMapChunk UnsetAllEdgesForChunkTile(NavMapChunk chunk, Vector2i tile)
|
||||
{
|
||||
var relative = SharedMapSystem.GetChunkRelative(tile, ChunkSize);
|
||||
var flag = (ushort) GetFlag(relative);
|
||||
var invFlag = (ushort) ~flag;
|
||||
|
||||
foreach (var (direction, _) in chunk.TileData)
|
||||
chunk.TileData[direction] &= invFlag;
|
||||
|
||||
return chunk;
|
||||
}
|
||||
|
||||
public ushort GetCombinedEdgesForChunk(Dictionary<AtmosDirection, ushort> tile)
|
||||
{
|
||||
ushort combined = 0;
|
||||
|
||||
foreach (var kvp in tile)
|
||||
combined |= kvp.Value;
|
||||
|
||||
return combined;
|
||||
}
|
||||
|
||||
public bool AllTileEdgesAreOccupied(Dictionary<AtmosDirection, ushort> tileData, Vector2i tile)
|
||||
{
|
||||
var flag = (ushort) GetFlag(tile);
|
||||
|
||||
foreach (var kvp in tileData)
|
||||
{
|
||||
if ((kvp.Value & flag) == 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public NavMapChunkType GetAssociatedEntityChunkType(EntityUid uid)
|
||||
{
|
||||
var category = NavMapChunkType.Invalid;
|
||||
|
||||
if (HasComp<NavMapDoorComponent>(uid))
|
||||
category = NavMapChunkType.Airlock;
|
||||
|
||||
else if (_tagSystem.HasAnyTag(uid, _wallTags))
|
||||
category = NavMapChunkType.Wall;
|
||||
|
||||
return category;
|
||||
}
|
||||
|
||||
protected bool TryCreateNavMapBeaconData(EntityUid uid, NavMapBeaconComponent component, TransformComponent xform, [NotNullWhen(true)] out NavMapBeacon? beaconData)
|
||||
protected bool TryCreateNavMapBeaconData(EntityUid uid, NavMapBeaconComponent component, TransformComponent xform, MetaDataComponent meta, [NotNullWhen(true)] out NavMapBeacon? beaconData)
|
||||
{
|
||||
beaconData = null;
|
||||
|
||||
if (!component.Enabled || xform.GridUid == null || !xform.Anchored)
|
||||
return false;
|
||||
|
||||
string? name = component.Text;
|
||||
var meta = MetaData(uid);
|
||||
|
||||
var name = component.Text;
|
||||
if (string.IsNullOrEmpty(name))
|
||||
name = meta.EntityName;
|
||||
|
||||
beaconData = new NavMapBeacon(meta.NetEntity, component.Color, name, xform.LocalPosition)
|
||||
{
|
||||
LastUpdate = _gameTiming.CurTick
|
||||
};
|
||||
beaconData = new NavMapBeacon(meta.NetEntity, component.Color, name, xform.LocalPosition);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -140,64 +85,36 @@ public abstract class SharedNavMapSystem : EntitySystem
|
|||
|
||||
private void OnGetState(EntityUid uid, NavMapComponent component, ref ComponentGetState args)
|
||||
{
|
||||
var chunks = new Dictionary<(NavMapChunkType, Vector2i), Dictionary<AtmosDirection, ushort>>();
|
||||
var beacons = new HashSet<NavMapBeacon>();
|
||||
Dictionary<Vector2i, int[]> chunks;
|
||||
|
||||
// Should this be a full component state or a delta-state?
|
||||
if (args.FromTick <= component.CreationTick)
|
||||
{
|
||||
foreach (var ((category, origin), chunk) in component.Chunks)
|
||||
// Full state
|
||||
chunks = new(component.Chunks.Count);
|
||||
foreach (var (origin, chunk) in component.Chunks)
|
||||
{
|
||||
var chunkDatum = new Dictionary<AtmosDirection, ushort>(chunk.TileData.Count);
|
||||
|
||||
foreach (var (direction, tileData) in chunk.TileData)
|
||||
chunkDatum[direction] = tileData;
|
||||
|
||||
chunks.Add((category, origin), chunkDatum);
|
||||
chunks.Add(origin, chunk.TileData);
|
||||
}
|
||||
|
||||
var beaconQuery = AllEntityQuery<NavMapBeaconComponent, TransformComponent>();
|
||||
|
||||
while (beaconQuery.MoveNext(out var beaconUid, out var beacon, out var xform))
|
||||
{
|
||||
if (xform.GridUid != uid)
|
||||
continue;
|
||||
|
||||
if (!TryCreateNavMapBeaconData(beaconUid, beacon, xform, out var beaconData))
|
||||
continue;
|
||||
|
||||
beacons.Add(beaconData.Value);
|
||||
}
|
||||
|
||||
args.State = new NavMapComponentState(chunks, beacons);
|
||||
args.State = new NavMapComponentState(chunks, component.Beacons);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var ((category, origin), chunk) in component.Chunks)
|
||||
chunks = new();
|
||||
foreach (var (origin, chunk) in component.Chunks)
|
||||
{
|
||||
if (chunk.LastUpdate < args.FromTick)
|
||||
continue;
|
||||
|
||||
var chunkDatum = new Dictionary<AtmosDirection, ushort>(chunk.TileData.Count);
|
||||
|
||||
foreach (var (direction, tileData) in chunk.TileData)
|
||||
chunkDatum[direction] = tileData;
|
||||
|
||||
chunks.Add((category, origin), chunkDatum);
|
||||
chunks.Add(origin, chunk.TileData);
|
||||
}
|
||||
|
||||
foreach (var beacon in component.Beacons)
|
||||
{
|
||||
if (beacon.LastUpdate < args.FromTick)
|
||||
continue;
|
||||
|
||||
beacons.Add(beacon);
|
||||
}
|
||||
|
||||
args.State = new NavMapComponentState(chunks, beacons)
|
||||
args.State = new NavMapComponentState(chunks, component.Beacons)
|
||||
{
|
||||
// TODO NAVMAP cache a single AllChunks hashset in the component.
|
||||
// Or maybe just only send them if a chunk gets removed.
|
||||
AllChunks = new(component.Chunks.Keys),
|
||||
AllBeacons = new(component.Beacons)
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -206,22 +123,18 @@ public abstract class SharedNavMapSystem : EntitySystem
|
|||
#region: System messages
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
protected sealed class NavMapComponentState : ComponentState, IComponentDeltaState
|
||||
protected sealed class NavMapComponentState(
|
||||
Dictionary<Vector2i, int[]> chunks,
|
||||
Dictionary<NetEntity, NavMapBeacon> beacons)
|
||||
: ComponentState, IComponentDeltaState
|
||||
{
|
||||
public Dictionary<(NavMapChunkType, Vector2i), Dictionary<AtmosDirection, ushort>> Chunks = new();
|
||||
public HashSet<NavMapBeacon> Beacons = new();
|
||||
public Dictionary<Vector2i, int[]> Chunks = chunks;
|
||||
public Dictionary<NetEntity, NavMapBeacon> Beacons = beacons;
|
||||
|
||||
// Required to infer deleted/missing chunks for delta states
|
||||
public HashSet<(NavMapChunkType, Vector2i)>? AllChunks;
|
||||
public HashSet<NavMapBeacon>? AllBeacons;
|
||||
public HashSet<Vector2i>? AllChunks;
|
||||
|
||||
public NavMapComponentState(Dictionary<(NavMapChunkType, Vector2i), Dictionary<AtmosDirection, ushort>> chunks, HashSet<NavMapBeacon> beacons)
|
||||
{
|
||||
Chunks = chunks;
|
||||
Beacons = beacons;
|
||||
}
|
||||
|
||||
public bool FullState => (AllChunks == null || AllBeacons == null);
|
||||
public bool FullState => AllChunks == null;
|
||||
|
||||
public void ApplyToFullState(IComponentState fullState)
|
||||
{
|
||||
|
|
@ -229,25 +142,25 @@ public abstract class SharedNavMapSystem : EntitySystem
|
|||
var state = (NavMapComponentState) fullState;
|
||||
DebugTools.Assert(state.FullState);
|
||||
|
||||
// Update chunks
|
||||
foreach (var key in state.Chunks.Keys)
|
||||
{
|
||||
if (!AllChunks!.Contains(key))
|
||||
state.Chunks.Remove(key);
|
||||
}
|
||||
|
||||
foreach (var (chunk, data) in Chunks)
|
||||
state.Chunks[chunk] = new(data);
|
||||
|
||||
// Update beacons
|
||||
foreach (var beacon in state.Beacons)
|
||||
foreach (var (index, data) in Chunks)
|
||||
{
|
||||
if (!AllBeacons!.Contains(beacon))
|
||||
state.Beacons.Remove(beacon);
|
||||
if (!state.Chunks.TryGetValue(index, out var stateValue))
|
||||
state.Chunks[index] = stateValue = new int[data.Length];
|
||||
|
||||
Array.Copy(data, stateValue, data.Length);
|
||||
}
|
||||
|
||||
foreach (var beacon in Beacons)
|
||||
state.Beacons.Add(beacon);
|
||||
state.Beacons.Clear();
|
||||
foreach (var (nuid, beacon) in Beacons)
|
||||
{
|
||||
state.Beacons.Add(nuid, beacon);
|
||||
}
|
||||
}
|
||||
|
||||
public IComponentState CreateNewFullState(IComponentState fullState)
|
||||
|
|
@ -256,36 +169,26 @@ public abstract class SharedNavMapSystem : EntitySystem
|
|||
var state = (NavMapComponentState) fullState;
|
||||
DebugTools.Assert(state.FullState);
|
||||
|
||||
var chunks = new Dictionary<(NavMapChunkType, Vector2i), Dictionary<AtmosDirection, ushort>>();
|
||||
var beacons = new HashSet<NavMapBeacon>();
|
||||
|
||||
foreach (var (chunk, data) in Chunks)
|
||||
chunks[chunk] = new(data);
|
||||
|
||||
foreach (var (chunk, data) in state.Chunks)
|
||||
var chunks = new Dictionary<Vector2i, int[]>(state.Chunks.Count);
|
||||
foreach (var (index, data) in state.Chunks)
|
||||
{
|
||||
if (AllChunks!.Contains(chunk))
|
||||
chunks.TryAdd(chunk, new(data));
|
||||
if (!AllChunks!.Contains(index))
|
||||
continue;
|
||||
|
||||
var newData = chunks[index] = new int[ArraySize];
|
||||
|
||||
if (Chunks.TryGetValue(index, out var updatedData))
|
||||
Array.Copy(newData, updatedData, ArraySize);
|
||||
else
|
||||
Array.Copy(newData, data, ArraySize);
|
||||
}
|
||||
|
||||
foreach (var beacon in Beacons)
|
||||
beacons.Add(new NavMapBeacon(beacon.NetEnt, beacon.Color, beacon.Text, beacon.Position));
|
||||
|
||||
foreach (var beacon in state.Beacons)
|
||||
{
|
||||
if (AllBeacons!.Contains(beacon))
|
||||
beacons.Add(new NavMapBeacon(beacon.NetEnt, beacon.Color, beacon.Text, beacon.Position));
|
||||
}
|
||||
|
||||
return new NavMapComponentState(chunks, beacons);
|
||||
return new NavMapComponentState(chunks, new(Beacons));
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public record struct NavMapBeacon(NetEntity NetEnt, Color Color, string Text, Vector2 Position)
|
||||
{
|
||||
public GameTick LastUpdate;
|
||||
}
|
||||
public record struct NavMapBeacon(NetEntity NetEnt, Color Color, string Text, Vector2 Position);
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,18 @@ namespace Content.Shared.Polymorph.Components;
|
|||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
|
||||
public sealed partial class ChameleonDisguiseComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The user of this disguise.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntityUid User;
|
||||
|
||||
/// <summary>
|
||||
/// The projector that created this disguise.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntityUid Projector;
|
||||
|
||||
/// <summary>
|
||||
/// The disguise source entity for copying the sprite.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
using Content.Shared.FixedPoint;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Polymorph.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Added to a player when they use a chameleon projector.
|
||||
/// Handles making them invisible and revealing when damaged enough or switching hands.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class ChameleonDisguisedComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// How much damage can be taken before revealing automatically.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public FixedPoint2 Integrity = FixedPoint2.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// The disguise entity parented to the player.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntityUid Disguise;
|
||||
|
||||
/// <summary>
|
||||
/// For client, whether the user's sprite was previously visible or not.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool WasVisible;
|
||||
}
|
||||
|
|
@ -25,22 +25,26 @@ public sealed partial class ChameleonProjectorComponent : Component
|
|||
public EntityWhitelist? Blacklist;
|
||||
|
||||
/// <summary>
|
||||
/// Polymorph configuration for the disguise entity.
|
||||
/// Disguise entity to spawn and use.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public PolymorphConfiguration Polymorph = new();
|
||||
public EntProtoId DisguiseProto = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Action for disabling your disguise's rotation.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntProtoId NoRotAction = "ActionDisguiseNoRot";
|
||||
[DataField]
|
||||
public EntityUid? NoRotActionEntity;
|
||||
|
||||
/// <summary>
|
||||
/// Action for anchoring your disguise in place.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntProtoId AnchorAction = "ActionDisguiseAnchor";
|
||||
[DataField]
|
||||
public EntityUid? AnchorActionEntity;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum health to give the disguise.
|
||||
|
|
@ -54,6 +58,12 @@ public sealed partial class ChameleonProjectorComponent : Component
|
|||
[DataField]
|
||||
public float MaxHealth = 100f;
|
||||
|
||||
/// <summary>
|
||||
/// Popup shown to the user when they try to disguise as an entity inside a container.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public LocId ContainerPopup = "chameleon-projector-inside-container";
|
||||
|
||||
/// <summary>
|
||||
/// Popup shown to the user when they try to disguise as an invalid entity.
|
||||
/// </summary>
|
||||
|
|
@ -65,4 +75,10 @@ public sealed partial class ChameleonProjectorComponent : Component
|
|||
/// </summary>
|
||||
[DataField]
|
||||
public LocId SuccessPopup = "chameleon-projector-success";
|
||||
|
||||
/// <summary>
|
||||
/// User currently disguised by this projector, if any
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntityUid? Disguised;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,31 +1,92 @@
|
|||
using Content.Shared.Actions;
|
||||
using Content.Shared.Construction.Components;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Hands;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Item;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Polymorph;
|
||||
using Content.Shared.Polymorph.Components;
|
||||
using Content.Shared.Popups;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Content.Shared.Polymorph.Systems;
|
||||
|
||||
/// <summary>
|
||||
/// Handles whitelist/blacklist checking.
|
||||
/// Actual polymorphing and deactivation is done serverside.
|
||||
/// Handles disguise validation, disguising and revealing.
|
||||
/// Most appearance copying is done clientside.
|
||||
/// </summary>
|
||||
public abstract class SharedChameleonProjectorSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly INetManager _net = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly ISerializationManager _serMan = default!;
|
||||
[Dependency] private readonly MetaDataSystem _meta = default!;
|
||||
[Dependency] private readonly MobThresholdSystem _mobThreshold = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actions = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _xform = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ChameleonDisguiseComponent, GotEquippedHandEvent>(OnDisguiseEquippedHand);
|
||||
SubscribeLocalEvent<ChameleonDisguiseComponent, ComponentShutdown>(OnDisguiseShutdown);
|
||||
|
||||
SubscribeLocalEvent<ChameleonDisguisedComponent, DamageChangedEvent>(OnDamageChanged);
|
||||
|
||||
SubscribeLocalEvent<ChameleonProjectorComponent, AfterInteractEvent>(OnInteract);
|
||||
SubscribeLocalEvent<ChameleonProjectorComponent, DisguiseToggleNoRotEvent>(OnToggleNoRot);
|
||||
SubscribeLocalEvent<ChameleonProjectorComponent, DisguiseToggleAnchoredEvent>(OnToggleAnchored);
|
||||
SubscribeLocalEvent<ChameleonProjectorComponent, HandDeselectedEvent>(OnDeselected);
|
||||
SubscribeLocalEvent<ChameleonProjectorComponent, GotUnequippedHandEvent>(OnUnequipped);
|
||||
SubscribeLocalEvent<ChameleonProjectorComponent, ComponentShutdown>(OnProjectorShutdown);
|
||||
}
|
||||
|
||||
#region Disguise entity
|
||||
|
||||
private void OnDisguiseEquippedHand(Entity<ChameleonDisguiseComponent> ent, ref GotEquippedHandEvent args)
|
||||
{
|
||||
TryReveal(ent.Comp.User);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnDisguiseShutdown(Entity<ChameleonDisguiseComponent> ent, ref ComponentShutdown args)
|
||||
{
|
||||
_actions.RemoveProvidedActions(ent.Comp.User, ent.Comp.Projector);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Disguised player
|
||||
|
||||
private void OnDamageChanged(Entity<ChameleonDisguisedComponent> ent, ref DamageChangedEvent args)
|
||||
{
|
||||
if (args.DamageDelta is not {} damage)
|
||||
return;
|
||||
|
||||
// reveal once enough damage is taken for the disguise to reveal itself
|
||||
var total = damage.GetTotal();
|
||||
if (total > ent.Comp.Integrity)
|
||||
TryReveal((ent, ent.Comp));
|
||||
else
|
||||
ent.Comp.Integrity -= total;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Projector
|
||||
|
||||
private void OnInteract(Entity<ChameleonProjectorComponent> ent, ref AfterInteractEvent args)
|
||||
{
|
||||
if (!args.CanReach || args.Target is not {} target)
|
||||
|
|
@ -34,6 +95,12 @@ public abstract class SharedChameleonProjectorSystem : EntitySystem
|
|||
var user = args.User;
|
||||
args.Handled = true;
|
||||
|
||||
if (_container.IsEntityInContainer(target))
|
||||
{
|
||||
_popup.PopupClient(Loc.GetString(ent.Comp.ContainerPopup), target, user);
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsInvalid(ent.Comp, target))
|
||||
{
|
||||
_popup.PopupClient(Loc.GetString(ent.Comp.InvalidPopup), target, user);
|
||||
|
|
@ -41,9 +108,53 @@ public abstract class SharedChameleonProjectorSystem : EntitySystem
|
|||
}
|
||||
|
||||
_popup.PopupClient(Loc.GetString(ent.Comp.SuccessPopup), target, user);
|
||||
Disguise(ent.Comp, user, target);
|
||||
Disguise(ent, user, target);
|
||||
}
|
||||
|
||||
private void OnToggleNoRot(Entity<ChameleonProjectorComponent> ent, ref DisguiseToggleNoRotEvent args)
|
||||
{
|
||||
if (ent.Comp.Disguised is not {} uid)
|
||||
return;
|
||||
|
||||
var xform = Transform(uid);
|
||||
_xform.SetLocalRotationNoLerp(uid, 0, xform);
|
||||
xform.NoLocalRotation = !xform.NoLocalRotation;
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnToggleAnchored(Entity<ChameleonProjectorComponent> ent, ref DisguiseToggleAnchoredEvent args)
|
||||
{
|
||||
if (ent.Comp.Disguised is not {} uid)
|
||||
return;
|
||||
|
||||
var xform = Transform(uid);
|
||||
if (xform.Anchored)
|
||||
_xform.Unanchor(uid, xform);
|
||||
else
|
||||
_xform.AnchorEntity((uid, xform));
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnDeselected(Entity<ChameleonProjectorComponent> ent, ref HandDeselectedEvent args)
|
||||
{
|
||||
RevealProjector(ent);
|
||||
}
|
||||
|
||||
private void OnUnequipped(Entity<ChameleonProjectorComponent> ent, ref GotUnequippedHandEvent args)
|
||||
{
|
||||
RevealProjector(ent);
|
||||
}
|
||||
|
||||
private void OnProjectorShutdown(Entity<ChameleonProjectorComponent> ent, ref ComponentShutdown args)
|
||||
{
|
||||
RevealProjector(ent);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region API
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if an entity cannot be used as a disguise.
|
||||
/// </summary>
|
||||
|
|
@ -56,10 +167,97 @@ public abstract class SharedChameleonProjectorSystem : EntitySystem
|
|||
/// <summary>
|
||||
/// On server, polymorphs the user into an entity and sets up the disguise.
|
||||
/// </summary>
|
||||
public virtual void Disguise(ChameleonProjectorComponent comp, EntityUid user, EntityUid entity)
|
||||
public void Disguise(Entity<ChameleonProjectorComponent> ent, EntityUid user, EntityUid entity)
|
||||
{
|
||||
var proj = ent.Comp;
|
||||
|
||||
// no spawning prediction sorry
|
||||
if (_net.IsClient)
|
||||
return;
|
||||
|
||||
// reveal first to allow quick switching
|
||||
TryReveal(user);
|
||||
|
||||
// add actions for controlling transform aspects
|
||||
_actions.AddAction(user, ref proj.NoRotActionEntity, proj.NoRotAction, container: ent);
|
||||
_actions.AddAction(user, ref proj.AnchorActionEntity, proj.AnchorAction, container: ent);
|
||||
|
||||
proj.Disguised = user;
|
||||
|
||||
var xform = Transform(user);
|
||||
var disguise = Spawn(proj.DisguiseProto, xform.Coordinates);
|
||||
var disguiseXform = Transform(disguise);
|
||||
_xform.SetParent(disguise, disguiseXform, user, xform);
|
||||
|
||||
var disguised = AddComp<ChameleonDisguisedComponent>(user);
|
||||
disguised.Disguise = disguise;
|
||||
Dirty(user, disguised);
|
||||
|
||||
// make disguise look real (for simple things at least)
|
||||
var meta = MetaData(entity);
|
||||
_meta.SetEntityName(disguise, meta.EntityName);
|
||||
_meta.SetEntityDescription(disguise, meta.EntityDescription);
|
||||
|
||||
var comp = EnsureComp<ChameleonDisguiseComponent>(disguise);
|
||||
comp.User = user;
|
||||
comp.Projector = ent;
|
||||
comp.SourceEntity = entity;
|
||||
comp.SourceProto = Prototype(entity)?.ID;
|
||||
Dirty(disguise, comp);
|
||||
|
||||
// item disguises can be picked up to be revealed, also makes sure their examine size is correct
|
||||
CopyComp<ItemComponent>((disguise, comp));
|
||||
|
||||
_appearance.CopyData(entity, disguise);
|
||||
|
||||
var mass = CompOrNull<PhysicsComponent>(entity)?.Mass ?? 0f;
|
||||
|
||||
// let the disguise die when its taken enough damage, which then transfers to the player
|
||||
// health is proportional to mass, and capped to not be insane
|
||||
if (TryComp<MobThresholdsComponent>(disguise, out var thresholds) && TryComp<MobThresholdsComponent>(user, out var userThresholds))
|
||||
{
|
||||
// cap disguise integrity at max health so you dont have to kill beforeif the player is of flesh and blood, cap max health to theirs
|
||||
// so that when reverting damage scales 1:1 and not round removing
|
||||
var playerMax = _mobThreshold.GetThresholdForState(user, MobState.Dead, userThresholds).Float();
|
||||
var max = playerMax == 0f ? proj.MaxHealth : Math.Max(proj.MaxHealth, playerMax);
|
||||
disguised.Integrity = max;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the disguise, if the user is disguised.
|
||||
/// </summary>
|
||||
public bool TryReveal(Entity<ChameleonDisguisedComponent?> ent)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp, false))
|
||||
return false;
|
||||
|
||||
if (TryComp<ChameleonDisguiseComponent>(ent.Comp.Disguise, out var disguise)
|
||||
&& TryComp<ChameleonProjectorComponent>(disguise.Projector, out var proj))
|
||||
{
|
||||
proj.Disguised = null;
|
||||
}
|
||||
|
||||
var xform = Transform(ent);
|
||||
xform.NoLocalRotation = false;
|
||||
_xform.Unanchor(ent, xform);
|
||||
|
||||
Del(ent.Comp.Disguise);
|
||||
RemComp<ChameleonDisguisedComponent>(ent);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reveal a projector's user, if any.
|
||||
/// </summary>
|
||||
public void RevealProjector(Entity<ChameleonProjectorComponent> ent)
|
||||
{
|
||||
if (ent.Comp.Disguised is {} user)
|
||||
TryReveal(user);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Copy a component from the source entity/prototype to the disguise entity.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Shared.Power;
|
||||
|
|
@ -5,4 +6,23 @@ namespace Content.Shared.Power;
|
|||
[UsedImplicitly]
|
||||
public abstract class SharedPowerMonitoringConsoleSystem : EntitySystem
|
||||
{
|
||||
// Chunk size is limited as we require ChunkSize^2 <= 32 (number of bits in an int)
|
||||
public const int ChunkSize = 5;
|
||||
|
||||
/// <summary>
|
||||
/// Converts the chunk's tile into a bitflag for the slot.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int GetFlag(Vector2i relativeTile)
|
||||
{
|
||||
return 1 << (relativeTile.X * ChunkSize + relativeTile.Y);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Vector2i GetTileFromIndex(int index)
|
||||
{
|
||||
var x = index / ChunkSize;
|
||||
var y = index % ChunkSize;
|
||||
return new Vector2i(x, y);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
using Content.Shared.Actions;
|
||||
|
||||
namespace Content.Shared.Resist;
|
||||
|
||||
// DeltaV
|
||||
public sealed partial class EscapeInventoryCancelActionEvent : InstantActionEvent;
|
||||
|
|
@ -9,9 +9,12 @@ public sealed class TagSystem : EntitySystem
|
|||
{
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
|
||||
private EntityQuery<TagComponent> _tagQuery;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_tagQuery = GetEntityQuery<TagComponent>();
|
||||
SubscribeLocalEvent<TagComponent, ComponentGetState>(OnTagGetState);
|
||||
SubscribeLocalEvent<TagComponent, ComponentHandleState>(OnTagHandleState);
|
||||
|
||||
|
|
@ -124,7 +127,7 @@ public sealed class TagSystem : EntitySystem
|
|||
/// </exception>
|
||||
public bool TryAddTag(EntityUid entity, string id)
|
||||
{
|
||||
return TryComp<TagComponent>(entity, out var component) &&
|
||||
return _tagQuery.TryComp(entity, out var component) &&
|
||||
AddTag(entity, component, id);
|
||||
}
|
||||
|
||||
|
|
@ -142,7 +145,7 @@ public sealed class TagSystem : EntitySystem
|
|||
/// </exception>
|
||||
public bool TryAddTags(EntityUid entity, params string[] ids)
|
||||
{
|
||||
return TryComp<TagComponent>(entity, out var component) &&
|
||||
return _tagQuery.TryComp(entity, out var component) &&
|
||||
AddTags(entity, component, ids);
|
||||
}
|
||||
|
||||
|
|
@ -160,7 +163,7 @@ public sealed class TagSystem : EntitySystem
|
|||
/// </exception>
|
||||
public bool TryAddTags(EntityUid entity, IEnumerable<string> ids)
|
||||
{
|
||||
return TryComp<TagComponent>(entity, out var component) &&
|
||||
return _tagQuery.TryComp(entity, out var component) &&
|
||||
AddTags(entity, component, ids);
|
||||
}
|
||||
|
||||
|
|
@ -175,7 +178,7 @@ public sealed class TagSystem : EntitySystem
|
|||
/// </exception>
|
||||
public bool HasTag(EntityUid entity, string id)
|
||||
{
|
||||
return TryComp<TagComponent>(entity, out var component) &&
|
||||
return _tagQuery.TryComp(entity, out var component) &&
|
||||
HasTag(component, id);
|
||||
}
|
||||
|
||||
|
|
@ -210,7 +213,7 @@ public sealed class TagSystem : EntitySystem
|
|||
/// </exception>
|
||||
public bool HasAllTags(EntityUid entity, List<string> ids)
|
||||
{
|
||||
return TryComp<TagComponent>(entity, out var component) &&
|
||||
return _tagQuery.TryComp(entity, out var component) &&
|
||||
HasAllTags(component, ids);
|
||||
}
|
||||
|
||||
|
|
@ -225,7 +228,7 @@ public sealed class TagSystem : EntitySystem
|
|||
/// </exception>
|
||||
public bool HasAllTags(EntityUid entity, IEnumerable<string> ids)
|
||||
{
|
||||
return TryComp<TagComponent>(entity, out var component) &&
|
||||
return _tagQuery.TryComp(entity, out var component) &&
|
||||
HasAllTags(component, ids);
|
||||
}
|
||||
|
||||
|
|
@ -240,7 +243,7 @@ public sealed class TagSystem : EntitySystem
|
|||
/// </exception>
|
||||
public bool HasAnyTag(EntityUid entity, params string[] ids)
|
||||
{
|
||||
return TryComp<TagComponent>(entity, out var component) &&
|
||||
return _tagQuery.TryComp(entity, out var component) &&
|
||||
HasAnyTag(component, ids);
|
||||
}
|
||||
|
||||
|
|
@ -266,7 +269,7 @@ public sealed class TagSystem : EntitySystem
|
|||
/// </exception>
|
||||
public bool HasAnyTag(EntityUid entity, List<string> ids)
|
||||
{
|
||||
return TryComp<TagComponent>(entity, out var component) &&
|
||||
return _tagQuery.TryComp(entity, out var component) &&
|
||||
HasAnyTag(component, ids);
|
||||
}
|
||||
|
||||
|
|
@ -281,7 +284,7 @@ public sealed class TagSystem : EntitySystem
|
|||
/// </exception>
|
||||
public bool HasAnyTag(EntityUid entity, IEnumerable<string> ids)
|
||||
{
|
||||
return TryComp<TagComponent>(entity, out var component) &&
|
||||
return _tagQuery.TryComp(entity, out var component) &&
|
||||
HasAnyTag(component, ids);
|
||||
}
|
||||
|
||||
|
|
@ -298,7 +301,7 @@ public sealed class TagSystem : EntitySystem
|
|||
/// </exception>
|
||||
public bool RemoveTag(EntityUid entity, string id)
|
||||
{
|
||||
return TryComp<TagComponent>(entity, out var component) &&
|
||||
return _tagQuery.TryComp(entity, out var component) &&
|
||||
RemoveTag(entity, component, id);
|
||||
}
|
||||
|
||||
|
|
@ -315,7 +318,7 @@ public sealed class TagSystem : EntitySystem
|
|||
/// </returns>
|
||||
public bool RemoveTags(EntityUid entity, params string[] ids)
|
||||
{
|
||||
return TryComp<TagComponent>(entity, out var component) &&
|
||||
return _tagQuery.TryComp(entity, out var component) &&
|
||||
RemoveTags(entity, component, ids);
|
||||
}
|
||||
|
||||
|
|
@ -332,7 +335,7 @@ public sealed class TagSystem : EntitySystem
|
|||
/// </exception>
|
||||
public bool RemoveTags(EntityUid entity, IEnumerable<string> ids)
|
||||
{
|
||||
return TryComp<TagComponent>(entity, out var component) &&
|
||||
return _tagQuery.TryComp(entity, out var component) &&
|
||||
RemoveTags(entity, component, ids);
|
||||
}
|
||||
|
||||
|
|
@ -488,7 +491,15 @@ public sealed class TagSystem : EntitySystem
|
|||
/// </exception>
|
||||
public bool HasAnyTag(TagComponent component, params string[] ids)
|
||||
{
|
||||
return HasAnyTag(component, ids.AsEnumerable());
|
||||
foreach (var id in ids)
|
||||
{
|
||||
AssertValidTag(id);
|
||||
|
||||
if (component.Tags.Contains(id))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,78 @@
|
|||
# sorted alphabetically based on filenames in the folder Resources/Audio/DeltaV/Jukebox
|
||||
# keep it ordered or I'll stab you
|
||||
|
||||
- files: ["a_different_reality_lagoona_remix.xm-MONO.ogg"]
|
||||
license: "CC-BY-4.0"
|
||||
copyright: "A.D.R (Lagoona rmx) by Andreas Viklund, converted to mono"
|
||||
source: "https://modarchive.org/index.php?request=view_by_moduleid&query=134786"
|
||||
|
||||
- files: ["aggravated.it-MONO.ogg"]
|
||||
license: "CC-BY-NC-SA-4.0"
|
||||
copyright: "MEL -Aggravated Assault by melcom, converted to mono"
|
||||
source: "https://modarchive.org/index.php?request=view_by_moduleid&query=176234"
|
||||
|
||||
- files: ["autumnal_equinox.xm-MONO.ogg"]
|
||||
license: "CC-BY-NC-4.0"
|
||||
copyright: "Autumnal Equinox by lemonade, converted to mono"
|
||||
source: "https://modarchive.org/index.php?request=view_by_moduleid&query=143993"
|
||||
|
||||
- files: ["DOS=HIGH,_UMB.ogg"]
|
||||
license: "Custom"
|
||||
copyright: "DOS=HIGH, UMB by MASTER BOOT RECORD may be used non-commercially in the Delta-V fork of SS14. Converted from FLAC to OGG, then converted to mono."
|
||||
source: "https://masterbootrecord.bandcamp.com/album/c-edit-config-sys"
|
||||
|
||||
- files: ["drozerix_-_alone.xm-MONO.ogg"]
|
||||
license: "Custom"
|
||||
copyright: "Alone by Drozerix, converted to mono"
|
||||
source: "https://modarchive.org/index.php?request=view_by_moduleid&query=199968"
|
||||
|
||||
- files: ["drozerix_-_leisurely_voice.xm-MONO.ogg"]
|
||||
license: "Custom"
|
||||
copyright: "Leisurely Voice by Drozerix, converted to mono"
|
||||
source: "https://modarchive.org/index.php?request=view_by_moduleid&query=183837"
|
||||
|
||||
- files: ["femtanyl_-_MURDER_EVERY_1_U_KNOW_feat._takihasdied_MONO.ogg"]
|
||||
license: "CC-BY-NC-SA-3.0"
|
||||
copyright: "MURDER EVERY 1 U KNOW by Femtanyl featuring takihasdied, used with the understanding that this is a noncommercial project with credit given to the author, also converted to mono."
|
||||
source: "https://femtanyl.bandcamp.com/track/murder-every-1-u-know-feat-takihasdied"
|
||||
|
||||
- files: ["hackers-MONO.ogg"]
|
||||
license: "CC-BY-NC-SA-3.0"
|
||||
copyright: "Hackers by Karl Casey @ White Bat Audio, converted to mono"
|
||||
source: "https://www.youtube.com/watch?v=k8nHWwO1U2Q"
|
||||
|
||||
- files: ["marhaba-MONO.ogg"]
|
||||
license: "CC-BY-NC-SA-3.0"
|
||||
copyright: "Marhaba by Ian Alex Mac. Converted from MP3 to OGG, then converted to mono"
|
||||
source: "https://freemusicarchive.org/music/Ian_Alex_Mac/Cues/Marhaba"
|
||||
|
||||
- files: ["Patricia_Taxxon_-_Minute_-_MONO.ogg"]
|
||||
license: "CC-BY-SA-3.0"
|
||||
copyright: "Minute by Patricia Taxxon off the album 'Aeroplane,' converted to mono"
|
||||
source: "https://patriciataxxon.bandcamp.com/track/minute"
|
||||
|
||||
- files: ["psirius_-_nymphs_of_the_forest.mptm-MONO.ogg"]
|
||||
license: "CC-BY-NC-SA-4.0"
|
||||
copyright: "Nymphs of the forest by Psirius, converted to mono"
|
||||
source: "https://modarchive.org/index.php?request=view_by_moduleid&query=185545"
|
||||
|
||||
- files: ["Scratch_Post_-_OST_MONO.ogg"]
|
||||
license: "CC-BY-SA-4.0"
|
||||
copyright: "Scratch Post by Ghirardelli7 on SoundCloud, used with the understanding that credit is given to the artist."
|
||||
source: "https://soundcloud.com/ghirardelli7/scratch-post-ost"
|
||||
|
||||
- files: ["shibamata-MONO.ogg"]
|
||||
license: "Custom"
|
||||
copyright: "Shibamata by .2gou / Dot Nigou. This track is not released under any formal licensure, and exists in the public domain in multiple forms, including remixes and mashups. Converted to mono."
|
||||
source: "https://www.youtube.com/watch?v=FIw-HUP7XK0"
|
||||
|
||||
- files: ["space_asshole.ogg"]
|
||||
license: "Custom"
|
||||
copyright: "Space Asshole by Chris Remo is used with special permission from the author, under the condition that the project remains non-commercial and open source. The author also requested that a link to his bandcamp be included: https://chrisremo.bandcamp.com/ Converted from stereo to mono."
|
||||
source: "https://idlethumbs.bandcamp.com/track/space-asshole"
|
||||
|
||||
- files: ["superposition-MONO.ogg"]
|
||||
license: "CC-BY-NC-3.0"
|
||||
copyright: "Superposition by Amie Waters, converted to mono"
|
||||
source: "https://amiewaters.bandcamp.com/track/superposition-2"
|
||||
|
||||
|
|
@ -58,6 +58,16 @@
|
|||
copyright: "Flip Flap by X-ceed is licensed under a short but clear license (see flip-flap.txt in this directory) and is free for non-commercial use. It was converted from MOD to WAV using Schism Tracker, in 16 Bit, Non-Interpolated mode, no output equalizer settings, Ramp volume at start of sample enabled. From there it was converted to OGG Vorbis with `ffmpeg -i flip-flap.wav -q 2.9 flip-flap-renc.ogg` (quality scale chosen to match size of the OGG file being replaced). Non-interpolated mode was chosen as the module has a high enough sample rate to balance it out, but seems muffled in other interpolation modes. If 'Ramp volume at start of sample' is not enabled, a clicking phenomenon results."
|
||||
source: "http://aminet.net/package/mods/xceed/Flipflap"
|
||||
|
||||
- files: ["Patricia_Taxxon_-_Chipshop.ogg"]
|
||||
license: "CC-BY-SA-3.0"
|
||||
copyright: "Chipshop by Patricia Taxxon off the album 'Bicycle'"
|
||||
source: "https://patriciataxxon.bandcamp.com/track/chipshop"
|
||||
|
||||
- files: ["Patricia_Taxxon_-_Minute.ogg"]
|
||||
license: "CC-BY-SA-3.0"
|
||||
copyright: "Minute by Patricia Taxxon off the album 'Aeroplane'"
|
||||
source: "https://patriciataxxon.bandcamp.com/track/minute"
|
||||
|
||||
- files: ["psirius_-_nights_of_orion.xm.ogg"]
|
||||
license: "CC-BY-NC-SA-4.0"
|
||||
copyright: "Nights of Orion by Psirius"
|
||||
|
|
|
|||
|
|
@ -2119,3 +2119,117 @@
|
|||
id: 320
|
||||
time: '2024-04-19T02:51:31.0000000+00:00'
|
||||
url: https://github.com/DeltaV-Station/Delta-v/pull/931
|
||||
- author: NullWanderer
|
||||
changes:
|
||||
- message: Merged upstream
|
||||
type: Add
|
||||
id: 321
|
||||
time: '2024-04-22T17:25:33.0000000+00:00'
|
||||
url: https://github.com/DeltaV-Station/Delta-v/pull/1119
|
||||
- author: Velcroboy
|
||||
changes:
|
||||
- message: Tweaked the inventories of unlocked Booze-O-Mats
|
||||
type: Tweak
|
||||
id: 322
|
||||
time: '2024-04-23T21:50:33.0000000+00:00'
|
||||
url: https://github.com/DeltaV-Station/Delta-v/pull/1127
|
||||
- author: Adrian16199
|
||||
changes:
|
||||
- message: Paramedic void suit, elite syndicate hardsuit, blood-red commander/normal
|
||||
hardsuit has now vulpkanin helmets possibility.
|
||||
type: Add
|
||||
id: 323
|
||||
time: '2024-04-25T17:26:39.0000000+00:00'
|
||||
url: https://github.com/DeltaV-Station/Delta-v/pull/1077
|
||||
- author: TadJohnson00
|
||||
changes:
|
||||
- message: Edited the rules. Please make sure to review them.
|
||||
type: Tweak
|
||||
id: 324
|
||||
time: '2024-04-25T21:27:43.0000000+00:00'
|
||||
url: https://github.com/DeltaV-Station/Delta-v/pull/1137
|
||||
- author: adeinitas
|
||||
changes:
|
||||
- message: Added a new piece of lobby art and 2 new tracks.
|
||||
type: Add
|
||||
id: 325
|
||||
time: '2024-04-28T16:15:51.0000000+00:00'
|
||||
url: https://github.com/DeltaV-Station/Delta-v/pull/1143
|
||||
- author: TadJohnson00
|
||||
changes:
|
||||
- message: Vulcans now receive a fire selector and vastly improved ergonomics. Ask
|
||||
your warden for some .30 practice and try it out!
|
||||
type: Tweak
|
||||
id: 326
|
||||
time: '2024-04-28T20:37:31.0000000+00:00'
|
||||
url: https://github.com/DeltaV-Station/Delta-v/pull/1139
|
||||
- author: pissdemon
|
||||
changes:
|
||||
- message: Vulpkanins or harpies getting gibbed/meatspiked no longer cures the colorblindness
|
||||
of vulpkanins/harpies nearby.
|
||||
type: Fix
|
||||
- message: Species vision filters no longer break the construction overlay. Vulp
|
||||
and harpy engineers rejoice!
|
||||
type: Fix
|
||||
id: 327
|
||||
time: '2024-04-28T22:09:58.0000000+00:00'
|
||||
url: https://github.com/DeltaV-Station/Delta-v/pull/1150
|
||||
- author: '- Mike and Still-Icarus'
|
||||
changes:
|
||||
- message: Added the Decimator Railgun, a thunderous new weapon for both syndicate
|
||||
and nuclear operatives.
|
||||
type: Add
|
||||
id: 328
|
||||
time: '2024-04-29T10:16:53.0000000+00:00'
|
||||
url: https://github.com/DeltaV-Station/Delta-v/pull/718
|
||||
- author: pissdemon
|
||||
changes:
|
||||
- message: Voice changer mask now lists vulpkanin, felinid and harpy accents correctly.
|
||||
type: Fix
|
||||
id: 329
|
||||
time: '2024-05-03T21:49:12.0000000+00:00'
|
||||
url: https://github.com/DeltaV-Station/Delta-v/pull/1162
|
||||
- author: DangerRevolution
|
||||
changes:
|
||||
- message: Chameleon clothing can now be set to skirts again
|
||||
type: Fix
|
||||
- message: Harpies can now wear chameleon clothing
|
||||
type: Fix
|
||||
id: 330
|
||||
time: '2024-05-03T23:49:06.0000000+00:00'
|
||||
url: https://github.com/DeltaV-Station/Delta-v/pull/1110
|
||||
- author: NullWanderer
|
||||
changes:
|
||||
- message: Fixed server performance issues
|
||||
type: Fix
|
||||
id: 331
|
||||
time: '2024-05-04T16:26:37.0000000+00:00'
|
||||
url: https://github.com/DeltaV-Station/Delta-v/pull/1163
|
||||
- author: Colin-Tel
|
||||
changes:
|
||||
- message: Added a number of new tracks to the Jukebox.
|
||||
type: Add
|
||||
id: 332
|
||||
time: '2024-05-04T16:59:28.0000000+00:00'
|
||||
url: https://github.com/DeltaV-Station/Delta-v/pull/1130
|
||||
- author: NullWanderer
|
||||
changes:
|
||||
- message: The color mixing applied to moths has been increased in strength by 25%.
|
||||
type: Tweak
|
||||
id: 333
|
||||
time: '2024-05-06T15:27:27.0000000+00:00'
|
||||
url: https://github.com/DeltaV-Station/Delta-v/pull/1015
|
||||
- author: Mnemotechnician
|
||||
changes:
|
||||
- message: Carrying is less likely to behave erratically or suddenly interrupt now.
|
||||
type: Fix
|
||||
- message: You can now see when someone is trying to pick you up, and also you can
|
||||
interrupt your attempt at escaping from their hands or inventory.
|
||||
type: Add
|
||||
- message: You can now properly take Felinids out of bags and place them inside.
|
||||
type: Add
|
||||
- message: Scientists have discovered that Felinids can sleep in bags.
|
||||
type: Add
|
||||
id: 334
|
||||
time: '2024-05-06T18:21:07.0000000+00:00'
|
||||
url: https://github.com/DeltaV-Station/Delta-v/pull/1118
|
||||
|
|
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
chameleon-projector-inside-container = There's no room to scan that!
|
||||
chameleon-projector-invalid = You can't disguise as that!
|
||||
chameleon-projector-success = Projected new disguise.
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
popup-sleep-in-bag = {THE($entity)} curls up and falls asleep.
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
carry-verb = Carry
|
||||
|
||||
carry-too-heavy = You're not strong enough.
|
||||
carry-started = {THE($carrier)} is trying to pick you up!
|
||||
|
|
|
|||
|
|
@ -0,0 +1,92 @@
|
|||
# sorted alphabetically based on filenames in the folder Resources/Audio/DeltaV/Jukebox
|
||||
# keep it ordered or I'll stab you
|
||||
|
||||
- type: jukebox
|
||||
id: ADiffReal
|
||||
name: Andreas Viklund - A.D.R (Lagoona rmx)
|
||||
path:
|
||||
path: /Audio/DeltaV/Jukebox/a_different_reality_lagoona_remix.xm-MONO.ogg
|
||||
|
||||
- type: jukebox
|
||||
id: AggAss
|
||||
name: melcom - Aggravated Assault
|
||||
path:
|
||||
path: /Audio/DeltaV/Jukebox/aggravated.it-MONO.ogg
|
||||
|
||||
- type: jukebox
|
||||
id: AutEqu
|
||||
name: lemonade - Autumnal Equinox
|
||||
path:
|
||||
path: /Audio/DeltaV/Jukebox/autumnal_equinox.xm-MONO.ogg
|
||||
|
||||
- type: jukebox
|
||||
id: DosHighUmb
|
||||
name: MASTER BOOT RECORD - DOS=HIGH, UMB
|
||||
path:
|
||||
path: /Audio/DeltaV/Jukebox/DOS=HIGH,_UMB.ogg
|
||||
|
||||
- type: jukebox
|
||||
id: DrozAlone
|
||||
name: Drozerix - Alone
|
||||
path:
|
||||
path: /Audio/DeltaV/Jukebox/drozerix_-_alone.xm-MONO.ogg
|
||||
|
||||
- type: jukebox
|
||||
id: DrozLeisure
|
||||
name: Drozerix - Leisurely Voice
|
||||
path:
|
||||
path: /Audio/DeltaV/Jukebox/drozerix_-_leisurely_voice.xm-MONO.ogg
|
||||
|
||||
- type: jukebox
|
||||
id: FemtMurder
|
||||
name: Femtanyl feat. takihasdied - MURDER EVERY 1 U KNOW
|
||||
path:
|
||||
path: /Audio/DeltaV/Jukebox/femtanyl_-_MURDER_EVERY_1_U_KNOW_feat._takihasdied_MONO.ogg
|
||||
|
||||
- type: jukebox
|
||||
id: KCHaxors
|
||||
name: Karl Casey @ White Bat Audio - Hackers
|
||||
path:
|
||||
path: /Audio/DeltaV/Jukebox/hackers-MONO.ogg
|
||||
|
||||
- type: jukebox
|
||||
id: IanMarhaba
|
||||
name: Ian Alex Mac. - Marhaba
|
||||
path:
|
||||
path: /Audio/DeltaV/Jukebox/marhaba-MONO.ogg
|
||||
|
||||
- type: jukebox
|
||||
id: PTMinute
|
||||
name: Patricia Taxxon - Minute
|
||||
path:
|
||||
path: /Audio/DeltaV/Jukebox/Patricia_Taxxon_-_Minute_-_MONO.ogg
|
||||
|
||||
- type: jukebox
|
||||
id: NymphsForest
|
||||
name: Psirius - Nymphs of the Forest
|
||||
path:
|
||||
path: /Audio/DeltaV/Jukebox/psirius_-_nymphs_of_the_forest.mptm-MONO.ogg
|
||||
|
||||
- type: jukebox
|
||||
id: GhirScratch
|
||||
name: ghirardelli7 - Scratch Post
|
||||
path:
|
||||
path: /Audio/DeltaV/Jukebox/Scratch_Post_-_OST_MONO.ogg
|
||||
|
||||
- type: jukebox
|
||||
id: JukeShiba
|
||||
name: Dot Nigou - Shibamata
|
||||
path:
|
||||
path: /Audio/DeltaV/Jukebox/shibamata-MONO.ogg
|
||||
|
||||
- type: jukebox
|
||||
id: SpaceAsshowl
|
||||
name: Chris Remo - Space Asshole
|
||||
path:
|
||||
path: /Audio/DeltaV/Jukebox/space_asshole-MONO.ogg
|
||||
|
||||
- type: jukebox
|
||||
id: AmieSuperpos
|
||||
name: Amie Waters - Superposition
|
||||
path:
|
||||
path: /Audio/DeltaV/Jukebox/superposition-MONO.ogg
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
- type: vendingMachineInventory
|
||||
id: BoozeOMatUnlockedInventory
|
||||
startingInventory:
|
||||
DrinkGlass: 15 #Kept glasses at top for ease to differentiate from booze.
|
||||
DrinkShotGlass: 5
|
||||
DrinkGlassCoupeShaped: 5
|
||||
DrinkVacuumFlask: 1
|
||||
DrinkFlaskBar: 1
|
||||
DrinkShaker: 4
|
||||
CustomDrinkJug: 2 #to allow for custom drinks in the soda/booze dispensers
|
||||
DrinkAleBottleFull: 8
|
||||
DrinkBeerBottleFull: 8
|
||||
DrinkBeerCan: 8
|
||||
DrinkWineCan: 8
|
||||
DrinkSolDryCan: 8
|
||||
DrinkWineBottleFull: 3
|
||||
DrinkSojuBottleFull: 1
|
||||
DrinkSakeBottleFull: 1
|
||||
DrinkGinBottleFull: 3
|
||||
DrinkRumBottleFull: 3
|
||||
DrinkTequilaBottleFull: 3
|
||||
DrinkVodkaBottleFull: 3
|
||||
DrinkWhiskeyBottleFull: 3
|
||||
#Mixers
|
||||
DrinkColaBottleFull: 4
|
||||
DrinkCreamCarton: 5
|
||||
DrinkGrenadineBottleFull: 2
|
||||
DrinkJuiceLimeCarton: 3
|
||||
DrinkJuiceOrangeCarton: 3
|
||||
DrinkJuiceTomatoCarton: 3
|
||||
DrinkCoffeeLiqueurBottleFull: 1
|
||||
DrinkSodaWaterCan: 8
|
||||
DrinkSpaceMountainWindBottleFull: 3
|
||||
DrinkSpaceUpBottleFull: 3
|
||||
DrinkTonicWaterCan: 8
|
||||
DrinkVermouthBottleFull: 4
|
||||
#emaggedInventory:
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
- type: entity
|
||||
id: ActionCancelEscape
|
||||
name: Stop escaping
|
||||
description: Calm down and sit peacefuly in your carrier's inventory
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: InstantAction
|
||||
icon: DeltaV/Actions/escapeinventory.rsi/cancel-escape.png
|
||||
event: !type:EscapeInventoryCancelActionEvent
|
||||
useDelay: 2
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
name: Vulcan
|
||||
parent: BaseWeaponRifle
|
||||
id: WeaponRifleVulcan
|
||||
description: One of the first firearms modified for space usage, this rifle was purpose-designed to punch holes in Cargonian "combat mechs." Uses .30 rifle ammo.
|
||||
description: One of the heaviest small arms to grace Security's armory, this rifle is a modern take on a classic, informally dubbed the "Right Arm of the Free World". Uses .30 rifle ammo.
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: DeltaV/Objects/Weapons/Guns/Rifles/vulcan.rsi
|
||||
|
|
@ -12,10 +12,15 @@
|
|||
- state: mag-0
|
||||
map: ["enum.GunVisualLayers.Mag"]
|
||||
- type: Gun
|
||||
fireRate: 2
|
||||
minAngle: 1
|
||||
maxAngle: 24
|
||||
angleIncrease: 6
|
||||
angleDecay: 24
|
||||
fireRate: 5
|
||||
selectedMode: SemiAuto
|
||||
availableModes:
|
||||
- SemiAuto
|
||||
- FullAuto
|
||||
- type: ChamberMagazineAmmoProvider
|
||||
- type: ItemSlots
|
||||
slots:
|
||||
|
|
|
|||
|
|
@ -54,5 +54,7 @@
|
|||
id: VendingMachineBoozeUnlocked
|
||||
suffix: Unlocked
|
||||
components:
|
||||
- type: VendingMachine
|
||||
pack: BoozeOMatUnlockedInventory
|
||||
- type: AccessReader
|
||||
enabled: false
|
||||
|
|
|
|||
|
|
@ -56,6 +56,8 @@
|
|||
collection: VulpkaninWhines
|
||||
Howl:
|
||||
collection: VulpkaninHowls
|
||||
Awoo:
|
||||
collection: VulpkaninHowls
|
||||
Honk:
|
||||
collection: HarpyHonks
|
||||
Weh:
|
||||
|
|
@ -98,6 +100,8 @@
|
|||
collection: VulpkaninWhines
|
||||
Howl:
|
||||
collection: VulpkaninHowls
|
||||
Awoo:
|
||||
collection: VulpkaninHowls
|
||||
|
||||
- type: emoteSounds
|
||||
id: FemaleVulpkanin
|
||||
|
|
@ -136,3 +140,5 @@
|
|||
collection: VulpkaninWhines
|
||||
Howl:
|
||||
collection: VulpkaninHowls
|
||||
Awoo:
|
||||
collection: VulpkaninHowls
|
||||
|
|
|
|||
|
|
@ -125,3 +125,15 @@
|
|||
- howls!
|
||||
- howling.
|
||||
- howled.
|
||||
|
||||
- type: emote
|
||||
id: Awoo
|
||||
category: Vocal
|
||||
chatMessages: [awoos.]
|
||||
chatTriggers:
|
||||
- awoo.
|
||||
- awoo!
|
||||
- awoos.
|
||||
- awoos!
|
||||
- awooing.
|
||||
- awooed.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
- type: speechVerb
|
||||
id: Vulpkanin
|
||||
name: chat-speech-verb-name-vox
|
||||
name: chat-speech-verb-name-vulpkanin
|
||||
speechVerbStrings:
|
||||
- chat-speech-verb-vulpkanin-1
|
||||
- chat-speech-verb-vulpkanin-2
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
- type: speechVerb
|
||||
id: Felinid
|
||||
name: chat-speech-verb-name-vox
|
||||
name: chat-speech-verb-name-felinid
|
||||
speechVerbStrings:
|
||||
- chat-speech-verb-felinid-1
|
||||
- chat-speech-verb-felinid-2
|
||||
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
- type: speechVerb
|
||||
id: Harpy
|
||||
name: chat-speech-verb-name-vox
|
||||
name: chat-speech-verb-name-harpy
|
||||
speechVerbStrings:
|
||||
- chat-speech-verb-harpy-1
|
||||
- chat-speech-verb-harpy-2
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
delay: 0.5
|
||||
- type: ExplosionResistance
|
||||
damageCoefficient: 0.9
|
||||
- type: AllowsSleepInside # DeltaV - enable sleeping inside bags
|
||||
|
||||
- type: entity
|
||||
parent: ClothingBackpack
|
||||
|
|
@ -267,7 +268,7 @@
|
|||
- type: Sprite
|
||||
sprite: Clothing/Back/Backpacks/syndicate.rsi
|
||||
- type: ExplosionResistance
|
||||
damageCoefficient: 0.1
|
||||
damageCoefficient: 0.1
|
||||
|
||||
#Special
|
||||
- type: entity
|
||||
|
|
|
|||
|
|
@ -36,6 +36,11 @@
|
|||
- type: Clothing
|
||||
slots: [innerclothing]
|
||||
femaleMask: UniformTop
|
||||
- type: Tag #Delta-V : Adds tags to fix the Syndicate Jumpskirt, which is parented to this? this needs to be updated with *every* new tag given to UnsensoredClothingUniformBase, as tags aren't inherited
|
||||
tags:
|
||||
- Skirt # Delta-V : Harpies can wear this
|
||||
- ClothMade # Delta-V : Moths can eat this
|
||||
- WhitelistChameleon # Delta-V : You can set Chameleon clothes to this.
|
||||
|
||||
|
||||
- type: entity
|
||||
|
|
@ -59,7 +64,8 @@
|
|||
- type: Clothing
|
||||
slots: [innerclothing]
|
||||
femaleMask: UniformTop
|
||||
- type: Tag #DeltaV, needed for species with nonhuman legs/can only wear skirts
|
||||
- type: Tag #Delta-V : Adds the Skirt tag so Harpies can wear this, this needs to be updated with *every* new tag given to UnsensoredClothingUniformBase, as tags aren't inherited
|
||||
tags:
|
||||
- Skirt
|
||||
- ClothMade # Delta-V - allows moths to eat this
|
||||
- Skirt # Delta-V : Harpies can wear this
|
||||
- ClothMade # Delta-V : Moths can eat this
|
||||
- WhitelistChameleon # Delta-V : You can set Chameleon clothes to this.
|
||||
|
|
|
|||
|
|
@ -301,6 +301,7 @@
|
|||
- PrisonUniform
|
||||
- WhitelistChameleon
|
||||
- Skirt # Delta-V - Skirt Tag so Harpies can wear it.
|
||||
# Delta-V : This needs to be updated with every new tag given to ClothingUniformSkirtBase as tags aren't inherited.
|
||||
|
||||
- type: entity
|
||||
parent: ClothingUniformSkirtBase
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@
|
|||
suffix: Chameleon
|
||||
components:
|
||||
- type: Tag
|
||||
tags: [] # ignore "WhitelistChameleon" tag
|
||||
tags:
|
||||
- Skirt # Delta-V : Overwrites null tag so Harpies can wear chameleon suit #[] # ignore "WhitelistChameleon" tag
|
||||
- type: Sprite
|
||||
sprite: Clothing/Uniforms/Jumpsuit/color.rsi
|
||||
layers:
|
||||
|
|
@ -39,4 +40,4 @@
|
|||
- type: UserInterface
|
||||
interfaces:
|
||||
- key: enum.ChameleonUiKey.Key
|
||||
type: ChameleonBoundUserInterface
|
||||
type: ChameleonBoundUserInterface
|
||||
|
|
|
|||
|
|
@ -607,6 +607,10 @@
|
|||
name: ghost-role-information-Shiva-name
|
||||
description: ghost-role-information-Shiva-description
|
||||
rules: ghost-role-information-Shiva-rules
|
||||
requirements: # Delta V - No raiders
|
||||
- !type:DepartmentTimeRequirement
|
||||
department: Security
|
||||
time: 14400 # DeltaV - 4 hours
|
||||
- type: GhostTakeoverAvailable # DeltaV
|
||||
- type: InteractionPopup
|
||||
successChance: 0.5 # spider is mean
|
||||
|
|
@ -779,6 +783,10 @@
|
|||
allowMovement: true
|
||||
name: ghost-role-information-punpun-name
|
||||
description: ghost-role-information-punpun-description
|
||||
requirements: # Delta V - No raiders
|
||||
- !type:RoleTimeRequirement
|
||||
role: JobBartender
|
||||
time: 14400 # Delta V - 4 hours
|
||||
- type: GhostTakeoverAvailable
|
||||
- type: Butcherable
|
||||
butcheringType: Spike
|
||||
|
|
|
|||
|
|
@ -15,15 +15,12 @@
|
|||
blacklist:
|
||||
components:
|
||||
- ChameleonDisguise # no becoming kleiner
|
||||
- InsideEntityStorage # no clark kent going in phone booth and becoming superman
|
||||
- MindContainer # no
|
||||
- Pda # PDAs currently make you invisible /!\
|
||||
polymorph:
|
||||
entity: ChameleonDisguise
|
||||
disguiseProto: ChameleonDisguise
|
||||
|
||||
- type: entity
|
||||
noSpawn: true
|
||||
parent: BaseMob
|
||||
id: ChameleonDisguise
|
||||
name: Urist McKleiner
|
||||
components:
|
||||
|
|
@ -31,20 +28,8 @@
|
|||
- type: Sprite
|
||||
sprite: /Textures/Mobs/Species/Human/parts.rsi
|
||||
state: full
|
||||
# so people can attempt to pick it up
|
||||
- type: Item
|
||||
# so it can take damage
|
||||
# projector system sets health to be proportional to mass
|
||||
- type: Damageable
|
||||
- type: MobState
|
||||
- type: MobThresholds
|
||||
thresholds:
|
||||
0: Alive
|
||||
1: Critical
|
||||
200: Dead
|
||||
- type: MovementSpeedModifier
|
||||
baseWalkSpeed: 1 # precise movement for the perfect spot
|
||||
baseSprintSpeed: 5 # the jig is up
|
||||
- type: Transform
|
||||
noRot: true # players rotation and anchor is used instead
|
||||
- type: ChameleonDisguise
|
||||
|
||||
# actions
|
||||
|
|
|
|||
|
|
@ -1046,7 +1046,7 @@
|
|||
- type: entity
|
||||
parent: AirlockMaintCommandLocked
|
||||
id: AirlockMaintQuartermasterLocked
|
||||
suffix: Quartermaster, Locked
|
||||
suffix: Logistics Officer, Locked # DeltaV - Logistics Department replacing Cargo
|
||||
components:
|
||||
- type: ContainerFill
|
||||
containers:
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
stationProto: StandardNanotrasenStation
|
||||
components:
|
||||
- type: StationEmergencyShuttle
|
||||
emergencyShuttlePath: /Maps/Shuttles/DeltaV/NTES_Delta.yml
|
||||
emergencyShuttlePath: /Maps/Shuttles/DeltaV/NTES_Box.yml
|
||||
- type: StationNameSetup
|
||||
mapNameTemplate: '{0} Edge Station {1}'
|
||||
nameGenerator:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
- type: entity
|
||||
id: MobObserverTelegnostic
|
||||
name: telegnostic projection
|
||||
description: Ominous. Placeholder sprite.
|
||||
description: Ominous.
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: Sprite
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
parent: BaseItem
|
||||
id: CrystalNormality
|
||||
name: normality crystal
|
||||
description: It looks... normal. Placeholder sprite.
|
||||
description: It looks... normal.
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Nyanotrasen/Objects/Materials/materials.rsi
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
parent: BaseItem
|
||||
id: Hairball
|
||||
name: hairball
|
||||
description: Felinids, man... Placeholder sprite.
|
||||
description: Felinids, man...
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Nyanotrasen/Objects/Specific/Species/felinid.rsi
|
||||
|
|
|
|||
|
|
@ -125,8 +125,6 @@
|
|||
solution: vat_oil
|
||||
- type: Drink
|
||||
solution: vat_oil
|
||||
- type: Openable
|
||||
opened: true
|
||||
- type: Appearance
|
||||
- type: ActivatableUI
|
||||
key: enum.DeepFryerUiKey.Key
|
||||
|
|
|
|||
|
|
@ -23,8 +23,6 @@
|
|||
maxVol: 200
|
||||
- type: Drink
|
||||
solution: fountain
|
||||
- type: Openable
|
||||
opened: true
|
||||
- type: DrawableSolution
|
||||
solution: fountain
|
||||
- type: DrainableSolution
|
||||
|
|
|
|||
|
|
@ -13,8 +13,10 @@
|
|||
- /Audio/Lobby/melcom-cyberpunk.it.ogg
|
||||
- /Audio/Lobby/midori_-_conundrum_final.it.ogg
|
||||
- /Audio/Lobby/mod.flip-flap.ogg
|
||||
- /Audio/Lobby/Patricia_Taxxon_-_Minute.ogg
|
||||
- /Audio/Lobby/Patricia_Taxxon_-_Chipshop.ogg
|
||||
- /Audio/Lobby/psirius_-_nights_of_orion.xm.ogg
|
||||
#- /Audio/Lobby/psirius_-_nymphs_of_the_forest.mptm.ogg
|
||||
- /Audio/Lobby/psirius_-_nymphs_of_the_forest.mptm.ogg
|
||||
- /Audio/Lobby/superposition.ogg
|
||||
- /Audio/Lobby/hackers.ogg
|
||||
- /Audio/Lobby/every_light_is_blinking_at_once.ogg
|
||||
|
|
|
|||
|
|
@ -37,3 +37,7 @@
|
|||
- type: lobbyBackground
|
||||
id: TechnologicalProgress
|
||||
background: /Textures/LobbyScreens/DeltaV__Technological_Progress_by_threlo.png
|
||||
|
||||
- type: lobbyBackground
|
||||
id: OperatorPlush
|
||||
background: /Textures/LobbyScreens/OperatorPlush.webp
|
||||
|
|
|
|||
|
|
@ -1,135 +1,109 @@
|
|||
[color=#ff0000]YOU MUST BE AT LEAST 17 YEARS OF AGE TO PLAY ON DELTA-V SERVERS. ANY USERS SUSPECTED OF BEING UNDER 17 YEARS OF AGE WILL BE BANNED UNTIL THEY ARE OF AGE.[/color]
|
||||
[color=#ffff00]Mission Statement:
|
||||
|
||||
[color=#ff0000]DISCONNECTING FROM OR IGNORING/EVADING ADMIN-HELPS WILL RESULT IN AN APPEAL ONLY BAN.[/color]
|
||||
Delta-V is a Medium Roleplay server. Try to immerse yourself into your character. This includes doing your job, interacting with your fellow crewmates, and using roleplay as the primary vessel to play the game. MRP places less emphasis on "winning" and more on just telling a story.[/color]
|
||||
|
||||
[color=#ff0000]THE USAGE OF ANY THIRD-PARTY APPLICATIONS/SCRIPTS/CLIENT MODIFICATIONS TO GAIN AN ADVANTAGE, AVOID INTENDED GAME/SERVER MECHANICS, OR TO HARM SERVER INFRASTRUCTURE IS STRICTLY PROHIBITED. ANY AND ALL INSTANCES OF THIS WILL BE MET WITH AN APPEAL-ONLY BAN.[/color]
|
||||
[color=#ff0000]Server Disclaimers:[/color]
|
||||
You must be at least 17 years of age to play on Delta-V’s servers. Any users suspected of being under the age of 17 will be removed.
|
||||
|
||||
[color=#00ff00]Rules Update 11Mar2024 - Added Cryo Rules, Changed the Prisoner Rule, Expanded on EORG, added rules aimed at meta-grudging/vitriolic OOC/LOOC, and clarified part of Rule 6[/color]
|
||||
Intentionally disconnecting, responding with hostility, or refusing to respond when an admin privately messages you in-game will be met with an appeal-only ban. Additionally, abusing the AHelp relay by flooding it with garbage, checking for admins before stating a problem (ex: "hello?", "any admins?"), using it as a chatroom, or sending messages of no substance will result in your removal. All AHelp messages are relayed to the Delta-V Discord.
|
||||
|
||||
[color=#ffff00]Delta-V is a Medium Roleplay server. Try to immerse yourself into your character. This includes doing your job, interacting with your fellow crewmates, and using roleplay as the primary vessel to play the game. MRP places less emphasis on “winning” and more on just telling a story.[/color]
|
||||
The usage of any third-party applications/scripts/client modifications or any other applicable software to gain an advantage, avoid intended game/server mechanics, or to harm server infrastructure is strictly prohibited, as is the abuse of glitches, exploits and bugs. Any and all instances of this will be met with an appeal-only ban; please use the #bug-reports channel on the Discord to report any issues you find in-game.
|
||||
|
||||
If you have any questions about these rules, please use the admin help (ahelp) menu by hitting F1 in-game or clicking the “Ahelp” button in the lobby.
|
||||
If you have any questions about these rules, please use the admin help menu by hitting F1 in-game or clicking the "AHelp" button in the lobby.
|
||||
|
||||
[color=#a4885c]Community Rules:[/color]
|
||||
[color=#a4885c]0.[/color] Admins can disregard any and all of these rules if they deem it in the best interest of the current round, server, and/or community at large.
|
||||
- Administrators will be held fully accountable for their actions if they exercise this privilege.
|
||||
- All of these rules apply as they are intended. Every example of a rule break cannot be defined as written, therefore, enforcement of the rules is subject to staff interpretation of the rule's intention.
|
||||
- Administrators will be held fully accountable for their actions if they exercise this privilege.
|
||||
- All of these rules apply as they are intended. Every example of a rule break cannot be defined as written, therefore, enforcement of the rules is subject to staff interpretation of the rule's intention.
|
||||
- Any extended discussion regarding interpretation of the rules should be avoided in the AHelp menu and can be instead pursued within the Delta-V Discord in the #help channel.
|
||||
|
||||
[color=#a4885c]1.[/color] Erotic Roleplay (ERP), erotic content, or 18+ sexual content is [color=#ff0000]not allowed under any circumstance[/color]. This includes comments not explicitly sexual in nature that contain words, phrases, or ideations that are deemed inappropriate. [color=#ff0000]If roleplay reaches a point where it has become sexual and/or uncomfortable, immediately stop and contact an administrator.[/color]
|
||||
[color=#a4885c]E1.[/color] Erotic Roleplay (ERP), erotic content, or 18+ sexual content is [color=#ff0000]not allowed under any circumstance[/color]. This includes comments not explicitly sexual in nature that contain words, phrases, or ideations that are deemed inappropriate by staff or members. [color=#ff0000]If roleplay reaches a point where it has become sexual and/or uncomfortable, immediately stop and contact an administrator or moderator.[/color]
|
||||
|
||||
[color=#a4885c]2.[/color] Follow the server expectations
|
||||
- Do not cheat.
|
||||
- Do not abuse glitches and exploits. [color=#ff0000]We have zero tolerance for abusing exploits/bugs. If you’re not sure, ask an admin.[/color] Otherwise, please use the #bug-reports channel on the Discord to report the bug.
|
||||
- If you are banned from a role or department, you may not play that role. This includes seeking or accepting promotions into roles you are banned from.
|
||||
- Do not make yourself a major problem/annoyance/disruption for the crew while not being an antagonist (i.e. self-antagging).
|
||||
- Do not make yourself a major problem/annoyance/disruption for one specific crew member in a way that would actively detract from the other player's enjoyment of the shift.
|
||||
- Do not, as a crewmate, hide the nuclear fission explosive (i.e. "the nuke") in an impossible to see location.
|
||||
- Do not ignore the admin help relay or abuse it by flooding it with garbage, checking for admins before stating a problem (ex: "hello?", "any admins?"), using it as a chatroom, or sending messages of no substance. Hostility to administrators in the relay will result in your removal. All ahelp messages are sent to the Delta-V Discord.
|
||||
- Department strikes (ex: cargonia and any variation thereof), riots, cults, and any other type of similar largely disruptive behavior are strictly forbidden. These activities are generally antagonist-only and all players regardless of antagonist status must obtain admin Central Command permission before engaging in this behavior (you are extremely unlikely to get permission).
|
||||
- AFK (aka SSD) and catatonic players are considered to have the same rights as a conscious crewmate.
|
||||
- End-of-round grief (EORG) is not allowed. This includes attacking, destroying, polluting, and injuring without reason both *at* and *on the way to* Central Command.
|
||||
* This also extends to the following:
|
||||
* Neutral parties, such as space dragons or sentient artifacts activating harmful nodes.
|
||||
* Attempts to block the docking ports or ports leading into Central Command.
|
||||
[color=#a4885c]E2.[/color] Delta-V is an English-based community; use English as your primary method of communication within the game and Delta-V servers.
|
||||
- Do not spam or advertise on our server.
|
||||
- Be respectful towards other members and avoid making others uncomfortable.
|
||||
- We have a zero tolerance policy for racism, sexism, homophobia, violence, threats, hate speech, slurs, and other forms of bigotry, discrimination, and harassment. Slurs and terms that use real or implied mental or physical disability to mock or denigrate members or their in-game characters are also strictly prohibited. Failure to comply with this rule will result in community removal.
|
||||
- The denigration of other player characters through reference to their species/morphotype or debasement by comparison to nonsophonts on a discriminatory basis is strictly prohibited and may result in an appeal-only ban.
|
||||
|
||||
[color=#a4885c]3.[/color] Follow Chat Guidelines
|
||||
- Use English as your primary method of communication.
|
||||
- Do not spam.
|
||||
- Do not advertise.
|
||||
- Be respectful towards other players in LOOC and OOC channels, and avoid making others uncomfortable. This can range anywhere from starting arguments to personal attacks against others, depending on the context.
|
||||
- Do not use netspeak in character (i.e. btw, lmfao).
|
||||
- Do not use the Emotes channel to bypass an inability to speak. This includes examples like "motions for you to put parmesan THEN sauce on the spaghetti," "im friendly," and "huh weird looks like I can't type."
|
||||
- Use the LOOC and OOC channels properly. Don’t speak of in-character matters in those channels unless you’re asking questions related to in-game concepts.
|
||||
* DO NOT use in-character channels to bypass a lack of ability to use OOC/LOOC chats (e.g. "Huh, wonder where captain went? *OOC* He probably left to get dinner lol"). This will result in an immediate dewhitelist.
|
||||
- Hate speech, slurs and bigotry are [color=#ff0000]not allowed[/color]. Words that are closely tied to real life slurs are [color=#ff0000]not allowed[/color].
|
||||
- No racist remarks towards in-game races/morphotypes (i.e. Simulated Racism), while you don’t have to like everybody, you should not be acting upon nor expressing your distaste for other races/morphotypes.
|
||||
[color=#a4885c]E3.[/color] Delta-V welcomes content creators and streamers to our community, but we have a few rules regarding livestreaming game servers.
|
||||
- While a stream delay is not necessary, it is highly recommended to have at least a 10 second delay.
|
||||
- Please let admins know you are streaming via the AHelp prior to doing so.
|
||||
- If your chat or viewers are in-game with you while also watching your stream, they are breaking our server rules. Please do not coordinate with them and discourage this behavior if it is seen.
|
||||
|
||||
[color=#a4885c]4.[/color] Follow Metagaming guidelines
|
||||
- If you die and get revived, do not act on things you saw while you were dead.
|
||||
- Do not engage in meta-communications. This includes using chat channels outside of what is available in-game to communicate with other players in the same game.
|
||||
- Players are allowed to have in-character relationships (friends, enemies, or otherwise), however they cannot be used as a reason to grant or deny things based exclusively on having a relationship with one another(i.e. Meta-Friending).
|
||||
- You are allowed to have knowledge of past experiences with someone else in prior shifts. This does not give you permission to hold a grudge against someone that results in you treating them differently in an unfair way (i.e. Meta-Grudging).
|
||||
- Specific players who were antagonists in previous rounds must not be treated differently because of it.
|
||||
- Do not "Antag Roll." This is the act of joining rounds for the purpose of seeing if you joined as an antagonist, and leaving soon after if not. Players who have a history of this behavior will have their whitelist revoked and/or face a ban.
|
||||
- [color=#ff0000]Do not stream the current round to the Delta-V Discord.[/color]
|
||||
- Do not place players into cryosleep unless they have given consent to do so, they are fully catatonic, or they have been sentenced to preservative stasis. Always examine a character to double check if they are SSD or catatonic prior to placing them into cryosleep.
|
||||
[color=#a4885c]Game Server Rules:[/color]
|
||||
|
||||
[color=#a4885c]5.[/color] If a player dies and is brought back by way of cloning or borgification, they forget the last five minutes leading up to their death and cannot describe who or what killed them.
|
||||
[color=#a4885c]1.[/color] Follow the server expectations:
|
||||
- If you are banned from a role or department, you may not play that role. This includes seeking or accepting promotions into roles you are banned from.
|
||||
- Do not make yourself a major problem/annoyance/disruption for the crew while not being an antagonist (i.e. self-antagging). Intentionally hindering the station as a crew-aligned character is strictly against our rules.
|
||||
- Department strikes (e.g. cargonia and any variation thereof), riots, cults, and any other type of similar largely disruptive behavior are strictly forbidden without both excellent roleplay justification and administrator pre-approval.
|
||||
- AFK (a.k.a. SSD) and catatonic players are considered to have the same rights as any other crewmate.
|
||||
- If a player is dead and catatonic (not SSD), they are not required to be revived and may be stored in the morgue or otherwise handled by medical personnel.
|
||||
- If a player is alive and catatonic, they may be brought to their relevant department or cryostorage as necessary.
|
||||
|
||||
Players that are revived by using a defibrillator CAN recall what killed them and do not have any forgetfulness about what happened while they were alive.
|
||||
[color=#a4885c]2.[/color] Follow metagaming guidelines:
|
||||
- Do not use the Emote channel to bypass an inability to speak. This includes examples like "I’m friendly," "oh weird I can’t speak," or "mimes a flashlight".
|
||||
- Additionally, use of the emote channel to simulate character death rattles or other game mechanics is prohibited.
|
||||
- Use the local-out-of-character (LOOC) and global out-of-character (OOC) channels properly. Don’t speak of in-character matters in those channels unless you’re asking questions related to in-game concepts.
|
||||
- Do not use in-character channels to bypass a lack of ability to use OOC/LOOC chats (e.g. "Huh, wonder where the captain went? OOC He probably left to get dinner lol").
|
||||
- Do not engage in meta-communications (i.e. using external channels to communicate with other players in the same game).
|
||||
- Do not stream, discuss, or share information about the current round to the Delta-V Discord.
|
||||
- You are allowed to have knowledge of past experiences and prior shifts, and players are allowed to have in-character relationships (friends, enemies, or otherwise). However, this cannot be used as a sole reason to grant or deny items/accesses/favors based exclusively on having a relationship with one another (i.e. meta-friending/meta-grudging).
|
||||
- Antagonists in previous rounds must not be treated differently due to their prior antagonist status. Notwithstanding, confession of crimes committed in a prior shift are still admissible in court.
|
||||
- Additionally, and for the sake of continuity, your character will never have permanently died in previous rounds.
|
||||
- Do not "antag roll." This is the act of joining rounds for the purpose of seeing if you have received an antagonist role, and leaving soon after if not.
|
||||
|
||||
[color=#ff0000]Please report players who violate this rule.[/color]
|
||||
[color=#a4885c]3.[/color] Follow the new-life rules:
|
||||
- If a player dies and is brought back by way of cloning or borging, they forget the last five minutes leading up to their death and cannot describe who or what killed them.
|
||||
- Players that are revived by using a defibrillator CAN recall the circumstances of their death and do not have any forgetfulness/amnesia about what happened while they were conscious and awake.
|
||||
- In either case, characters do not have any knowledge of what happened while they were dead (i.e. ghosted/observing).
|
||||
|
||||
[color=#a4885c]6.[/color] All constructed/summoned beings that have laws are bound by those laws and must abide by them.
|
||||
- If a being has a master, they MUST follow their master's orders.
|
||||
- Your master is held accountable if they order you to do something malicious.
|
||||
*This does not apply to breaking server rules. If you are ordered to do something that would break a server rule, disregard it and inform an admin.
|
||||
- Not having orders, or being given free will by your master does NOT give you permission to self-antag or grief the crew.
|
||||
[color=#a4885c]4.[/color] Follow naming conventions:
|
||||
In-character names must fit the server standards of:
|
||||
- Doesn't make obvious references,
|
||||
- Isn't an obvious pun or play on words,
|
||||
- Could be feasibly be on a company-issued ID,
|
||||
OR
|
||||
- Is a result of random name generation.
|
||||
The only exception to the above is theatrical roles such as the clown, boxer, and mime; these roles are allowed stage names with some freedom, as long as it is not obscene. Administrators reserve the right to approve or deny character names at their discretion.
|
||||
|
||||
[color=#a4885c]7.[/color] Follow Naming Conventions
|
||||
- In-character names must fit the server standards of:
|
||||
* Doesn't make obvious references
|
||||
* Doesn't disturb roleplay
|
||||
OR
|
||||
* Is a result of random name generation.
|
||||
- The only exception to the above is that theatrical roles like clown, boxer, and mime are allowed stage names with some freedom, as long as it is not obscene.
|
||||
[color=#a4885c]5.[/color] Follow roleplay guidelines:
|
||||
- Your character is a separate entity from you, the player. Your character's actions, feelings, and knowledge in-game should be based solely on the character's experiences rather than your own as the player.
|
||||
- Low roleplay actions that have no regard for your character or little bearing on setting (such as memes, silly copy paste spam IC) are not acceptable. Your character is a worker aboard a space station and will be held accountable.
|
||||
- Do not escalate situations needlessly as a crew-aligned role. Very few things are deserving of a fight to the death.
|
||||
- If a fight results in someone being critically injured, seek medical help for them - and if they die, do not destroy or otherwise hide their body. This falls under self-antagonistic behavior.
|
||||
- Pest ghost roles such as mice are always fair game for attacking. Do not grief crew-aligned ghost roles such as familiars, drones, or pets without provocation.
|
||||
- Some awakened pest roles (e.g. sentient mothroach) may be considered crew pets and fall under this rule.
|
||||
- [color=#ff0000]Non-security personnel should not seek to kill or detain unrelated threats.[/color] Crew may neutralize threats when they present themselves or if they are relevant to the crew member in question.
|
||||
- Certain severe emergencies may make it reasonable for normal crew to defend themselves and their station, such as nuclear operatives, a zombie outbreak, or other critical situation.
|
||||
|
||||
[color=#a4885c]8.[/color] Follow Roleplay Guidelines
|
||||
- Treat your character as a separate entity from you, the player. Your character's actions, feelings, and knowledge in-game should be based solely on the character's experiences and not your own as the player. Low roleplay actions that have no regard for your character or the setting (Memes, silly copy paste spam IC) are not acceptable.
|
||||
- Character development can occur over rounds but each round is a soft-reset, meaning you can have previous shift experience but your character will never have died in the past.
|
||||
- Command and Security will be held to a higher standard for roleplay.
|
||||
- By picking prisoner, you have chosen to RP as a prisoner. You are still subject to rules pertaining to escalation, and should only seek to escape from the brig with good roleplay reasoning, such as abusive security, or a badly damaged perma. Escaping for no reason is considered a self-antag activity. If you are unsure whether your escape reason is valid, feel free to AHelp it first.
|
||||
[color=#a4885c]6.[/color] Follow powergaming guidelines:
|
||||
Don't rush for or prepare equipment unrelated to your job for no purpose other than to have it "just in case" and without valid reason.
|
||||
- A medical doctor does not need insulated gloves, the Head of Personnel does not need a gun, and the Captain does not need the Head of Security’s hardsuit. If you, in your given role, require an item that does not fall within your job’s usual parameters, have a valid reason to obtain and keep it.
|
||||
- Manufacturing weapons, bombs, or deadly poisons before you know of any reason you would need them is self-antagonistic behavior.
|
||||
- Hiding Traitor objective items or preemptively securing them with higher security than usually required or than would make logical sense violates our powergaming and roleplay guidelines.
|
||||
- Do not, [color=#ff0000]under any circumstances[/color], hide the nuclear fission explosive, authentication disk, or other sensitive items in an impossible to see/access location.
|
||||
|
||||
[color=#a4885c]9.[/color] Follow Escalation Guidelines
|
||||
- Do not escalate situations needlessly. Very few things are deserving of a fight to the death.
|
||||
- Antagonistic ghost roles, and pest ghost roles like mice are always fair game for attacking. Don't grief crew-aligned ghost roles like familiars, drones, or pets without provocation.
|
||||
- If a fight results in someone being critically injured, seek medical help for them. If they die, do not destroy or otherwise hide their body.
|
||||
[color=#a4885c]7.[/color] Follow end-of-round (EOR) rules:
|
||||
- Significant end-of-round grief (EORG) is not allowed. This includes attacking, destroying, polluting, and severely injuring without reason both at and on the way to Central Command.
|
||||
- Explosives and strategies with capacity for high collateral damage should not be used on or around the evacuation shuttle after it arrives.
|
||||
|
||||
[color=#a4885c]10.[/color] Follow Antagonist Guidelines
|
||||
- The damage antagonists cause should be roughly proportional to their objectives, and contribute towards achieving them in some way. However antagonists have leniency in regards to what they can and can’t do. If you are concerned as to whether or not what you're about to do is allowed, feel free to ahelp and ask an admin.
|
||||
- Other antagonists are not necessarily your friends. Traitors, rat kings, and possibly even space dragons are free agents, but no one should be working together with xenomorphs or zombies.
|
||||
- Exploits, arrivals camping, unnecessary round extensions, and other extremely lame behavior are forbidden.
|
||||
- Ghost roles have their own independent rules that must be followed. [color=#ff0000]Breaking these rules can result in a ban, whitelist removal, or both.[/color]
|
||||
[color=#a4885c]Command, Security, and Antagonist Rules:[/color]
|
||||
[color=#a4885c]C1.[/color] Follow Command and Security Guidelines:
|
||||
- Security and Command roles are held to a higher standard of roleplay, and are expected to know and perform their jobs to the best of their ability. These roles often heavily impact the round and require more careful and conscientious roleplay; if you cannot meet these requirements do not take these roles.
|
||||
- Both departments are required to read and follow Delta-V Space Law, Standard Operating Procedure, Alert Procedure, and Company Policy to the best of their ability.
|
||||
- Abandoning your role to go do whatever you want instead of managing your department or maintaining security, abusing your position or using it to make arbitrary or malicious choices to the detriment of the station, and being negligent to the point of harm is prohibited.
|
||||
- If you need to leave the round, please notify administrators via AHelp menu and notify your fellow crew via departmental radio. If you have the capability to do so, ensure that you return your items as necessary and seek cryostorage if you are not planning on returning.
|
||||
- Do not give up or trade away Traitor objective items, departmental/Command gear, and sensitive equipment without excellent reason.
|
||||
- Some leeway is given to making deals with criminals [italic]if and only if[/italic] the deal benefits the safety or situation of the station or circumstances rather than just yourself (e.g. hostage situations).
|
||||
- Some leeway can be given in extreme circumstances of extreme shortstaffing and/or emergency/crisis.
|
||||
- Both Command and Security are expected to uphold the law and maintain order aboard the station. Do not engage in lawbreaking activity or troublemaker behavior, and ensure that company policy and standard operating procedure - not just space law - is being observed. Security is expected to intervene into criminal activity where possible and safe to do so. Heads of departments are at minimum expected to report criminal activity to Security and should cooperate with law enforcement where possible.
|
||||
- Do not hire random crew to be your bodyguard(s) or promote random crewmembers to Command positions at random. If you require bodyguards or security details, talk to your Security department. If you need new Command staff, talk to the personnel in that related department.
|
||||
|
||||
[color=#a4885c]11.[/color] Psionics
|
||||
- Players that have psionic powers are allowed to use them at-will to accomplish their roleplay goals. It should be noted that in-character consequences can happen as a result of their use, including being stripped of psionic powers or even death.
|
||||
- As a psionic mantis, it is not your goal to hunt down psionics. Do not mindbreak others against their will solely because they have psionic powers.
|
||||
[color=#a4885c]C2.[/color] By picking the prisoner role, you have chosen to roleplay as a prisoner. You are still subject to the same rules regarding escalation, and should only seek to escape from the brig with excellent reasoning (e.g. abusive security personnel or badly damaged permanent brig). Escaping for no reason is considered a self-antagonistic activity. If you are unsure whether your escape reason is valid, feel free to AHelp it first. [color=#ff0000]Lack of administrator response does not constitute approval.[/color]
|
||||
|
||||
[color=#a4885c]12.[/color] Don't rush for or prepare equipment unrelated to your job for no purpose other than to have it "just in case" (referred to as "Powergaming").
|
||||
- A medical doctor does not need insulated gloves, and the Head of Personnel does not need to give themselves armory access so they can go grab a gun. Have an actual reason for needing these things.
|
||||
- Don't manufacture weapons, bombs, or deadly poisons before you know of any reason you would need them.
|
||||
- Don't preemptively hide antagonist objectives or preemptively secure them with higher security than normally required.
|
||||
- Don't manufacture or prepare things for the "end of the round" when the shuttle docks with Central Command.
|
||||
|
||||
[color=#ff0000]SECURITY & COMMAND RULES[/color]
|
||||
These rules apply to any individual who is promoted or is acting in the place of a Security/Command role (unless they are an antagonist).
|
||||
|
||||
[color=#a4885c]13.[/color] Security and command roles are held to a higher standard and must follow space law.
|
||||
- If you don’t have time to play a full round, do not select these roles.
|
||||
- If you need to leave your computer, send an ahelp or notify your fellow crew via the radio.
|
||||
- Be competent in your job and department. Failure to know the basics of your department is liable to result in a job ban.
|
||||
- Security and Command roles are forbidden from using a syndicate uplink to receive contraband without written permission from Central Command.
|
||||
- Do not give away your objective items (e.g. Captain’s equipment, Head of Personnel’s ID, etc.). Some leeway is given to making deals with criminals if the deal benefits the safety or situation of the station as a whole and not just yourself.
|
||||
- Uphold the Law & maintain order. Do not engage in lawbreaking activity or troublemaker behavior. Security is expected to intervene into criminal activity where possible. Heads of Staff are at minimum expected to report criminal activity to Security.
|
||||
- Do not immediately abandon your position as a Command role and go do whatever you want instead of managing your department/the station. Do not abuse your position or use it to make arbitrary choices to the detriment of the station.
|
||||
- Do not hire random crew to be your bodyguards or promote random crewmember to Captain or a Head of Staff at random. If you need bodyguards, talk to your security department. If you need a new Command role, talk to the personnel in that related department.
|
||||
- Do not abandon the station during Nuclear Operatives. You are supposed to protect the station, not let operatives kill everyone on it without a fight.
|
||||
|
||||
[color=#a4885c]14.[/color] Security (and Command where applicable) should try to remain non-lethal and effect arrests. Security/Command will be expected to answer for use of lethal force. Security/Command will be expected to effect arrests on criminals and prevent them from dying while in custody, even if lethal force is used.
|
||||
|
||||
In the following special circumstances, lethal force may be used by Security:
|
||||
- Lethal force is used against you (ex: firearms, lasers, disabling/stunning weapons with intent to kill, deadly melee weapons)
|
||||
- Suspect is equipped with dangerous equipment only used by enemy agents/antagonists and is not cuffed nor surrendering (ex: Bloodred Hardsuit, China Lake, C-20R, etc.).
|
||||
- You determine that your life or the life of another person is in immediate danger.
|
||||
- The suspect is unable to be safely detained by less-lethal means. This includes suspects who continually resist efforts to be cuffed or continuously manage to escape.
|
||||
- If no other reasonable options are readily available and allowing the suspect to continue would be an unreasonable danger to the station/crew.
|
||||
|
||||
[color=#a4885c]15.[/color] Security/Command are expected to protect detainees in their custody to the best of their ability so as long as it does not come to unreasonable risk to themselves, the crew, or the station at large to do so.
|
||||
- Brig times should generally not exceed 20 minutes unless the crime is permabriggable.
|
||||
- Security may choose to confiscate dangerous items (weapons, firearms) as well as items used to commit crimes or items that prove problematic in possession of the detainee (tools, insulated gloves, etc.).
|
||||
- Security may inspect PDAs of detainees and withhold them for the duration of detention, but can only confiscate them if they are obviously contraband. Suspicion alone is NOT sufficient for PDA confiscation by Security.
|
||||
- Security is prohibited from checking crewmates for implants without reasonable suspicion.
|
||||
- Detainees that die in your custody must be cloned unless they have been (legally) executed or have committed suicide.
|
||||
- Executions must be for a capital crime, used only as a last resort, and MUST be authorized by the highest ranking member of Security, who will answer to the use of execution.
|
||||
- Detainees in the brig have the right to know what they are being charged with.
|
||||
|
||||
[color=#a4885c]16.[/color] Command members besides the Logistics Officer are not permitted to leave the station on salvage expeditions.
|
||||
[color=#a4885c]C3.[/color] Follow antagonist guidelines:
|
||||
- The damage that antagonists can cause should be roughly proportional to their objectives, and contribute towards achieving them in some way. If you are concerned as to whether or not what you're about to do is allowed, feel free to AHelp and ask an admin for clarification. [color=#ff0000]Lack of administrator response does not constitute approval.[/color]
|
||||
- Antagonists exist not only as a role for your character but also as a game mechanic. Seek to push forward the round through your antagonistic actions, rather than grind it to halt in pursuit of your objectives.
|
||||
- Other antagonists are not necessarily your friends. Other agents, mercenaries, and possibly even space dragons are free agents that you may negotiate with at your own risk, but no one should be working together with xenomorphs or zombies.
|
||||
- Heavily damaging/spacing or camping arrivals/evac/cryo, unnecessarily extending the round, and behavior that calls the round to an end prematurely - as well as other lame behaviors - are strictly forbidden.
|
||||
- As a non- or partially-sentient antagonist, you are expected to have only a limited understanding of space stations and their mechanics. Slimes, zombies, and spiders should not be targeting the gravity generator or the AME, nor should they be unnecessarily spacing the station.
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 2.1 KiB |
|
|
@ -40,6 +40,18 @@
|
|||
{
|
||||
"name": "on-inhand-right",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "combat-equipped-HELMET-vulpkanin",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "on-equipped-HELMET-vulpkanin",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "off-equipped-HELMET-vulpkanin",
|
||||
"directions": 4
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
|
@ -37,6 +37,14 @@
|
|||
{
|
||||
"name": "on-inhand-right",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "off-equipped-HELMET-vulpkanin",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "on-equipped-HELMET-vulpkanin",
|
||||
"directions": 4
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
|
@ -37,6 +37,14 @@
|
|||
{
|
||||
"name": "on-inhand-right",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "off-equipped-HELMET-vulpkanin",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "on-equipped-HELMET-vulpkanin",
|
||||
"directions": 4
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 992 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.8 KiB |