Merge branch 'master' into mathmerge
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.GameObjects.Components.Movement;
|
||||
using Content.Shared.GameObjects.Components.Movement;
|
||||
@@ -19,23 +20,31 @@ namespace Content.Server.GameObjects.EntitySystems.AI
|
||||
[UsedImplicitly]
|
||||
internal class AiSystem : EntitySystem
|
||||
{
|
||||
#pragma warning disable 649
|
||||
[Dependency] private readonly IPauseManager _pauseManager;
|
||||
[Dependency] private readonly IDynamicTypeFactory _typeFactory;
|
||||
[Dependency] private readonly IReflectionManager _reflectionManager;
|
||||
#pragma warning restore 649
|
||||
[Dependency] private readonly IDynamicTypeFactory _typeFactory = default!;
|
||||
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
|
||||
|
||||
private readonly Dictionary<string, Type> _processorTypes = new Dictionary<string, Type>();
|
||||
|
||||
/// <summary>
|
||||
/// To avoid iterating over dead AI continuously they can wake and sleep themselves when necessary.
|
||||
/// </summary>
|
||||
private readonly HashSet<AiLogicProcessor> _awakeAi = new HashSet<AiLogicProcessor>();
|
||||
|
||||
// To avoid modifying awakeAi while iterating over it.
|
||||
private readonly List<SleepAiMessage> _queuedSleepMessages = new List<SleepAiMessage>();
|
||||
|
||||
public bool IsAwake(AiLogicProcessor processor) => _awakeAi.Contains(processor);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<SleepAiMessage>(HandleAiSleep);
|
||||
|
||||
var processors = _reflectionManager.GetAllChildren<AiLogicProcessor>();
|
||||
foreach (var processor in processors)
|
||||
{
|
||||
var att = (AiLogicProcessorAttribute)Attribute.GetCustomAttribute(processor, typeof(AiLogicProcessorAttribute));
|
||||
var att = (AiLogicProcessorAttribute) Attribute.GetCustomAttribute(processor, typeof(AiLogicProcessorAttribute))!;
|
||||
// Tests should pick this up
|
||||
DebugTools.AssertNotNull(att);
|
||||
_processorTypes.Add(att.SerializeName, processor);
|
||||
@@ -45,23 +54,35 @@ namespace Content.Server.GameObjects.EntitySystems.AI
|
||||
/// <inheritdoc />
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
foreach (var comp in ComponentManager.EntityQuery<AiControllerComponent>())
|
||||
foreach (var message in _queuedSleepMessages)
|
||||
{
|
||||
if (_pauseManager.IsEntityPaused(comp.Owner))
|
||||
switch (message.Sleep)
|
||||
{
|
||||
continue;
|
||||
case true:
|
||||
_awakeAi.Remove(message.Processor);
|
||||
break;
|
||||
case false:
|
||||
_awakeAi.Add(message.Processor);
|
||||
break;
|
||||
}
|
||||
|
||||
ProcessorInitialize(comp);
|
||||
|
||||
var processor = comp.Processor;
|
||||
|
||||
}
|
||||
|
||||
_queuedSleepMessages.Clear();
|
||||
|
||||
foreach (var processor in _awakeAi)
|
||||
{
|
||||
processor.Update(frameTime);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleAiSleep(SleepAiMessage message)
|
||||
{
|
||||
_queuedSleepMessages.Add(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Will start up the controller's processor if not already done so
|
||||
/// Will start up the controller's processor if not already done so.
|
||||
/// Also add them to the awakeAi for updates.
|
||||
/// </summary>
|
||||
/// <param name="controller"></param>
|
||||
public void ProcessorInitialize(AiControllerComponent controller)
|
||||
@@ -70,6 +91,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI
|
||||
controller.Processor = CreateProcessor(controller.LogicName);
|
||||
controller.Processor.SelfEntity = controller.Owner;
|
||||
controller.Processor.Setup();
|
||||
_awakeAi.Add(controller.Processor);
|
||||
}
|
||||
|
||||
private AiLogicProcessor CreateProcessor(string name)
|
||||
@@ -94,7 +116,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI
|
||||
+ "\n processorId: Class that inherits AiLogicProcessor and has an AiLogicProcessor attribute."
|
||||
+ "\n entityID: Uid of entity to add the AiControllerComponent to. Open its VV menu to find this.";
|
||||
|
||||
public void Execute(IConsoleShell shell, IPlayerSession player, string[] args)
|
||||
public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args)
|
||||
{
|
||||
if(args.Length != 2)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
using Robust.Server.AI;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Server.GameObjects.EntitySystems.AI
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates whether an AI should be updated by the AiSystem or not.
|
||||
/// Useful to sleep AI when they die or otherwise should be inactive.
|
||||
/// </summary>
|
||||
internal sealed class SleepAiMessage : EntitySystemMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// Sleep or awake.
|
||||
/// </summary>
|
||||
public bool Sleep { get; }
|
||||
public AiLogicProcessor Processor { get; }
|
||||
|
||||
public SleepAiMessage(AiLogicProcessor processor, bool sleep)
|
||||
{
|
||||
Processor = processor;
|
||||
Sleep = sleep;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Server.GameObjects.EntitySystems
|
||||
{
|
||||
/// <summary>
|
||||
/// This interface gives components behavior on getting destoyed.
|
||||
/// </summary>
|
||||
public interface IDestroyAct
|
||||
{
|
||||
/// <summary>
|
||||
/// Called when object is destroyed
|
||||
/// </summary>
|
||||
void OnDestroy(DestructionEventArgs eventArgs);
|
||||
}
|
||||
|
||||
public class DestructionEventArgs : EventArgs
|
||||
{
|
||||
public IEntity Owner { get; set; }
|
||||
public bool IsSpawnWreck { get; set; }
|
||||
}
|
||||
|
||||
public class BreakageEventArgs : EventArgs
|
||||
{
|
||||
public IEntity Owner { get; set; }
|
||||
}
|
||||
|
||||
public interface IBreakAct
|
||||
{
|
||||
/// <summary>
|
||||
/// Called when object is broken
|
||||
/// </summary>
|
||||
void OnBreak(BreakageEventArgs eventArgs);
|
||||
}
|
||||
|
||||
public interface IExAct
|
||||
{
|
||||
/// <summary>
|
||||
/// Called when explosion reaches the entity
|
||||
/// </summary>
|
||||
void OnExplosion(ExplosionEventArgs eventArgs);
|
||||
}
|
||||
|
||||
public class ExplosionEventArgs : EventArgs
|
||||
{
|
||||
public GridCoordinates Source { get; set; }
|
||||
public IEntity Target { get; set; }
|
||||
public ExplosionSeverity Severity { get; set; }
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class ActSystem : EntitySystem
|
||||
{
|
||||
public void HandleDestruction(IEntity owner, bool isWreck)
|
||||
{
|
||||
var eventArgs = new DestructionEventArgs
|
||||
{
|
||||
Owner = owner,
|
||||
IsSpawnWreck = isWreck
|
||||
};
|
||||
var destroyActs = owner.GetAllComponents<IDestroyAct>().ToList();
|
||||
|
||||
foreach (var destroyAct in destroyActs)
|
||||
{
|
||||
destroyAct.OnDestroy(eventArgs);
|
||||
}
|
||||
owner.Delete();
|
||||
}
|
||||
|
||||
public void HandleExplosion(GridCoordinates source, IEntity target, ExplosionSeverity severity)
|
||||
{
|
||||
var eventArgs = new ExplosionEventArgs
|
||||
{
|
||||
Source = source,
|
||||
Target = target,
|
||||
Severity = severity
|
||||
};
|
||||
var exActs = target.GetAllComponents<IExAct>().ToList();
|
||||
|
||||
foreach (var exAct in exActs)
|
||||
{
|
||||
exAct.OnExplosion(eventArgs);
|
||||
}
|
||||
}
|
||||
|
||||
public void HandleBreakage(IEntity owner)
|
||||
{
|
||||
var eventArgs = new BreakageEventArgs
|
||||
{
|
||||
Owner = owner,
|
||||
};
|
||||
var breakActs = owner.GetAllComponents<IBreakAct>().ToList();
|
||||
foreach (var breakAct in breakActs)
|
||||
{
|
||||
breakAct.OnBreak(eventArgs);
|
||||
}
|
||||
}
|
||||
}
|
||||
public enum ExplosionSeverity
|
||||
{
|
||||
Light,
|
||||
Heavy,
|
||||
Destruction,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,475 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Content.Server.GameObjects.Components.Atmos;
|
||||
using Content.Server.Interfaces.GameTicking;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.GameObjects.EntitySystems.Atmos;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.Interfaces.Player;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Interfaces.Configuration;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Map;
|
||||
using Robust.Shared.Interfaces.Timing;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.GameObjects.EntitySystems.Atmos
|
||||
{
|
||||
[UsedImplicitly]
|
||||
internal sealed class GasTileOverlaySystem : SharedGasTileOverlaySystem
|
||||
{
|
||||
[Robust.Shared.IoC.Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Robust.Shared.IoC.Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Robust.Shared.IoC.Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Robust.Shared.IoC.Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The tiles that have had their atmos data updated since last tick
|
||||
/// </summary>
|
||||
private Dictionary<GridId, HashSet<MapIndices>> _invalidTiles = new Dictionary<GridId, HashSet<MapIndices>>();
|
||||
|
||||
private Dictionary<IPlayerSession, PlayerGasOverlay> _knownPlayerChunks =
|
||||
new Dictionary<IPlayerSession, PlayerGasOverlay>();
|
||||
|
||||
/// <summary>
|
||||
/// Gas data stored in chunks to make PVS / bubbling easier.
|
||||
/// </summary>
|
||||
private Dictionary<GridId, Dictionary<MapIndices, GasOverlayChunk>> _overlay =
|
||||
new Dictionary<GridId, Dictionary<MapIndices, GasOverlayChunk>>();
|
||||
|
||||
/// <summary>
|
||||
/// How far away do we update gas overlays (minimum; due to chunking further away tiles may also be updated).
|
||||
/// </summary>
|
||||
private float _updateRange;
|
||||
// Because the gas overlay updates aren't run every tick we need to avoid the pop-in that might occur with
|
||||
// the regular PVS range.
|
||||
private const float RangeOffset = 6.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Overlay update ticks per second.
|
||||
/// </summary>
|
||||
private float _updateCooldown;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
|
||||
_mapManager.OnGridRemoved += OnGridRemoved;
|
||||
_configManager.RegisterCVar("net.gasoverlaytickrate", 3.0f);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_playerManager.PlayerStatusChanged -= OnPlayerStatusChanged;
|
||||
_mapManager.OnGridRemoved -= OnGridRemoved;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Invalidate(GridId gridIndex, MapIndices indices)
|
||||
{
|
||||
if (!_invalidTiles.TryGetValue(gridIndex, out var existing))
|
||||
{
|
||||
existing = new HashSet<MapIndices>();
|
||||
_invalidTiles[gridIndex] = existing;
|
||||
}
|
||||
|
||||
existing.Add(indices);
|
||||
}
|
||||
|
||||
private GasOverlayChunk GetOrCreateChunk(GridId gridIndex, MapIndices indices)
|
||||
{
|
||||
if (!_overlay.TryGetValue(gridIndex, out var chunks))
|
||||
{
|
||||
chunks = new Dictionary<MapIndices, GasOverlayChunk>();
|
||||
_overlay[gridIndex] = chunks;
|
||||
}
|
||||
|
||||
var chunkIndices = GetGasChunkIndices(indices);
|
||||
|
||||
if (!chunks.TryGetValue(chunkIndices, out var chunk))
|
||||
{
|
||||
chunk = new GasOverlayChunk(gridIndex, chunkIndices);
|
||||
chunks[chunkIndices] = chunk;
|
||||
}
|
||||
|
||||
return chunk;
|
||||
}
|
||||
|
||||
private void OnGridRemoved(GridId gridId)
|
||||
{
|
||||
if (_overlay.ContainsKey(gridId))
|
||||
{
|
||||
_overlay.Remove(gridId);
|
||||
}
|
||||
}
|
||||
|
||||
public void ResettingCleanup()
|
||||
{
|
||||
_invalidTiles.Clear();
|
||||
_overlay.Clear();
|
||||
|
||||
foreach (var (_, data) in _knownPlayerChunks)
|
||||
{
|
||||
data.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
|
||||
{
|
||||
if (e.NewStatus != SessionStatus.InGame)
|
||||
{
|
||||
if (_knownPlayerChunks.ContainsKey(e.Session))
|
||||
{
|
||||
_knownPlayerChunks.Remove(e.Session);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_knownPlayerChunks.ContainsKey(e.Session))
|
||||
{
|
||||
_knownPlayerChunks[e.Session] = new PlayerGasOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the overlay-relevant data for a gas tile has been updated.
|
||||
/// </summary>
|
||||
/// <param name="gam"></param>
|
||||
/// <param name="oldTile"></param>
|
||||
/// <param name="indices"></param>
|
||||
/// <param name="overlayData"></param>
|
||||
/// <returns>true if updated</returns>
|
||||
private bool TryRefreshTile(GridAtmosphereComponent gam, GasOverlayData oldTile, MapIndices indices, out GasOverlayData overlayData)
|
||||
{
|
||||
var tile = gam.GetTile(indices);
|
||||
var tileData = new List<GasData>();
|
||||
|
||||
for (byte i = 0; i < Atmospherics.TotalNumberOfGases; i++)
|
||||
{
|
||||
var gas = Atmospherics.GetGas(i);
|
||||
var overlay = Atmospherics.GetOverlay(i);
|
||||
if (overlay == null || tile.Air == null) continue;
|
||||
|
||||
var moles = tile.Air.Gases[i];
|
||||
|
||||
if (moles < gas.GasMolesVisible) continue;
|
||||
|
||||
var data = new GasData(i, (byte) (FloatMath.Clamp01(moles / gas.GasMolesVisibleMax) * 255));
|
||||
tileData.Add(data);
|
||||
}
|
||||
|
||||
overlayData = new GasOverlayData(tile.Hotspot.State, tile.Hotspot.Temperature, tileData.Count == 0 ? null : tileData.ToArray());
|
||||
|
||||
if (overlayData.Equals(oldTile))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get every chunk in range of our entity that exists, including on other grids.
|
||||
/// </summary>
|
||||
/// <param name="entity"></param>
|
||||
/// <returns></returns>
|
||||
private List<GasOverlayChunk> GetChunksInRange(IEntity entity)
|
||||
{
|
||||
var inRange = new List<GasOverlayChunk>();
|
||||
|
||||
// This is the max in any direction that we can get a chunk (e.g. max 2 chunks away of data).
|
||||
var (maxXDiff, maxYDiff) = ((int) (_updateRange / ChunkSize) + 1, (int) (_updateRange / ChunkSize) + 1);
|
||||
|
||||
var worldBounds = Box2.CenteredAround(entity.Transform.WorldPosition,
|
||||
new Vector2(_updateRange, _updateRange));
|
||||
|
||||
foreach (var grid in _mapManager.FindGridsIntersecting(entity.Transform.MapID, worldBounds))
|
||||
{
|
||||
if (!_overlay.TryGetValue(grid.Index, out var chunks))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var entityTile = grid.GetTileRef(entity.Transform.GridPosition).GridIndices;
|
||||
|
||||
for (var x = -maxXDiff; x <= maxXDiff; x++)
|
||||
{
|
||||
for (var y = -maxYDiff; y <= maxYDiff; y++)
|
||||
{
|
||||
var chunkIndices = GetGasChunkIndices(new MapIndices(entityTile.X + x * ChunkSize, entityTile.Y + y * ChunkSize));
|
||||
|
||||
if (!chunks.TryGetValue(chunkIndices, out var chunk)) continue;
|
||||
|
||||
// Now we'll check if it's in range and relevant for us
|
||||
// (e.g. if we're on the very edge of a chunk we may need more chunks).
|
||||
|
||||
var (xDiff, yDiff) = (chunkIndices.X - entityTile.X, chunkIndices.Y - entityTile.Y);
|
||||
if (xDiff > 0 && xDiff > _updateRange ||
|
||||
yDiff > 0 && yDiff > _updateRange ||
|
||||
xDiff < 0 && Math.Abs(xDiff + ChunkSize) > _updateRange ||
|
||||
yDiff < 0 && Math.Abs(yDiff + ChunkSize) > _updateRange) continue;
|
||||
|
||||
inRange.Add(chunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return inRange;
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
AccumulatedFrameTime += frameTime;
|
||||
_updateCooldown = 1 / _configManager.GetCVar<float>("net.gasoverlaytickrate");
|
||||
|
||||
if (AccumulatedFrameTime < _updateCooldown)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_updateRange = _configManager.GetCVar<float>("net.maxupdaterange") + RangeOffset;
|
||||
|
||||
// TODO: So in the worst case scenario we still have to send a LOT of tile data per tick if there's a fire.
|
||||
// If we go with say 15 tile radius then we have up to 900 tiles to update per tick.
|
||||
// In a saltern fire the worst you'll normally see is around 650 at the moment.
|
||||
// Need a way to fake this more because sending almost 2,000 tile updates per second to even 50 players is... yikes
|
||||
// I mean that's as big as it gets so larger maps will have the same but still, that's a lot of data.
|
||||
|
||||
// Some ways to do this are potentially: splitting fire and gas update data so they don't update at the same time
|
||||
// (gives the illusion of more updates happening), e.g. if gas updates are 3 times a second and fires are 1.6 times a second or something.
|
||||
// Could also look at updating tiles close to us more frequently (e.g. within 1 chunk every tick).
|
||||
// Stuff just out of our viewport we need so when we move it doesn't pop in but it doesn't mean we need to update it every tick.
|
||||
|
||||
AccumulatedFrameTime -= _updateCooldown;
|
||||
|
||||
var gridAtmosComponents = new Dictionary<GridId, GridAtmosphereComponent>();
|
||||
var updatedTiles = new Dictionary<GasOverlayChunk, HashSet<MapIndices>>();
|
||||
|
||||
// So up to this point we've been caching the updated tiles for multiple ticks.
|
||||
// Now we'll go through and check whether the update actually matters for the overlay or not,
|
||||
// and if not then we won't bother sending the data.
|
||||
foreach (var (gridId, indices) in _invalidTiles)
|
||||
{
|
||||
var gridEntityId = _mapManager.GetGrid(gridId).GridEntityId;
|
||||
|
||||
if (!EntityManager.GetEntity(gridEntityId).TryGetComponent(out GridAtmosphereComponent? gam))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// If it's being invalidated it should have this right?
|
||||
// At any rate we'll cache it for here + the AddChunk
|
||||
if (!gridAtmosComponents.ContainsKey(gridId))
|
||||
{
|
||||
gridAtmosComponents[gridId] = gam;
|
||||
}
|
||||
|
||||
foreach (var invalid in indices)
|
||||
{
|
||||
var chunk = GetOrCreateChunk(gridId, invalid);
|
||||
|
||||
if (!TryRefreshTile(gam, chunk.GetData(invalid), invalid, out var data)) continue;
|
||||
|
||||
if (!updatedTiles.TryGetValue(chunk, out var tiles))
|
||||
{
|
||||
tiles = new HashSet<MapIndices>();
|
||||
updatedTiles[chunk] = tiles;
|
||||
}
|
||||
|
||||
updatedTiles[chunk].Add(invalid);
|
||||
chunk.Update(data, invalid);
|
||||
}
|
||||
}
|
||||
|
||||
var currentTick = _gameTiming.CurTick;
|
||||
|
||||
// Set the LastUpdate for chunks.
|
||||
foreach (var (chunk, _) in updatedTiles)
|
||||
{
|
||||
chunk.Dirty(currentTick);
|
||||
}
|
||||
|
||||
// Now we'll go through each player, then through each chunk in range of that player checking if the player is still in range
|
||||
// If they are, check if they need the new data to send (i.e. if there's an overlay for the gas).
|
||||
// Afterwards we reset all the chunk data for the next time we tick.
|
||||
foreach (var (session, overlay) in _knownPlayerChunks)
|
||||
{
|
||||
if (session.AttachedEntity == null) continue;
|
||||
|
||||
// Get chunks in range and update if we've moved around or the chunks have new overlay data
|
||||
var chunksInRange = GetChunksInRange(session.AttachedEntity);
|
||||
var knownChunks = overlay.GetKnownChunks();
|
||||
var chunksToRemove = new List<GasOverlayChunk>();
|
||||
var chunksToAdd = new List<GasOverlayChunk>();
|
||||
|
||||
foreach (var chunk in chunksInRange)
|
||||
{
|
||||
if (!knownChunks.Contains(chunk))
|
||||
{
|
||||
chunksToAdd.Add(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var chunk in knownChunks)
|
||||
{
|
||||
if (!chunksInRange.Contains(chunk))
|
||||
{
|
||||
chunksToRemove.Add(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var chunk in chunksToAdd)
|
||||
{
|
||||
var message = overlay.AddChunk(currentTick, chunk);
|
||||
if (message != null)
|
||||
{
|
||||
RaiseNetworkEvent(message, session.ConnectedClient);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var chunk in chunksToRemove)
|
||||
{
|
||||
overlay.RemoveChunk(chunk);
|
||||
}
|
||||
|
||||
var clientInvalids = new Dictionary<GridId, List<(MapIndices, GasOverlayData)>>();
|
||||
|
||||
// Check for any dirty chunks in range and bundle the data to send to the client.
|
||||
foreach (var chunk in chunksInRange)
|
||||
{
|
||||
if (!updatedTiles.TryGetValue(chunk, out var invalids)) continue;
|
||||
|
||||
if (!clientInvalids.TryGetValue(chunk.GridIndices, out var existingData))
|
||||
{
|
||||
existingData = new List<(MapIndices, GasOverlayData)>();
|
||||
clientInvalids[chunk.GridIndices] = existingData;
|
||||
}
|
||||
|
||||
chunk.GetData(existingData, invalids);
|
||||
}
|
||||
|
||||
foreach (var (grid, data) in clientInvalids)
|
||||
{
|
||||
RaiseNetworkEvent(overlay.UpdateClient(grid, data), session.ConnectedClient);
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
_invalidTiles.Clear();
|
||||
}
|
||||
private sealed class PlayerGasOverlay
|
||||
{
|
||||
private readonly Dictionary<GridId, Dictionary<MapIndices, GasOverlayChunk>> _data =
|
||||
new Dictionary<GridId, Dictionary<MapIndices, GasOverlayChunk>>();
|
||||
|
||||
private readonly Dictionary<GasOverlayChunk, GameTick> _lastSent =
|
||||
new Dictionary<GasOverlayChunk, GameTick>();
|
||||
|
||||
public GasOverlayMessage UpdateClient(GridId grid, List<(MapIndices, GasOverlayData)> data)
|
||||
{
|
||||
return new GasOverlayMessage(grid, data);
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_data.Clear();
|
||||
_lastSent.Clear();
|
||||
}
|
||||
|
||||
public List<GasOverlayChunk> GetKnownChunks()
|
||||
{
|
||||
var known = new List<GasOverlayChunk>();
|
||||
|
||||
foreach (var (_, chunks) in _data)
|
||||
{
|
||||
foreach (var (_, chunk) in chunks)
|
||||
{
|
||||
known.Add(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
return known;
|
||||
}
|
||||
|
||||
public GasOverlayMessage? AddChunk(GameTick currentTick, GasOverlayChunk chunk)
|
||||
{
|
||||
if (!_data.TryGetValue(chunk.GridIndices, out var chunks))
|
||||
{
|
||||
chunks = new Dictionary<MapIndices, GasOverlayChunk>();
|
||||
_data[chunk.GridIndices] = chunks;
|
||||
}
|
||||
|
||||
if (_lastSent.TryGetValue(chunk, out var last) && last >= chunk.LastUpdate)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
_lastSent[chunk] = currentTick;
|
||||
var message = ChunkToMessage(chunk);
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
public void RemoveChunk(GasOverlayChunk chunk)
|
||||
{
|
||||
// Don't need to sync to client as they can manage it themself.
|
||||
if (!_data.TryGetValue(chunk.GridIndices, out var chunks))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (chunks.ContainsKey(chunk.MapIndices))
|
||||
{
|
||||
chunks.Remove(chunk.MapIndices);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve a whole chunk as a message, only getting the relevant tiles for the gas overlay.
|
||||
/// </summary>
|
||||
/// <param name="chunk"></param>
|
||||
/// <returns></returns>
|
||||
private GasOverlayMessage? ChunkToMessage(GasOverlayChunk chunk)
|
||||
{
|
||||
// Chunk data should already be up to date.
|
||||
// Only send relevant tiles to client.
|
||||
|
||||
var tileData = new List<(MapIndices, GasOverlayData)>();
|
||||
|
||||
for (var x = 0; x < ChunkSize; x++)
|
||||
{
|
||||
for (var y = 0; y < ChunkSize; y++)
|
||||
{
|
||||
// TODO: Check could be more robust I think.
|
||||
var data = chunk.TileData[x, y];
|
||||
if ((data.Gas == null || data.Gas.Length == 0) && data.FireState == 0 && data.FireTemperature == 0.0f)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var indices = new MapIndices(chunk.MapIndices.X + x, chunk.MapIndices.Y + y);
|
||||
tileData.Add((indices, data));
|
||||
}
|
||||
}
|
||||
|
||||
if (tileData.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new GasOverlayMessage(chunk.GridIndices, tileData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ namespace Content.Server.GameObjects.EntitySystems
|
||||
|
||||
if (!EntityManager.TryGetEntity(grid.GridEntityId, out var gridEnt)) return null;
|
||||
|
||||
return gridEnt.TryGetComponent(out IGridAtmosphereComponent atmos) ? atmos : null;
|
||||
return gridEnt.TryGetComponent(out IGridAtmosphereComponent? atmos) ? atmos : null;
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
using Content.Server.GameObjects.Components.Metabolism;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
|
||||
namespace Content.Server.GameObjects.EntitySystems
|
||||
{
|
||||
/// <summary>
|
||||
/// Triggers metabolism updates for <see cref="BloodstreamComponent"/>
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
internal sealed class BloodstreamSystem : EntitySystem
|
||||
{
|
||||
private float _accumulatedFrameTime;
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
//Trigger metabolism updates at most once per second
|
||||
_accumulatedFrameTime += frameTime;
|
||||
if (_accumulatedFrameTime > 1.0f)
|
||||
{
|
||||
foreach (var component in ComponentManager.EntityQuery<BloodstreamComponent>())
|
||||
{
|
||||
component.OnUpdate(_accumulatedFrameTime);
|
||||
}
|
||||
_accumulatedFrameTime -= 1.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
29
Content.Server/GameObjects/EntitySystems/BodySystem.cs
Normal file
29
Content.Server/GameObjects/EntitySystems/BodySystem.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using Content.Server.GameObjects.Components.Body;
|
||||
using Content.Server.GameObjects.Components.Metabolism;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
|
||||
namespace Content.Server.GameObjects.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class BodySystem : EntitySystem
|
||||
{
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
foreach (var body in ComponentManager.EntityQuery<BodyManagerComponent>())
|
||||
{
|
||||
body.PreMetabolism(frameTime);
|
||||
}
|
||||
|
||||
foreach (var metabolism in ComponentManager.EntityQuery<MetabolismComponent>())
|
||||
{
|
||||
metabolism.Update(frameTime);
|
||||
}
|
||||
|
||||
foreach (var body in ComponentManager.EntityQuery<BodyManagerComponent>())
|
||||
{
|
||||
body.PostMetabolism(frameTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.GameObjects.Components.GUI;
|
||||
using Content.Server.GameObjects.Components.Mobs;
|
||||
using Content.Server.GameObjects.Components.Movement;
|
||||
@@ -12,6 +13,7 @@ using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.Input;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Content.Shared.Physics;
|
||||
using Content.Shared.Physics.Pull;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Interfaces.Player;
|
||||
@@ -437,7 +439,7 @@ namespace Content.Server.GameObjects.EntitySystems.Click
|
||||
/// Uses a weapon/object on an entity
|
||||
/// Finds components with the InteractUsing interface and calls their function
|
||||
/// </summary>
|
||||
public void Interaction(IEntity user, IEntity weapon, IEntity attacked, GridCoordinates clickLocation)
|
||||
public async Task Interaction(IEntity user, IEntity weapon, IEntity attacked, GridCoordinates clickLocation)
|
||||
{
|
||||
var attackMsg = new InteractUsingMessage(user, weapon, attacked, clickLocation);
|
||||
RaiseLocalEvent(attackMsg);
|
||||
@@ -446,7 +448,7 @@ namespace Content.Server.GameObjects.EntitySystems.Click
|
||||
return;
|
||||
}
|
||||
|
||||
var attackBys = attacked.GetAllComponents<IInteractUsing>().ToList();
|
||||
var attackBys = attacked.GetAllComponents<IInteractUsing>().OrderByDescending(x => x.Priority);
|
||||
var attackByEventArgs = new InteractUsingEventArgs
|
||||
{
|
||||
User = user, ClickLocation = clickLocation, Using = weapon, Target = attacked
|
||||
@@ -457,7 +459,7 @@ namespace Content.Server.GameObjects.EntitySystems.Click
|
||||
{
|
||||
foreach (var attackBy in attackBys)
|
||||
{
|
||||
if (attackBy.InteractUsing(attackByEventArgs))
|
||||
if (await attackBy.InteractUsing(attackByEventArgs))
|
||||
{
|
||||
// If an InteractUsing returns a status completion we finish our attack
|
||||
return;
|
||||
|
||||
18
Content.Server/GameObjects/EntitySystems/ClimbSystem.cs
Normal file
18
Content.Server/GameObjects/EntitySystems/ClimbSystem.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using Content.Server.GameObjects.Components.Movement;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
|
||||
namespace Content.Server.GameObjects.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
internal sealed class ClimbSystem : EntitySystem
|
||||
{
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
foreach (var comp in ComponentManager.EntityQuery<ClimbingComponent>())
|
||||
{
|
||||
comp.Update(frameTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
24
Content.Server/GameObjects/EntitySystems/CloningSystem.cs
Normal file
24
Content.Server/GameObjects/EntitySystems/CloningSystem.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
|
||||
namespace Content.Server.GameObjects.EntitySystems
|
||||
{
|
||||
internal sealed class CloningSystem : EntitySystem
|
||||
{
|
||||
public static List<EntityUid> scannedUids = new List<EntityUid>();
|
||||
|
||||
public static void AddToScannedUids(EntityUid uid)
|
||||
{
|
||||
if (!scannedUids.Contains(uid))
|
||||
{
|
||||
scannedUids.Add(uid);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool HasUid(EntityUid uid)
|
||||
{
|
||||
return scannedUids.Contains(uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.GameObjects.Components.Construction;
|
||||
using Content.Server.GameObjects.Components.GUI;
|
||||
using Content.Server.GameObjects.Components.Interactable;
|
||||
@@ -76,7 +77,7 @@ namespace Content.Server.GameObjects.EntitySystems
|
||||
TryStartItemConstruction(placingEnt, msg.PrototypeName);
|
||||
}
|
||||
|
||||
private void HandleToolInteraction(AfterInteractMessage msg)
|
||||
private async void HandleToolInteraction(AfterInteractMessage msg)
|
||||
{
|
||||
if(msg.Handled)
|
||||
return;
|
||||
@@ -104,7 +105,7 @@ namespace Content.Server.GameObjects.EntitySystems
|
||||
// the target entity is in the process of being constructed/deconstructed
|
||||
if (msg.Attacked.TryGetComponent<ConstructionComponent>(out var constructComp))
|
||||
{
|
||||
var result = TryConstructEntity(constructComp, handEnt, msg.User);
|
||||
var result = await TryConstructEntity(constructComp, handEnt, msg.User);
|
||||
|
||||
// TryConstructEntity may delete the existing entity
|
||||
|
||||
@@ -367,7 +368,7 @@ namespace Content.Server.GameObjects.EntitySystems
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryConstructEntity(ConstructionComponent constructionComponent, IEntity handTool, IEntity user)
|
||||
private async Task<bool> TryConstructEntity(ConstructionComponent constructionComponent, IEntity handTool, IEntity user)
|
||||
{
|
||||
var constructEntity = constructionComponent.Owner;
|
||||
var spriteComponent = constructEntity.GetComponent<SpriteComponent>();
|
||||
@@ -384,7 +385,7 @@ namespace Content.Server.GameObjects.EntitySystems
|
||||
|
||||
var stage = constructPrototype.Stages[constructionComponent.Stage];
|
||||
|
||||
if (TryProcessStep(constructEntity, stage.Forward, handTool, user, transformComponent.GridPosition))
|
||||
if (await TryProcessStep(constructEntity, stage.Forward, handTool, user, transformComponent.GridPosition))
|
||||
{
|
||||
constructionComponent.Stage++;
|
||||
if (constructionComponent.Stage == constructPrototype.Stages.Count - 1)
|
||||
@@ -406,7 +407,7 @@ namespace Content.Server.GameObjects.EntitySystems
|
||||
}
|
||||
}
|
||||
|
||||
else if (TryProcessStep(constructEntity, stage.Backward, handTool, user, transformComponent.GridPosition))
|
||||
else if (await TryProcessStep(constructEntity, stage.Backward, handTool, user, transformComponent.GridPosition))
|
||||
{
|
||||
constructionComponent.Stage--;
|
||||
stage = constructPrototype.Stages[constructionComponent.Stage];
|
||||
@@ -443,7 +444,7 @@ namespace Content.Server.GameObjects.EntitySystems
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryProcessStep(IEntity constructEntity, ConstructionStep step, IEntity slapped, IEntity user, GridCoordinates gridCoords)
|
||||
private async Task<bool> TryProcessStep(IEntity constructEntity, ConstructionStep step, IEntity slapped, IEntity user, GridCoordinates gridCoords)
|
||||
{
|
||||
if (step == null)
|
||||
{
|
||||
@@ -473,9 +474,9 @@ namespace Content.Server.GameObjects.EntitySystems
|
||||
// Handle welder manually since tool steps specify fuel amount needed, for some reason.
|
||||
if (toolStep.ToolQuality.HasFlag(ToolQuality.Welding))
|
||||
return slapped.TryGetComponent<WelderComponent>(out var welder)
|
||||
&& welder.UseTool(user, constructEntity, toolStep.ToolQuality, toolStep.Amount);
|
||||
&& await welder.UseTool(user, constructEntity, toolStep.DoAfterDelay, toolStep.ToolQuality, toolStep.Amount);
|
||||
|
||||
return tool.UseTool(user, constructEntity, toolStep.ToolQuality);
|
||||
return await tool.UseTool(user, constructEntity, toolStep.DoAfterDelay, toolStep.ToolQuality);
|
||||
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
|
||||
@@ -5,6 +5,7 @@ using Content.Server.GameObjects.Components.Damage;
|
||||
using Content.Server.GameObjects.Components.GUI;
|
||||
using Content.Server.GameObjects.Components.Items.Storage;
|
||||
using Content.Server.GameObjects.Components.Mobs;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Robust.Shared.Interfaces.Timing;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
@@ -53,7 +54,7 @@ namespace Content.Server.GameObjects.EntitySystems.DoAfter
|
||||
|
||||
// For this we need to stay on the same hand slot and need the same item in that hand slot
|
||||
// (or if there is no item there we need to keep it free).
|
||||
if (eventArgs.NeedHand && eventArgs.User.TryGetComponent(out HandsComponent handsComponent))
|
||||
if (eventArgs.NeedHand && eventArgs.User.TryGetComponent(out HandsComponent? handsComponent))
|
||||
{
|
||||
_activeHand = handsComponent.ActiveHand;
|
||||
_activeItem = handsComponent.GetActiveHand;
|
||||
@@ -63,7 +64,7 @@ namespace Content.Server.GameObjects.EntitySystems.DoAfter
|
||||
AsTask = Tcs.Task;
|
||||
}
|
||||
|
||||
public void HandleDamage(object? sender, DamageEventArgs eventArgs)
|
||||
public void HandleDamage(HealthChangedEventArgs args)
|
||||
{
|
||||
_tookDamage = true;
|
||||
}
|
||||
@@ -125,7 +126,7 @@ namespace Content.Server.GameObjects.EntitySystems.DoAfter
|
||||
}
|
||||
|
||||
if (EventArgs.BreakOnStun &&
|
||||
EventArgs.User.TryGetComponent(out StunnableComponent stunnableComponent) &&
|
||||
EventArgs.User.TryGetComponent(out StunnableComponent? stunnableComponent) &&
|
||||
stunnableComponent.Stunned)
|
||||
{
|
||||
return true;
|
||||
@@ -133,7 +134,7 @@ namespace Content.Server.GameObjects.EntitySystems.DoAfter
|
||||
|
||||
if (EventArgs.NeedHand)
|
||||
{
|
||||
if (!EventArgs.User.TryGetComponent(out HandsComponent handsComponent))
|
||||
if (!EventArgs.User.TryGetComponent(out HandsComponent? handsComponent))
|
||||
{
|
||||
// If we had a hand but no longer have it that's still a paddlin'
|
||||
if (_activeHand != null)
|
||||
|
||||
@@ -3,7 +3,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.GameObjects.Components;
|
||||
using Content.Server.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.Interfaces.Timing;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
@@ -73,19 +73,19 @@ namespace Content.Server.GameObjects.EntitySystems.DoAfter
|
||||
// Caller's gonna be responsible for this I guess
|
||||
var doAfterComponent = eventArgs.User.GetComponent<DoAfterComponent>();
|
||||
doAfterComponent.Add(doAfter);
|
||||
DamageableComponent? damageableComponent = null;
|
||||
IDamageableComponent? damageableComponent = null;
|
||||
|
||||
// TODO: If the component's deleted this may not get unsubscribed?
|
||||
if (eventArgs.BreakOnDamage && eventArgs.User.TryGetComponent(out damageableComponent))
|
||||
{
|
||||
damageableComponent.Damaged += doAfter.HandleDamage;
|
||||
damageableComponent.HealthChangedEvent += doAfter.HandleDamage;
|
||||
}
|
||||
|
||||
await doAfter.AsTask;
|
||||
|
||||
if (damageableComponent != null)
|
||||
{
|
||||
damageableComponent.Damaged -= doAfter.HandleDamage;
|
||||
damageableComponent.HealthChangedEvent -= doAfter.HandleDamage;
|
||||
}
|
||||
|
||||
return doAfter.Status;
|
||||
|
||||
@@ -1,145 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Content.Server.GameObjects.Components.Atmos;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.Interfaces.Player;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Map;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Server.GameObjects.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class GasTileOverlaySystem : SharedGasTileOverlaySystem
|
||||
{
|
||||
private int _tickTimer = 0;
|
||||
private HashSet<GasTileOverlayData> _queue = new HashSet<GasTileOverlayData>();
|
||||
private Dictionary<GridId, HashSet<MapIndices>> _invalid = new Dictionary<GridId, HashSet<MapIndices>>();
|
||||
|
||||
private Dictionary<GridId, Dictionary<MapIndices, GasOverlayData>> _overlay =
|
||||
new Dictionary<GridId, Dictionary<MapIndices, GasOverlayData>>();
|
||||
|
||||
[Robust.Shared.IoC.Dependency] private IPlayerManager _playerManager = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Invalidate(GridId gridIndex, MapIndices indices)
|
||||
{
|
||||
if (!_invalid.TryGetValue(gridIndex, out var set) || set == null)
|
||||
{
|
||||
set = new HashSet<MapIndices>();
|
||||
_invalid.Add(gridIndex, set);
|
||||
}
|
||||
|
||||
set.Add(indices);
|
||||
}
|
||||
|
||||
public void SetTileOverlay(GridId gridIndex, MapIndices indices, GasData[] gasData, int fireState = 0, float fireTemperature = 0f)
|
||||
{
|
||||
if(!_overlay.TryGetValue(gridIndex, out var _))
|
||||
_overlay[gridIndex] = new Dictionary<MapIndices, GasOverlayData>();
|
||||
|
||||
_overlay[gridIndex][indices] = new GasOverlayData(fireState, fireTemperature, gasData);
|
||||
_queue.Add(GetData(gridIndex, indices));
|
||||
}
|
||||
|
||||
private void OnPlayerStatusChanged(object sender, SessionStatusEventArgs e)
|
||||
{
|
||||
if (e.NewStatus != SessionStatus.InGame) return;
|
||||
|
||||
RaiseNetworkEvent(new GasTileOverlayMessage(GetData(), true), e.Session.ConnectedClient);
|
||||
}
|
||||
|
||||
private GasTileOverlayData[] GetData()
|
||||
{
|
||||
var list = new List<GasTileOverlayData>();
|
||||
|
||||
foreach (var (gridId, tiles) in _overlay)
|
||||
{
|
||||
foreach (var (indices, _) in tiles)
|
||||
{
|
||||
var data = GetData(gridId, indices);
|
||||
if(data.Data.Gas.Length > 0)
|
||||
list.Add(data);
|
||||
}
|
||||
}
|
||||
|
||||
return list.ToArray();
|
||||
}
|
||||
|
||||
private GasTileOverlayData GetData(GridId gridIndex, MapIndices indices)
|
||||
{
|
||||
return new GasTileOverlayData(gridIndex, indices, _overlay[gridIndex][indices]);
|
||||
}
|
||||
|
||||
private void Revalidate()
|
||||
{
|
||||
var mapMan = IoCManager.Resolve<IMapManager>();
|
||||
var entityMan = IoCManager.Resolve<IEntityManager>();
|
||||
var list = new List<GasData>();
|
||||
|
||||
foreach (var (gridId, indices) in _invalid)
|
||||
{
|
||||
if (!mapMan.GridExists(gridId))
|
||||
{
|
||||
_invalid.Remove(gridId);
|
||||
return;
|
||||
}
|
||||
var grid = entityMan.GetEntity(mapMan.GetGrid(gridId).GridEntityId);
|
||||
if (!grid.TryGetComponent(out GridAtmosphereComponent gam)) continue;
|
||||
|
||||
foreach (var index in indices)
|
||||
{
|
||||
var tile = gam.GetTile(index);
|
||||
|
||||
if (tile?.Air == null) continue;
|
||||
|
||||
list.Clear();
|
||||
|
||||
for(var i = 0; i < Atmospherics.TotalNumberOfGases; i++)
|
||||
{
|
||||
var gas = Atmospherics.GetGas(i);
|
||||
var overlay = gas.GasOverlay;
|
||||
if (overlay == null) continue;
|
||||
var moles = tile.Air.Gases[i];
|
||||
if(moles == 0f || moles < gas.GasMolesVisible) continue;
|
||||
list.Add(new GasData(i, MathF.Max(MathF.Min(1, moles / gas.GasMolesVisibleMax), 0f)));
|
||||
}
|
||||
|
||||
if (list.Count == 0) continue;
|
||||
|
||||
SetTileOverlay(gridId, index, list.ToArray(), tile.Hotspot.State, tile.Hotspot.Temperature);
|
||||
}
|
||||
|
||||
indices.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
_tickTimer++;
|
||||
|
||||
Revalidate();
|
||||
|
||||
if (_tickTimer < 10) return;
|
||||
|
||||
_tickTimer = 0;
|
||||
if(_queue.Count > 0)
|
||||
RaiseNetworkEvent(new GasTileOverlayMessage(_queue.ToArray()));
|
||||
_queue.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
20
Content.Server/GameObjects/EntitySystems/GasVaporSystem.cs
Normal file
20
Content.Server/GameObjects/EntitySystems/GasVaporSystem.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using Content.Server.Atmos;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
|
||||
namespace Content.Server.GameObjects.EntitySystems
|
||||
{
|
||||
public class GasVaporSystem : EntitySystem
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
foreach (var GasVapor in ComponentManager.EntityQuery<GasVaporComponent>())
|
||||
{
|
||||
if (GasVapor.Initialized)
|
||||
{
|
||||
GasVapor.Update(frameTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,20 +5,21 @@ using Robust.Shared.GameObjects.Systems;
|
||||
namespace Content.Server.GameObjects.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
internal sealed class HungerSystem : EntitySystem
|
||||
public class HungerSystem : EntitySystem
|
||||
{
|
||||
private float _accumulatedFrameTime;
|
||||
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
_accumulatedFrameTime += frameTime;
|
||||
if (_accumulatedFrameTime > 1.0f)
|
||||
|
||||
if (_accumulatedFrameTime > 1)
|
||||
{
|
||||
foreach (var comp in ComponentManager.EntityQuery<HungerComponent>())
|
||||
{
|
||||
comp.OnUpdate(_accumulatedFrameTime);
|
||||
}
|
||||
_accumulatedFrameTime -= 1.0f;
|
||||
_accumulatedFrameTime -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace Content.Server.GameObjects.EntitySystems
|
||||
[UsedImplicitly]
|
||||
internal sealed class MedicalScannerSystem : EntitySystem
|
||||
{
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
foreach (var comp in ComponentManager.EntityQuery<MedicalScannerComponent>())
|
||||
|
||||
@@ -83,7 +83,7 @@ namespace Content.Server.GameObjects.EntitySystems
|
||||
ev.Entity.RemoveComponent<PlayerInputMoverComponent>();
|
||||
}
|
||||
|
||||
if (ev.Entity.TryGetComponent(out ICollidableComponent physics) &&
|
||||
if (ev.Entity.TryGetComponent(out ICollidableComponent? physics) &&
|
||||
physics.TryGetController(out MoverController controller))
|
||||
{
|
||||
controller.StopMoving();
|
||||
|
||||
@@ -6,6 +6,7 @@ using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.Input;
|
||||
using Content.Shared.Interfaces;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects.Components;
|
||||
using Robust.Server.Interfaces.Player;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Enums;
|
||||
@@ -113,7 +114,13 @@ namespace Content.Server.GameObjects.EntitySystems
|
||||
|
||||
var viewers = _playerManager.GetPlayersInRange(player.Transform.GridPosition, 15);
|
||||
|
||||
EntityManager.SpawnEntity("pointingarrow", coords);
|
||||
var arrow = EntityManager.SpawnEntity("pointingarrow", coords);
|
||||
|
||||
if (player.TryGetComponent(out VisibilityComponent? playerVisibility))
|
||||
{
|
||||
var arrowVisibility = arrow.EnsureComponent<VisibilityComponent>();
|
||||
arrowVisibility.Layer = playerVisibility.Layer;
|
||||
}
|
||||
|
||||
string selfMessage;
|
||||
string viewerMessage;
|
||||
|
||||
@@ -2,7 +2,9 @@ using System.Collections.Generic;
|
||||
using Content.Server.GameObjects.Components.Damage;
|
||||
using Content.Server.GameObjects.Components.Mobs;
|
||||
using Content.Server.GameObjects.Components.StationEvents;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.GameObjects;
|
||||
using Content.Shared.GameObjects.Components.Body;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -28,13 +30,13 @@ namespace Content.Server.GameObjects.EntitySystems.StationEvents
|
||||
private const int DamageThreshold = 10;
|
||||
|
||||
private Dictionary<IEntity, float> _accumulatedDamage = new Dictionary<IEntity, float>();
|
||||
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_speciesQuery = new TypeEntityQuery(typeof(SpeciesComponent));
|
||||
_speciesQuery = new TypeEntityQuery(typeof(IBodyManagerComponent));
|
||||
}
|
||||
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
@@ -72,7 +74,7 @@ namespace Content.Server.GameObjects.EntitySystems.StationEvents
|
||||
var damageMultiple = (int) (totalDamage / DamageThreshold);
|
||||
_accumulatedDamage[species] = totalDamage % DamageThreshold;
|
||||
|
||||
damageableComponent.TakeDamage(DamageType.Heat, damageMultiple * DamageThreshold, comp.Owner, comp.Owner);
|
||||
damageableComponent.ChangeDamage(DamageType.Heat, damageMultiple * DamageThreshold, false, comp.Owner);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,10 +82,10 @@ namespace Content.Server.GameObjects.EntitySystems.StationEvents
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// probably don't need to worry about clearing this at roundreset unless you have a radiation pulse at roundstart
|
||||
// (which is currently not possible)
|
||||
_accumulatedDamage.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,25 +2,34 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Content.Server.StationEvents;
|
||||
using Content.Server.Interfaces.GameTicking;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.Console;
|
||||
using Robust.Server.Interfaces.Player;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.Network;
|
||||
using Robust.Shared.Interfaces.Random;
|
||||
using Robust.Shared.Interfaces.Reflection;
|
||||
using Robust.Shared.Interfaces.Timing;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using static Content.Shared.StationEvents.SharedStationEvent;
|
||||
|
||||
namespace Content.Server.GameObjects.EntitySystems.StationEvents
|
||||
{
|
||||
[UsedImplicitly]
|
||||
// Somewhat based off of TG's implementation of events
|
||||
public sealed class StationEventSystem : EntitySystem
|
||||
{
|
||||
// Somewhat based off of TG's implementation of events
|
||||
|
||||
public StationEvent CurrentEvent { get; private set; }
|
||||
#pragma warning disable 649
|
||||
[Dependency] private readonly IServerNetManager _netManager;
|
||||
[Dependency] private readonly IPlayerManager _playerManager;
|
||||
[Dependency] private readonly IGameTicker _gameTicker;
|
||||
#pragma warning restore 649
|
||||
|
||||
public StationEvent CurrentEvent { get; private set; }
|
||||
public IReadOnlyCollection<StationEvent> StationEvents => _stationEvents;
|
||||
|
||||
private List<StationEvent> _stationEvents = new List<StationEvent>();
|
||||
|
||||
private const float MinimumTimeUntilFirstEvent = 600;
|
||||
@@ -154,8 +163,31 @@ namespace Content.Server.GameObjects.EntitySystems.StationEvents
|
||||
var stationEvent = (StationEvent) typeFactory.CreateInstance(type);
|
||||
_stationEvents.Add(stationEvent);
|
||||
}
|
||||
|
||||
_netManager.RegisterNetMessage<MsgGetStationEvents>(nameof(MsgGetStationEvents), GetEventReceived);
|
||||
}
|
||||
|
||||
private void GetEventReceived(MsgGetStationEvents msg)
|
||||
{
|
||||
var player = _playerManager.GetSessionByChannel(msg.MsgChannel);
|
||||
SendEvents(player);
|
||||
}
|
||||
|
||||
private void SendEvents(IPlayerSession player)
|
||||
{
|
||||
if (!IoCManager.Resolve<IConGroupController>().CanCommand(player, "events"))
|
||||
return;
|
||||
|
||||
var newMsg = _netManager.CreateNetMessage<MsgGetStationEvents>();
|
||||
newMsg.Events = new List<string>();
|
||||
foreach (var e in StationEvents)
|
||||
{
|
||||
newMsg.Events.Add(e.Name);
|
||||
}
|
||||
_netManager.ServerSendMessage(newMsg, player.ConnectedClient);
|
||||
}
|
||||
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
@@ -164,7 +196,18 @@ namespace Content.Server.GameObjects.EntitySystems.StationEvents
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Stop events from happening in lobby and force active event to end if the round ends
|
||||
if (_gameTicker.RunLevel != GameTicking.GameRunLevel.InRound)
|
||||
{
|
||||
if (CurrentEvent != null)
|
||||
{
|
||||
Enabled = false;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Keep running the current event
|
||||
if (CurrentEvent != null)
|
||||
{
|
||||
@@ -318,4 +361,4 @@ namespace Content.Server.GameObjects.EntitySystems.StationEvents
|
||||
CurrentEvent?.Shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
using Content.Server.GameObjects.Components.Nutrition;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
|
||||
namespace Content.Server.GameObjects.EntitySystems
|
||||
{
|
||||
/// <summary>
|
||||
/// Triggers digestion updates on <see cref="StomachComponent"/>
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
internal sealed class StomachSystem : EntitySystem
|
||||
{
|
||||
private float _accumulatedFrameTime;
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
//Update at most once per second
|
||||
_accumulatedFrameTime += frameTime;
|
||||
if (_accumulatedFrameTime > 1.0f)
|
||||
{
|
||||
foreach (var component in ComponentManager.EntityQuery<StomachComponent>())
|
||||
{
|
||||
component.OnUpdate(_accumulatedFrameTime);
|
||||
}
|
||||
_accumulatedFrameTime -= 1.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,20 +5,21 @@ using Robust.Shared.GameObjects.Systems;
|
||||
namespace Content.Server.GameObjects.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
internal sealed class ThirstSystem : EntitySystem
|
||||
public class ThirstSystem : EntitySystem
|
||||
{
|
||||
private float _accumulatedFrameTime;
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
_accumulatedFrameTime += frameTime;
|
||||
if (_accumulatedFrameTime > 1.0f)
|
||||
|
||||
if (_accumulatedFrameTime > 1)
|
||||
{
|
||||
foreach (var component in ComponentManager.EntityQuery<ThirstComponent>())
|
||||
{
|
||||
component.OnUpdate(_accumulatedFrameTime);
|
||||
}
|
||||
_accumulatedFrameTime -= 1.0f;
|
||||
_accumulatedFrameTime -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,8 +113,17 @@ namespace Content.Server.GameObjects.EntitySystems
|
||||
if (verb.RequireInteractionRange && !VerbUtility.InVerbUseRange(userEntity, entity))
|
||||
continue;
|
||||
|
||||
if (verb.BlockedByContainers && !userEntity.IsInSameOrNoContainer(entity))
|
||||
continue;
|
||||
if (verb.BlockedByContainers)
|
||||
{
|
||||
if (!userEntity.IsInSameOrNoContainer(entity))
|
||||
{
|
||||
if (!ContainerHelpers.TryGetContainer(entity, out var container) ||
|
||||
container.Owner != userEntity)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var verbData = verb.GetData(userEntity, component);
|
||||
if (verbData.IsInvisible)
|
||||
|
||||
Reference in New Issue
Block a user