Merge branch 'master' into 2021-12-03-remove-IEntity-komm-süsser-todd
# Conflicts: # Content.Client/Crayon/CrayonDecalVisualizer.cs # Content.Client/Tabletop/TabletopSystem.cs # Content.IntegrationTests/Tests/InventoryHelpersTest.cs # Content.Server/AI/EntitySystems/AiSystem.cs # Content.Server/AI/Utility/AiLogic/UtilityAI.cs # Content.Server/AME/AMENodeGroup.cs # Content.Server/Administration/AdminVerbSystem.cs # Content.Server/Body/Systems/RespiratorSystem.cs # Content.Server/Chemistry/Components/InjectorComponent.cs # Content.Server/Chemistry/TileReactions/CleanTileReaction.cs # Content.Server/Chemistry/TileReactions/SpillTileReaction.cs # Content.Server/Crayon/CrayonComponent.cs # Content.Server/Doors/Components/ServerDoorComponent.cs # Content.Server/Explosion/EntitySystems/TriggerSystem.cs # Content.Server/Fluids/Components/MopComponent.cs # Content.Server/Fluids/Components/SpillExtensions.cs # Content.Server/Fluids/EntitySystems/PuddleSystem.cs # Content.Server/Instruments/InstrumentSystem.cs # Content.Server/Nutrition/EntitySystems/DrinkSystem.cs # Content.Server/Nutrition/EntitySystems/FoodSystem.cs # Content.Server/PneumaticCannon/PneumaticCannonSystem.cs # Content.Server/Storage/Components/EntityStorageComponent.cs # Content.Server/Storage/Components/StorageFillComponent.cs # Content.Server/Stunnable/StunbatonSystem.cs # Content.Server/Throwing/ThrowHelper.cs # Content.Server/Weapon/Ranged/Barrels/BarrelSystem.cs # Content.Server/Weapon/Ranged/Barrels/Components/ServerBatteryBarrelComponent.cs # Content.Server/Weapon/Ranged/ServerRangedWeaponComponent.cs # Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs # Content.Shared/Damage/Components/DamageableComponent.cs # Content.Shared/Damage/Systems/DamageableSystem.cs # Content.Shared/MobState/Components/MobStateComponent.cs # Content.Shared/Slippery/SharedSlipperySystem.cs
This commit is contained in:
2
.github/workflows/publish.yml
vendored
2
.github/workflows/publish.yml
vendored
@@ -2,7 +2,7 @@ name: Publish
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ master ]
|
branches: [ stable ]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|||||||
@@ -395,7 +395,13 @@ namespace Content.Client.Chat.UI
|
|||||||
|
|
||||||
private void WriteChatMessage(StoredChatMessage message)
|
private void WriteChatMessage(StoredChatMessage message)
|
||||||
{
|
{
|
||||||
Logger.DebugS("chat", $"{message.Channel}: {message.Message}");
|
var messageText = FormattedMessage.EscapeText(message.Message);
|
||||||
|
if (!string.IsNullOrEmpty(message.MessageWrap))
|
||||||
|
{
|
||||||
|
messageText = string.Format(message.MessageWrap, messageText);
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.DebugS("chat", $"{message.Channel}: {messageText}");
|
||||||
|
|
||||||
if (IsFilteredOut(message.Channel))
|
if (IsFilteredOut(message.Channel))
|
||||||
return;
|
return;
|
||||||
@@ -403,12 +409,6 @@ namespace Content.Client.Chat.UI
|
|||||||
// TODO: Can make this "smarter" later by only setting it false when the message has been scrolled to
|
// TODO: Can make this "smarter" later by only setting it false when the message has been scrolled to
|
||||||
message.Read = true;
|
message.Read = true;
|
||||||
|
|
||||||
var messageText = FormattedMessage.EscapeText(message.Message);
|
|
||||||
if (!string.IsNullOrEmpty(message.MessageWrap))
|
|
||||||
{
|
|
||||||
messageText = string.Format(message.MessageWrap, messageText);
|
|
||||||
}
|
|
||||||
|
|
||||||
var color = message.MessageColorOverride != Color.Transparent
|
var color = message.MessageColorOverride != Color.Transparent
|
||||||
? message.MessageColorOverride
|
? message.MessageColorOverride
|
||||||
: ChatHelper.ChatColor(message.Channel);
|
: ChatHelper.ChatColor(message.Channel);
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
using Content.Shared.Crayon;
|
|
||||||
using JetBrains.Annotations;
|
|
||||||
using Robust.Client.GameObjects;
|
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
using Robust.Shared.IoC;
|
|
||||||
using Robust.Shared.Maths;
|
|
||||||
|
|
||||||
namespace Content.Client.Crayon
|
|
||||||
{
|
|
||||||
[UsedImplicitly]
|
|
||||||
public class CrayonDecalVisualizer : AppearanceVisualizer
|
|
||||||
{
|
|
||||||
public override void OnChangeData(AppearanceComponent component)
|
|
||||||
{
|
|
||||||
base.OnChangeData(component);
|
|
||||||
|
|
||||||
var sprite = IoCManager.Resolve<IEntityManager>().GetComponent<SpriteComponent>(component.Owner);
|
|
||||||
|
|
||||||
if (component.TryGetData(CrayonVisuals.State, out string state))
|
|
||||||
{
|
|
||||||
sprite.LayerSetState(0, state);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (component.TryGetData(CrayonVisuals.Color, out string color))
|
|
||||||
{
|
|
||||||
sprite.LayerSetColor(0, Color.FromName(color));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Shared.Crayon;
|
using Content.Shared.Crayon;
|
||||||
|
using Content.Shared.Decals;
|
||||||
using Robust.Client.GameObjects;
|
using Robust.Client.GameObjects;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
@@ -22,9 +23,8 @@ namespace Content.Client.Crayon.UI
|
|||||||
|
|
||||||
_menu.OnClose += Close;
|
_menu.OnClose += Close;
|
||||||
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||||
var crayonDecals = prototypeManager.EnumeratePrototypes<CrayonDecalPrototype>().FirstOrDefault();
|
var crayonDecals = prototypeManager.EnumeratePrototypes<DecalPrototype>().Where(x => x.Tags.Contains("crayon"));
|
||||||
if (crayonDecals != null)
|
_menu.Populate(crayonDecals);
|
||||||
_menu.Populate(crayonDecals);
|
|
||||||
_menu.OpenCentered();
|
_menu.OpenCentered();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Content.Client.Stylesheets;
|
using Content.Client.Stylesheets;
|
||||||
using Content.Shared.Crayon;
|
using Content.Shared.Crayon;
|
||||||
|
using Content.Shared.Decals;
|
||||||
using Robust.Client.AutoGenerated;
|
using Robust.Client.AutoGenerated;
|
||||||
using Robust.Client.Graphics;
|
using Robust.Client.Graphics;
|
||||||
using Robust.Client.UserInterface.Controls;
|
using Robust.Client.UserInterface.Controls;
|
||||||
@@ -89,14 +90,12 @@ namespace Content.Client.Crayon.UI
|
|||||||
RefreshList();
|
RefreshList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Populate(CrayonDecalPrototype proto)
|
public void Populate(IEnumerable<DecalPrototype> prototypes)
|
||||||
{
|
{
|
||||||
var path = new ResourcePath(proto.SpritePath);
|
|
||||||
_decals = new Dictionary<string, Texture>();
|
_decals = new Dictionary<string, Texture>();
|
||||||
foreach (var state in proto.Decals)
|
foreach (var decalPrototype in prototypes)
|
||||||
{
|
{
|
||||||
var rsi = new SpriteSpecifier.Rsi(path, state);
|
_decals.Add(decalPrototype.ID, decalPrototype.Sprite.Frame0());
|
||||||
_decals.Add(state, rsi.Frame0());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RefreshList();
|
RefreshList();
|
||||||
|
|||||||
60
Content.Client/Decals/DecalOverlay.cs
Normal file
60
Content.Client/Decals/DecalOverlay.cs
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Shared.Decals;
|
||||||
|
using Robust.Client.Graphics;
|
||||||
|
using Robust.Client.Utility;
|
||||||
|
using Robust.Shared.Enums;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
|
||||||
|
namespace Content.Client.Decals
|
||||||
|
{
|
||||||
|
public class DecalOverlay : Overlay
|
||||||
|
{
|
||||||
|
private readonly DecalSystem _system;
|
||||||
|
private readonly IMapManager _mapManager;
|
||||||
|
private readonly IPrototypeManager _prototypeManager;
|
||||||
|
|
||||||
|
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowEntities;
|
||||||
|
|
||||||
|
public DecalOverlay(DecalSystem system, IMapManager mapManager, IPrototypeManager prototypeManager)
|
||||||
|
{
|
||||||
|
_system = system;
|
||||||
|
_mapManager = mapManager;
|
||||||
|
_prototypeManager = prototypeManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Draw(in OverlayDrawArgs args)
|
||||||
|
{
|
||||||
|
var handle = args.WorldHandle;
|
||||||
|
|
||||||
|
Dictionary<string, SpriteSpecifier> cachedTextures = new();
|
||||||
|
|
||||||
|
SpriteSpecifier GetSpriteSpecifier(string id)
|
||||||
|
{
|
||||||
|
if (cachedTextures.TryGetValue(id, out var spriteSpecifier))
|
||||||
|
return spriteSpecifier;
|
||||||
|
|
||||||
|
spriteSpecifier = _prototypeManager.Index<DecalPrototype>(id).Sprite;
|
||||||
|
cachedTextures.Add(id, spriteSpecifier);
|
||||||
|
return spriteSpecifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var (gridId, zIndexDictionary) in _system.DecalRenderIndex)
|
||||||
|
{
|
||||||
|
var grid = _mapManager.GetGrid(gridId);
|
||||||
|
handle.SetTransform(grid.WorldMatrix);
|
||||||
|
foreach (var (_, decals) in zIndexDictionary)
|
||||||
|
{
|
||||||
|
foreach (var (_, decal) in decals)
|
||||||
|
{
|
||||||
|
var spriteSpecifier = GetSpriteSpecifier(decal.Id);
|
||||||
|
handle.DrawTexture(spriteSpecifier.Frame0(), decal.Coordinates, decal.Angle, decal.Color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
114
Content.Client/Decals/DecalSystem.cs
Normal file
114
Content.Client/Decals/DecalSystem.cs
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Shared.Decals;
|
||||||
|
using Robust.Client.Graphics;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
|
||||||
|
namespace Content.Client.Decals
|
||||||
|
{
|
||||||
|
public class DecalSystem : SharedDecalSystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IOverlayManager _overlayManager = default!;
|
||||||
|
|
||||||
|
private DecalOverlay _overlay = default!;
|
||||||
|
public Dictionary<GridId, SortedDictionary<int, SortedDictionary<uint, Decal>>> DecalRenderIndex = new();
|
||||||
|
private Dictionary<GridId, Dictionary<uint, int>> DecalZIndexIndex = new();
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
_overlay = new DecalOverlay(this, MapManager, PrototypeManager);
|
||||||
|
_overlayManager.AddOverlay(_overlay);
|
||||||
|
|
||||||
|
SubscribeNetworkEvent<DecalChunkUpdateEvent>(OnChunkUpdate);
|
||||||
|
SubscribeLocalEvent<GridInitializeEvent>(OnGridInitialize);
|
||||||
|
SubscribeLocalEvent<GridRemovalEvent>(OnGridRemoval);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ToggleOverlay()
|
||||||
|
{
|
||||||
|
if (_overlayManager.HasOverlay<DecalOverlay>())
|
||||||
|
{
|
||||||
|
_overlayManager.RemoveOverlay(_overlay);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_overlayManager.AddOverlay(_overlay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGridRemoval(GridRemovalEvent ev)
|
||||||
|
{
|
||||||
|
DecalRenderIndex.Remove(ev.GridId);
|
||||||
|
DecalZIndexIndex.Remove(ev.GridId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGridInitialize(GridInitializeEvent ev)
|
||||||
|
{
|
||||||
|
DecalRenderIndex[ev.GridId] = new();
|
||||||
|
DecalZIndexIndex[ev.GridId] = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Shutdown()
|
||||||
|
{
|
||||||
|
base.Shutdown();
|
||||||
|
_overlayManager.RemoveOverlay(_overlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool RemoveDecalHook(GridId gridId, uint uid)
|
||||||
|
{
|
||||||
|
RemoveDecalFromRenderIndex(gridId, uid);
|
||||||
|
|
||||||
|
return base.RemoveDecalHook(gridId, uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveDecalFromRenderIndex(GridId gridId, uint uid)
|
||||||
|
{
|
||||||
|
var zIndex = DecalZIndexIndex[gridId][uid];
|
||||||
|
|
||||||
|
DecalRenderIndex[gridId][zIndex].Remove(uid);
|
||||||
|
if (DecalRenderIndex[gridId][zIndex].Count == 0)
|
||||||
|
DecalRenderIndex[gridId].Remove(zIndex);
|
||||||
|
|
||||||
|
DecalZIndexIndex[gridId].Remove(uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnChunkUpdate(DecalChunkUpdateEvent ev)
|
||||||
|
{
|
||||||
|
foreach (var (gridId, gridChunks) in ev.Data)
|
||||||
|
{
|
||||||
|
foreach (var (indices, newChunkData) in gridChunks)
|
||||||
|
{
|
||||||
|
var chunkCollection = ChunkCollection(gridId);
|
||||||
|
if (chunkCollection.TryGetValue(indices, out var chunk))
|
||||||
|
{
|
||||||
|
var removedUids = new HashSet<uint>(chunk.Keys);
|
||||||
|
removedUids.ExceptWith(newChunkData.Keys);
|
||||||
|
foreach (var removedUid in removedUids)
|
||||||
|
{
|
||||||
|
RemoveDecalFromRenderIndex(gridId, removedUid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach (var (uid, decal) in newChunkData)
|
||||||
|
{
|
||||||
|
if(!DecalRenderIndex[gridId].ContainsKey(decal.ZIndex))
|
||||||
|
DecalRenderIndex[gridId][decal.ZIndex] = new();
|
||||||
|
|
||||||
|
if (DecalZIndexIndex.TryGetValue(gridId, out var values) && values.TryGetValue(uid, out var zIndex))
|
||||||
|
{
|
||||||
|
DecalRenderIndex[gridId][zIndex].Remove(uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
DecalRenderIndex[gridId][decal.ZIndex][uid] = decal;
|
||||||
|
DecalZIndexIndex[gridId][uid] = decal.ZIndex;
|
||||||
|
|
||||||
|
ChunkIndex[gridId][uid] = indices;
|
||||||
|
}
|
||||||
|
chunkCollection[indices] = newChunkData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
Content.Client/Decals/ToggleDecalCommand.cs
Normal file
15
Content.Client/Decals/ToggleDecalCommand.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using Robust.Shared.Console;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Client.Decals;
|
||||||
|
|
||||||
|
public class ToggleDecalCommand : IConsoleCommand
|
||||||
|
{
|
||||||
|
public string Command => "toggledecals";
|
||||||
|
public string Description => "Toggles decaloverlay";
|
||||||
|
public string Help => $"{Command}";
|
||||||
|
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||||
|
{
|
||||||
|
EntitySystem.Get<DecalSystem>().ToggleOverlay();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ namespace Content.Client.Entry
|
|||||||
"Anchorable",
|
"Anchorable",
|
||||||
"AmmoBox",
|
"AmmoBox",
|
||||||
"Pickaxe",
|
"Pickaxe",
|
||||||
|
"IngestionBlocker",
|
||||||
"Interactable",
|
"Interactable",
|
||||||
"CloningPod",
|
"CloningPod",
|
||||||
"Destructible",
|
"Destructible",
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ using Robust.Shared.IoC;
|
|||||||
using Robust.Shared.Log;
|
using Robust.Shared.Log;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Maths;
|
using Robust.Shared.Maths;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
using static Robust.Shared.Input.Binding.PointerInputCmdHandler;
|
using static Robust.Shared.Input.Binding.PointerInputCmdHandler;
|
||||||
using DrawDepth = Content.Shared.DrawDepth.DrawDepth;
|
using DrawDepth = Content.Shared.DrawDepth.DrawDepth;
|
||||||
|
|
||||||
@@ -29,6 +30,7 @@ namespace Content.Client.Tabletop
|
|||||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||||
[Dependency] private readonly IUserInterfaceManager _uiManger = default!;
|
[Dependency] private readonly IUserInterfaceManager _uiManger = default!;
|
||||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||||
|
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||||
|
|
||||||
// Time in seconds to wait until sending the location of a dragged entity to the server again
|
// Time in seconds to wait until sending the location of a dragged entity to the server again
|
||||||
private const float Delay = 1f / 10; // 10 Hz
|
private const float Delay = 1f / 10; // 10 Hz
|
||||||
@@ -51,6 +53,10 @@ namespace Content.Client.Tabletop
|
|||||||
|
|
||||||
public override void Update(float frameTime)
|
public override void Update(float frameTime)
|
||||||
{
|
{
|
||||||
|
// don't send network messages when doing prediction.
|
||||||
|
if (!_gameTiming.IsFirstTimePredicted)
|
||||||
|
return;
|
||||||
|
|
||||||
// If there is no player entity, return
|
// If there is no player entity, return
|
||||||
if (_playerManager.LocalPlayer is not {ControlledEntity: { } playerEntity}) return;
|
if (_playerManager.LocalPlayer is not {ControlledEntity: { } playerEntity}) return;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using Content.Client.Items.Components;
|
using Content.Client.Items.Components;
|
||||||
using Content.Client.Stylesheets;
|
using Content.Client.Stylesheets;
|
||||||
|
using Content.Shared.Containers.ItemSlots;
|
||||||
using Content.Shared.Weapons.Ranged.Barrels.Components;
|
using Content.Shared.Weapons.Ranged.Barrels.Components;
|
||||||
using Robust.Client.Graphics;
|
using Robust.Client.Graphics;
|
||||||
using Robust.Client.UserInterface;
|
using Robust.Client.UserInterface;
|
||||||
@@ -8,6 +9,7 @@ using Robust.Client.UserInterface.Controls;
|
|||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.GameStates;
|
using Robust.Shared.GameStates;
|
||||||
using Robust.Shared.Maths;
|
using Robust.Shared.Maths;
|
||||||
|
using Robust.Shared.Serialization.Manager.Attributes;
|
||||||
using Robust.Shared.ViewVariables;
|
using Robust.Shared.ViewVariables;
|
||||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||||
|
|
||||||
@@ -21,6 +23,9 @@ namespace Content.Client.Weapons.Ranged.Barrels.Components
|
|||||||
|
|
||||||
private StatusControl? _statusControl;
|
private StatusControl? _statusControl;
|
||||||
|
|
||||||
|
[DataField("cellSlot", required: true)]
|
||||||
|
public ItemSlot CellSlot = default!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Count of bullets in the magazine.
|
/// Count of bullets in the magazine.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -30,6 +35,18 @@ namespace Content.Client.Weapons.Ranged.Barrels.Components
|
|||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
public (int count, int max)? MagazineCount { get; private set; }
|
public (int count, int max)? MagazineCount { get; private set; }
|
||||||
|
|
||||||
|
protected override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
EntitySystem.Get<ItemSlotsSystem>().AddItemSlot(Owner, $"{Name}-powercell-container", CellSlot);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnRemove()
|
||||||
|
{
|
||||||
|
base.OnRemove();
|
||||||
|
EntitySystem.Get<ItemSlotsSystem>().RemoveItemSlot(Owner, CellSlot);
|
||||||
|
}
|
||||||
|
|
||||||
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
|
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
|
||||||
{
|
{
|
||||||
base.HandleComponentState(curState, nextState);
|
base.HandleComponentState(curState, nextState);
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ namespace Content.IntegrationTests.Tests
|
|||||||
{
|
{
|
||||||
var options = new ServerContentIntegrationOption()
|
var options = new ServerContentIntegrationOption()
|
||||||
{
|
{
|
||||||
CVarOverrides = {{CCVars.AIMaxUpdates.Name, int.MaxValue.ToString()}}
|
CVarOverrides = {{CCVars.NPCMaxUpdates.Name, int.MaxValue.ToString()}}
|
||||||
};
|
};
|
||||||
|
|
||||||
var server = StartServer(options);
|
var server = StartServer(options);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Content.Server.Fluids.Components;
|
using Content.Server.Fluids.Components;
|
||||||
|
using Content.Server.Fluids.EntitySystems;
|
||||||
using Content.Shared.Chemistry.Components;
|
using Content.Shared.Chemistry.Components;
|
||||||
using Content.Shared.Coordinates;
|
using Content.Shared.Coordinates;
|
||||||
using Content.Shared.FixedPoint;
|
using Content.Shared.FixedPoint;
|
||||||
@@ -24,6 +25,8 @@ namespace Content.IntegrationTests.Tests.Fluids
|
|||||||
await server.WaitIdleAsync();
|
await server.WaitIdleAsync();
|
||||||
|
|
||||||
var mapManager = server.ResolveDependency<IMapManager>();
|
var mapManager = server.ResolveDependency<IMapManager>();
|
||||||
|
var entitySystemManager = server.ResolveDependency<IEntitySystemManager>();
|
||||||
|
var spillSystem = entitySystemManager.GetEntitySystem<SpillableSystem>();
|
||||||
|
|
||||||
server.Assert(() =>
|
server.Assert(() =>
|
||||||
{
|
{
|
||||||
@@ -31,7 +34,7 @@ namespace Content.IntegrationTests.Tests.Fluids
|
|||||||
var grid = GetMainGrid(mapManager);
|
var grid = GetMainGrid(mapManager);
|
||||||
var (x, y) = GetMainTile(grid).GridIndices;
|
var (x, y) = GetMainTile(grid).GridIndices;
|
||||||
var coordinates = new EntityCoordinates(grid.GridEntityId, x, y);
|
var coordinates = new EntityCoordinates(grid.GridEntityId, x, y);
|
||||||
var puddle = solution.SpillAt(coordinates, "PuddleSmear");
|
var puddle = spillSystem.SpillAt(solution, coordinates, "PuddleSmear");
|
||||||
|
|
||||||
Assert.NotNull(puddle);
|
Assert.NotNull(puddle);
|
||||||
});
|
});
|
||||||
@@ -47,6 +50,8 @@ namespace Content.IntegrationTests.Tests.Fluids
|
|||||||
await server.WaitIdleAsync();
|
await server.WaitIdleAsync();
|
||||||
|
|
||||||
var mapManager = server.ResolveDependency<IMapManager>();
|
var mapManager = server.ResolveDependency<IMapManager>();
|
||||||
|
var entitySystemManager = server.ResolveDependency<IEntitySystemManager>();
|
||||||
|
var spillSystem = entitySystemManager.GetEntitySystem<SpillableSystem>();
|
||||||
|
|
||||||
IMapGrid grid = null;
|
IMapGrid grid = null;
|
||||||
|
|
||||||
@@ -67,7 +72,7 @@ namespace Content.IntegrationTests.Tests.Fluids
|
|||||||
{
|
{
|
||||||
var coordinates = grid.ToCoordinates();
|
var coordinates = grid.ToCoordinates();
|
||||||
var solution = new Solution("Water", FixedPoint2.New(20));
|
var solution = new Solution("Water", FixedPoint2.New(20));
|
||||||
var puddle = solution.SpillAt(coordinates, "PuddleSmear");
|
var puddle = spillSystem.SpillAt(solution, coordinates, "PuddleSmear");
|
||||||
Assert.Null(puddle);
|
Assert.Null(puddle);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -120,13 +125,17 @@ namespace Content.IntegrationTests.Tests.Fluids
|
|||||||
float evaporateTime = default;
|
float evaporateTime = default;
|
||||||
PuddleComponent puddle = null;
|
PuddleComponent puddle = null;
|
||||||
EvaporationComponent evaporation;
|
EvaporationComponent evaporation;
|
||||||
|
|
||||||
var amount = 2;
|
var amount = 2;
|
||||||
|
|
||||||
|
var entitySystemManager = server.ResolveDependency<IEntitySystemManager>();
|
||||||
|
var spillSystem = entitySystemManager.GetEntitySystem<SpillableSystem>();
|
||||||
|
|
||||||
// Spawn a puddle
|
// Spawn a puddle
|
||||||
await server.WaitAssertion(() =>
|
await server.WaitAssertion(() =>
|
||||||
{
|
{
|
||||||
var solution = new Solution("Water", FixedPoint2.New(amount));
|
var solution = new Solution("Water", FixedPoint2.New(amount));
|
||||||
puddle = solution.SpillAt(sCoordinates, "PuddleSmear");
|
puddle = spillSystem.SpillAt(solution, sCoordinates, "PuddleSmear");
|
||||||
|
|
||||||
// Check that the puddle was created
|
// Check that the puddle was created
|
||||||
Assert.NotNull(puddle);
|
Assert.NotNull(puddle);
|
||||||
@@ -184,4 +193,4 @@ namespace Content.IntegrationTests.Tests.Fluids
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -78,7 +78,7 @@ namespace Content.IntegrationTests.Tests
|
|||||||
ID: "InventoryJumpsuitJanitorDummy"
|
ID: "InventoryJumpsuitJanitorDummy"
|
||||||
});
|
});
|
||||||
|
|
||||||
EntitySystem.Get<StunSystem>().TryStun(human, TimeSpan.FromSeconds(1f));
|
EntitySystem.Get<StunSystem>().TryStun(human, TimeSpan.FromSeconds(1f), true);
|
||||||
|
|
||||||
// Since the mob is stunned, they can't equip this.
|
// Since the mob is stunned, they can't equip this.
|
||||||
Assert.That(inventory.SpawnItemInSlot(Slots.IDCARD, "InventoryIDCardDummy", true), Is.False);
|
Assert.That(inventory.SpawnItemInSlot(Slots.IDCARD, "InventoryIDCardDummy", true), Is.False);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Content.Server.GameTicking;
|
using Content.Server.AI.EntitySystems;
|
||||||
|
using Content.Server.GameTicking;
|
||||||
using Content.Shared.Movement.Components;
|
using Content.Shared.Movement.Components;
|
||||||
using Content.Shared.Roles;
|
using Content.Shared.Roles;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
@@ -19,6 +20,29 @@ namespace Content.Server.AI.Components
|
|||||||
|
|
||||||
public override string Name => "AiController";
|
public override string Name => "AiController";
|
||||||
|
|
||||||
|
// TODO: Need to ECS a lot more of the AI first before we can ECS this
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the AI is actively iterated.
|
||||||
|
/// </summary>
|
||||||
|
public bool Awake
|
||||||
|
{
|
||||||
|
get => _awake;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_awake == value) return;
|
||||||
|
|
||||||
|
_awake = value;
|
||||||
|
|
||||||
|
if (_awake)
|
||||||
|
EntitySystem.Get<NPCSystem>().WakeNPC(this);
|
||||||
|
else
|
||||||
|
EntitySystem.Get<NPCSystem>().SleepNPC(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[DataField("awake")]
|
||||||
|
private bool _awake = true;
|
||||||
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
[DataField("startingGear")]
|
[DataField("startingGear")]
|
||||||
public string? StartingGearPrototype { get; set; }
|
public string? StartingGearPrototype { get; set; }
|
||||||
|
|||||||
@@ -1,133 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Content.Server.AI.Components;
|
|
||||||
using Content.Server.AI.Utility.AiLogic;
|
|
||||||
using Content.Shared;
|
|
||||||
using Content.Shared.CCVar;
|
|
||||||
using Content.Shared.MobState;
|
|
||||||
using JetBrains.Annotations;
|
|
||||||
using Robust.Shared.Configuration;
|
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
using Robust.Shared.IoC;
|
|
||||||
using Robust.Shared.Log;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.EntitySystems
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Handles NPCs running every tick.
|
|
||||||
/// </summary>
|
|
||||||
[UsedImplicitly]
|
|
||||||
internal class AiSystem : EntitySystem
|
|
||||||
{
|
|
||||||
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// To avoid iterating over dead AI continuously they can wake and sleep themselves when necessary.
|
|
||||||
/// </summary>
|
|
||||||
private readonly HashSet<AiControllerComponent> _awakeAi = new();
|
|
||||||
|
|
||||||
// To avoid modifying awakeAi while iterating over it.
|
|
||||||
private readonly List<SleepAiMessage> _queuedSleepMessages = new();
|
|
||||||
|
|
||||||
private readonly List<MobStateChangedMessage> _queuedMobStateMessages = new();
|
|
||||||
|
|
||||||
public bool IsAwake(AiControllerComponent npc) => _awakeAi.Contains(npc);
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override void Initialize()
|
|
||||||
{
|
|
||||||
base.Initialize();
|
|
||||||
SubscribeLocalEvent<SleepAiMessage>(HandleAiSleep);
|
|
||||||
SubscribeLocalEvent<MobStateChangedMessage>(MobStateChanged);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override void Update(float frameTime)
|
|
||||||
{
|
|
||||||
var cvarMaxUpdates = _configurationManager.GetCVar(CCVars.AIMaxUpdates);
|
|
||||||
if (cvarMaxUpdates <= 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
foreach (var message in _queuedMobStateMessages)
|
|
||||||
{
|
|
||||||
// TODO: Need to generecise this but that will be part of a larger cleanup later anyway.
|
|
||||||
if ((!IoCManager.Resolve<IEntityManager>().EntityExists(message.Entity) ? EntityLifeStage.Deleted : IoCManager.Resolve<IEntityManager>().GetComponent<MetaDataComponent>(message.Entity).EntityLifeStage) >= EntityLifeStage.Deleted ||
|
|
||||||
!IoCManager.Resolve<IEntityManager>().TryGetComponent(message.Entity, out UtilityAi? controller))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
controller.MobStateChanged(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
_queuedMobStateMessages.Clear();
|
|
||||||
|
|
||||||
foreach (var message in _queuedSleepMessages)
|
|
||||||
{
|
|
||||||
switch (message.Sleep)
|
|
||||||
{
|
|
||||||
case true:
|
|
||||||
if (_awakeAi.Count == cvarMaxUpdates && _awakeAi.Contains(message.Component))
|
|
||||||
{
|
|
||||||
Logger.Warning($"Under AI limit again: {_awakeAi.Count - 1} / {cvarMaxUpdates}");
|
|
||||||
}
|
|
||||||
_awakeAi.Remove(message.Component);
|
|
||||||
break;
|
|
||||||
case false:
|
|
||||||
_awakeAi.Add(message.Component);
|
|
||||||
|
|
||||||
if (_awakeAi.Count > cvarMaxUpdates)
|
|
||||||
{
|
|
||||||
Logger.Warning($"AI limit exceeded: {_awakeAi.Count} / {cvarMaxUpdates}");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_queuedSleepMessages.Clear();
|
|
||||||
var toRemove = new List<AiControllerComponent>();
|
|
||||||
var maxUpdates = Math.Min(_awakeAi.Count, cvarMaxUpdates);
|
|
||||||
var count = 0;
|
|
||||||
|
|
||||||
foreach (var npc in _awakeAi)
|
|
||||||
{
|
|
||||||
if (npc.Deleted)
|
|
||||||
{
|
|
||||||
toRemove.Add(npc);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (npc.Paused)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (count >= maxUpdates)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
npc.Update(frameTime);
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var processor in toRemove)
|
|
||||||
{
|
|
||||||
_awakeAi.Remove(processor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleAiSleep(SleepAiMessage message)
|
|
||||||
{
|
|
||||||
_queuedSleepMessages.Add(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void MobStateChanged(MobStateChangedMessage message)
|
|
||||||
{
|
|
||||||
if (!IoCManager.Resolve<IEntityManager>().HasComponent<AiControllerComponent>(message.Entity))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_queuedMobStateMessages.Add(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
135
Content.Server/AI/EntitySystems/NPCSystem.cs
Normal file
135
Content.Server/AI/EntitySystems/NPCSystem.cs
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Content.Server.AI.Components;
|
||||||
|
using Content.Server.MobState.States;
|
||||||
|
using Content.Shared.CCVar;
|
||||||
|
using Content.Shared.MobState;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Shared.Configuration;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Random;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.EntitySystems
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Handles NPCs running every tick.
|
||||||
|
/// </summary>
|
||||||
|
[UsedImplicitly]
|
||||||
|
internal class NPCSystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||||
|
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// To avoid iterating over dead AI continuously they can wake and sleep themselves when necessary.
|
||||||
|
/// </summary>
|
||||||
|
private readonly HashSet<AiControllerComponent> _awakeNPCs = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether any NPCs are allowed to run at all.
|
||||||
|
/// </summary>
|
||||||
|
public bool Enabled { get; set; } = true;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
SubscribeLocalEvent<AiControllerComponent, MobStateChangedEvent>(OnMobStateChange);
|
||||||
|
SubscribeLocalEvent<AiControllerComponent, ComponentInit>(OnNPCInit);
|
||||||
|
SubscribeLocalEvent<AiControllerComponent, ComponentShutdown>(OnNPCShutdown);
|
||||||
|
_configurationManager.OnValueChanged(CCVars.NPCEnabled, SetEnabled, true);
|
||||||
|
|
||||||
|
var maxUpdates = _configurationManager.GetCVar(CCVars.NPCMaxUpdates);
|
||||||
|
|
||||||
|
if (maxUpdates < 1024)
|
||||||
|
_awakeNPCs.EnsureCapacity(maxUpdates);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetEnabled(bool value) => Enabled = value;
|
||||||
|
|
||||||
|
public override void Shutdown()
|
||||||
|
{
|
||||||
|
base.Shutdown();
|
||||||
|
_configurationManager.UnsubValueChanged(CCVars.NPCEnabled, SetEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnNPCInit(EntityUid uid, AiControllerComponent component, ComponentInit args)
|
||||||
|
{
|
||||||
|
if (!component.Awake) return;
|
||||||
|
|
||||||
|
_awakeNPCs.Add(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnNPCShutdown(EntityUid uid, AiControllerComponent component, ComponentShutdown args)
|
||||||
|
{
|
||||||
|
_awakeNPCs.Remove(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Allows the NPC to actively be updated.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="component"></param>
|
||||||
|
public void WakeNPC(AiControllerComponent component)
|
||||||
|
{
|
||||||
|
_awakeNPCs.Add(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stops the NPC from actively being updated.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="component"></param>
|
||||||
|
public void SleepNPC(AiControllerComponent component)
|
||||||
|
{
|
||||||
|
_awakeNPCs.Remove(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Update(float frameTime)
|
||||||
|
{
|
||||||
|
if (!Enabled) return;
|
||||||
|
|
||||||
|
var cvarMaxUpdates = _configurationManager.GetCVar(CCVars.NPCMaxUpdates);
|
||||||
|
|
||||||
|
if (cvarMaxUpdates <= 0) return;
|
||||||
|
|
||||||
|
var npcs = _awakeNPCs.ToArray();
|
||||||
|
var startIndex = 0;
|
||||||
|
|
||||||
|
// If we're overcap we'll just update randomly so they all still at least do something
|
||||||
|
// Didn't randomise the array (even though it'd probably be better) because god damn that'd be expensive.
|
||||||
|
if (npcs.Length > cvarMaxUpdates)
|
||||||
|
{
|
||||||
|
startIndex = _robustRandom.Next(npcs.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < npcs.Length; i++)
|
||||||
|
{
|
||||||
|
var index = (i + startIndex) % npcs.Length;
|
||||||
|
var npc = npcs[index];
|
||||||
|
|
||||||
|
if (npc.Deleted)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (npc.Paused)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
npc.Update(frameTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnMobStateChange(EntityUid uid, AiControllerComponent component, MobStateChangedEvent args)
|
||||||
|
{
|
||||||
|
switch (args.CurrentMobState)
|
||||||
|
{
|
||||||
|
case NormalMobState:
|
||||||
|
component.Awake = true;
|
||||||
|
break;
|
||||||
|
case CriticalMobState:
|
||||||
|
case DeadMobState:
|
||||||
|
component.Awake = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
using Content.Server.AI.Components;
|
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
|
|
||||||
namespace Content.Server.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 : EntityEventArgs
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Sleep or awake.
|
|
||||||
/// </summary>
|
|
||||||
public bool Sleep { get; }
|
|
||||||
public AiControllerComponent Component { get; }
|
|
||||||
|
|
||||||
public SleepAiMessage(AiControllerComponent component, bool sleep)
|
|
||||||
{
|
|
||||||
Component = component;
|
|
||||||
Sleep = sleep;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.Runtime.ExceptionServices;
|
using System.Runtime.ExceptionServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Content.Server.AI.Components;
|
using Content.Server.AI.Components;
|
||||||
|
using Content.Server.AI.EntitySystems;
|
||||||
using Content.Server.AI.LoadBalancer;
|
using Content.Server.AI.LoadBalancer;
|
||||||
using Content.Server.AI.Operators;
|
using Content.Server.AI.Operators;
|
||||||
using Content.Server.AI.Utility.Actions;
|
using Content.Server.AI.Utility.Actions;
|
||||||
@@ -59,33 +60,13 @@ namespace Content.Server.AI.Utility.AiLogic
|
|||||||
|
|
||||||
private CancellationTokenSource? _actionCancellation;
|
private CancellationTokenSource? _actionCancellation;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// If we can't do anything then stop thinking; should probably use ActionBlocker instead
|
|
||||||
/// </summary>
|
|
||||||
private bool _isDead;
|
|
||||||
|
|
||||||
/*public void AfterDeserialization()
|
|
||||||
{
|
|
||||||
if (BehaviorSets.Count > 0)
|
|
||||||
{
|
|
||||||
var behaviorManager = IoCManager.Resolve<INpcBehaviorManager>();
|
|
||||||
|
|
||||||
foreach (var bSet in BehaviorSets)
|
|
||||||
{
|
|
||||||
behaviorManager.AddBehaviorSet(this, bSet, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
behaviorManager.RebuildActions(this);
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
protected override void Initialize()
|
protected override void Initialize()
|
||||||
{
|
{
|
||||||
if (BehaviorSets.Count > 0)
|
if (BehaviorSets.Count > 0)
|
||||||
{
|
{
|
||||||
var behaviorManager = IoCManager.Resolve<INpcBehaviorManager>();
|
var behaviorManager = IoCManager.Resolve<INpcBehaviorManager>();
|
||||||
behaviorManager.RebuildActions(this);
|
behaviorManager.RebuildActions(this);
|
||||||
IoCManager.Resolve<IEntityManager>().EventBus.RaiseEvent(EventSource.Local, new SleepAiMessage(this, false));
|
EntitySystem.Get<NPCSystem>().WakeNPC(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
@@ -103,27 +84,6 @@ namespace Content.Server.AI.Utility.AiLogic
|
|||||||
CurrentAction = null;
|
CurrentAction = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void MobStateChanged(MobStateChangedMessage message)
|
|
||||||
{
|
|
||||||
var oldDeadState = _isDead;
|
|
||||||
_isDead = message.Component.IsIncapacitated();
|
|
||||||
|
|
||||||
if (oldDeadState != _isDead)
|
|
||||||
{
|
|
||||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
|
||||||
|
|
||||||
switch (_isDead)
|
|
||||||
{
|
|
||||||
case true:
|
|
||||||
entityManager.EventBus.RaiseEvent(EventSource.Local, new SleepAiMessage(this, true));
|
|
||||||
break;
|
|
||||||
case false:
|
|
||||||
entityManager.EventBus.RaiseEvent(EventSource.Local, new SleepAiMessage(this, false));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ReceivedAction()
|
private void ReceivedAction()
|
||||||
{
|
{
|
||||||
if (_actionRequest == null)
|
if (_actionRequest == null)
|
||||||
|
|||||||
@@ -84,9 +84,9 @@ namespace Content.Server.AI.Utility
|
|||||||
if (rebuild)
|
if (rebuild)
|
||||||
RebuildActions(npc);
|
RebuildActions(npc);
|
||||||
|
|
||||||
if (npc.BehaviorSets.Count == 1 && !EntitySystem.Get<AiSystem>().IsAwake(npc))
|
if (npc.BehaviorSets.Count == 1 && !npc.Awake)
|
||||||
{
|
{
|
||||||
_entityManager.EventBus.RaiseEvent(EventSource.Local, new SleepAiMessage(npc, false));
|
EntitySystem.Get<NPCSystem>().WakeNPC(npc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,9 +113,9 @@ namespace Content.Server.AI.Utility
|
|||||||
if (rebuild)
|
if (rebuild)
|
||||||
RebuildActions(npc);
|
RebuildActions(npc);
|
||||||
|
|
||||||
if (npc.BehaviorSets.Count == 0 && EntitySystem.Get<AiSystem>().IsAwake(npc))
|
if (npc.BehaviorSets.Count == 0 && npc.Awake)
|
||||||
{
|
{
|
||||||
_entityManager.EventBus.RaiseEvent(EventSource.Local, new SleepAiMessage(npc, true));
|
EntitySystem.Get<NPCSystem>().SleepNPC(npc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,13 @@ namespace Content.Server.Access.Components
|
|||||||
{
|
{
|
||||||
public override string Name => "AccessReader";
|
public override string Name => "AccessReader";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether this reader is enabled or not. If disabled, all access
|
||||||
|
/// checks will pass.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public bool Enabled = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The set of tags that will automatically deny an allowed check, if any of them are present.
|
/// The set of tags that will automatically deny an allowed check, if any of them are present.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ namespace Content.Server.Access.Systems
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return reader.AccessLists.Count == 0 || reader.AccessLists.Any(a => a.IsSubsetOf(accessTags));
|
return !reader.Enabled || reader.AccessLists.Count == 0 || reader.AccessLists.Any(a => a.IsSubsetOf(accessTags));
|
||||||
}
|
}
|
||||||
|
|
||||||
public ICollection<string> FindAccessTags(EntityUid uid)
|
public ICollection<string> FindAccessTags(EntityUid uid)
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ namespace Content.Server.Administration
|
|||||||
verb.Act = () =>
|
verb.Act = () =>
|
||||||
{
|
{
|
||||||
var coords = EntityManager.GetComponent<TransformComponent>(args.Target).Coordinates;
|
var coords = EntityManager.GetComponent<TransformComponent>(args.Target).Coordinates;
|
||||||
Timer.Spawn(_gameTiming.TickPeriod, () => _explosions.SpawnExplosion(coords, 0, 1, 2, 1), CancellationToken.None);
|
Timer.Spawn(_gameTiming.TickPeriod, () => _explosions.SpawnExplosion(coords, 0, 1, 2, 1, args.Target), CancellationToken.None);
|
||||||
if (EntityManager.TryGetComponent(args.Target, out SharedBodyComponent? body))
|
if (EntityManager.TryGetComponent(args.Target, out SharedBodyComponent? body))
|
||||||
{
|
{
|
||||||
body.Gib();
|
body.Gib();
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ namespace Content.Server.Atmos.EntitySystems
|
|||||||
flammable.Resisting = true;
|
flammable.Resisting = true;
|
||||||
|
|
||||||
flammable.Owner.PopupMessage(Loc.GetString("flammable-component-resist-message"));
|
flammable.Owner.PopupMessage(Loc.GetString("flammable-component-resist-message"));
|
||||||
_stunSystem.TryParalyze(uid, TimeSpan.FromSeconds(2f), alerts: alerts);
|
_stunSystem.TryParalyze(uid, TimeSpan.FromSeconds(2f), true, alerts: alerts);
|
||||||
|
|
||||||
// TODO FLAMMABLE: Make this not use TimerComponent...
|
// TODO FLAMMABLE: Make this not use TimerComponent...
|
||||||
flammable.Owner.SpawnTimer(2000, () =>
|
flammable.Owner.SpawnTimer(2000, () =>
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
using Content.Server.Atmos.EntitySystems;
|
using Content.Server.Atmos.EntitySystems;
|
||||||
using Content.Server.Fluids.Components;
|
using Content.Server.Fluids.Components;
|
||||||
|
using Content.Server.Fluids.EntitySystems;
|
||||||
using Content.Shared.Chemistry.Components;
|
using Content.Shared.Chemistry.Components;
|
||||||
using Content.Shared.Chemistry.Reagent;
|
using Content.Shared.Chemistry.Reagent;
|
||||||
using Content.Shared.FixedPoint;
|
using Content.Shared.FixedPoint;
|
||||||
using Content.Shared.Maps;
|
using Content.Shared.Maps;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.Serialization.Manager.Attributes;
|
using Robust.Shared.Serialization.Manager.Attributes;
|
||||||
|
|
||||||
namespace Content.Server.Atmos.Reactions
|
namespace Content.Server.Atmos.Reactions
|
||||||
@@ -35,8 +37,9 @@ namespace Content.Server.Atmos.Reactions
|
|||||||
// Remove the moles from the mixture...
|
// Remove the moles from the mixture...
|
||||||
mixture.AdjustMoles(GasId, -MolesPerUnit);
|
mixture.AdjustMoles(GasId, -MolesPerUnit);
|
||||||
|
|
||||||
var tileRef = tile.GridIndices.GetTileRef(tile.GridIndex);
|
var tileRef = tile.GridIndices.GetTileRef(tile.GridIndex);
|
||||||
tileRef.SpillAt(new Solution(Reagent, FixedPoint2.New(MolesPerUnit)), PuddlePrototype, sound: false);
|
EntitySystem.Get<SpillableSystem>()
|
||||||
|
.SpillAt(tileRef, new Solution(Reagent, FixedPoint2.New(MolesPerUnit)), PuddlePrototype, sound: false);
|
||||||
|
|
||||||
return ReactionResult.Reacting;
|
return ReactionResult.Reacting;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ public class LungSystem : EntitySystem
|
|||||||
Inhale(uid, lung.CycleDelay);
|
Inhale(uid, lung.CycleDelay);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateLung(EntityUid uid, float frameTime,
|
public void UpdateLung(EntityUid uid,
|
||||||
LungComponent? lung=null,
|
LungComponent? lung=null,
|
||||||
SharedMechanismComponent? mech=null)
|
SharedMechanismComponent? mech=null)
|
||||||
{
|
{
|
||||||
@@ -69,8 +69,8 @@ public class LungSystem : EntitySystem
|
|||||||
|
|
||||||
lung.AccumulatedFrametime += lung.Status switch
|
lung.AccumulatedFrametime += lung.Status switch
|
||||||
{
|
{
|
||||||
LungStatus.Inhaling => frameTime,
|
LungStatus.Inhaling => 1,
|
||||||
LungStatus.Exhaling => -frameTime,
|
LungStatus.Exhaling => -1,
|
||||||
_ => throw new ArgumentOutOfRangeException()
|
_ => throw new ArgumentOutOfRangeException()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -32,41 +32,40 @@ namespace Content.Server.Body.Systems
|
|||||||
foreach (var (respirator, blood, body) in
|
foreach (var (respirator, blood, body) in
|
||||||
EntityManager.EntityQuery<RespiratorComponent, BloodstreamComponent, SharedBodyComponent>())
|
EntityManager.EntityQuery<RespiratorComponent, BloodstreamComponent, SharedBodyComponent>())
|
||||||
{
|
{
|
||||||
var uid = (respirator).Owner;
|
var uid = respirator.Owner;
|
||||||
if (!EntityManager.TryGetComponent<MobStateComponent>(uid, out var state) ||
|
if (!EntityManager.TryGetComponent<MobStateComponent>(uid, out var state) ||
|
||||||
state.IsDead())
|
state.IsDead())
|
||||||
{
|
{
|
||||||
return;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
respirator.AccumulatedFrametime += frameTime;
|
respirator.AccumulatedFrametime += frameTime;
|
||||||
|
|
||||||
if (respirator.AccumulatedFrametime < 1)
|
if (respirator.AccumulatedFrametime < 1)
|
||||||
{
|
{
|
||||||
return;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
ProcessGases(uid, respirator, frameTime, blood, body);
|
ProcessGases(uid, respirator, blood, body);
|
||||||
|
|
||||||
respirator.AccumulatedFrametime -= 1;
|
respirator.AccumulatedFrametime -= 1;
|
||||||
|
|
||||||
if (SuffocatingPercentage(respirator) > 0)
|
if (SuffocatingPercentage(respirator) > 0)
|
||||||
{
|
{
|
||||||
TakeSuffocationDamage(uid, respirator);
|
TakeSuffocationDamage(uid, respirator);
|
||||||
return;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
StopSuffocation(uid, respirator);
|
StopSuffocation(uid, respirator);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Dictionary<Gas, float> NeedsAndDeficit(RespiratorComponent respirator)
|
||||||
private Dictionary<Gas, float> NeedsAndDeficit(RespiratorComponent respirator, float frameTime)
|
|
||||||
{
|
{
|
||||||
var needs = new Dictionary<Gas, float>(respirator.NeedsGases);
|
var needs = new Dictionary<Gas, float>(respirator.NeedsGases);
|
||||||
foreach (var (gas, amount) in respirator.DeficitGases)
|
foreach (var (gas, amount) in respirator.DeficitGases)
|
||||||
{
|
{
|
||||||
var newAmount = (needs.GetValueOrDefault(gas) + amount) * frameTime;
|
var newAmount = (needs.GetValueOrDefault(gas) + amount);
|
||||||
needs[gas] = newAmount;
|
needs[gas] = newAmount;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,7 +129,7 @@ namespace Content.Server.Body.Systems
|
|||||||
return respirator.ProducesGases.ToDictionary(pair => pair.Key, pair => GasProducedMultiplier(respirator, pair.Key, usedAverage));
|
return respirator.ProducesGases.ToDictionary(pair => pair.Key, pair => GasProducedMultiplier(respirator, pair.Key, usedAverage));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ProcessGases(EntityUid uid, RespiratorComponent respirator, float frameTime,
|
private void ProcessGases(EntityUid uid, RespiratorComponent respirator,
|
||||||
BloodstreamComponent? bloodstream,
|
BloodstreamComponent? bloodstream,
|
||||||
SharedBodyComponent? body)
|
SharedBodyComponent? body)
|
||||||
{
|
{
|
||||||
@@ -139,12 +138,12 @@ namespace Content.Server.Body.Systems
|
|||||||
|
|
||||||
var lungs = _bodySystem.GetComponentsOnMechanisms<LungComponent>(uid, body).ToArray();
|
var lungs = _bodySystem.GetComponentsOnMechanisms<LungComponent>(uid, body).ToArray();
|
||||||
|
|
||||||
var needs = NeedsAndDeficit(respirator, frameTime);
|
var needs = NeedsAndDeficit(respirator);
|
||||||
var used = 0f;
|
var used = 0f;
|
||||||
|
|
||||||
foreach (var (lung, mech) in lungs)
|
foreach (var (lung, mech) in lungs)
|
||||||
{
|
{
|
||||||
_lungSystem.UpdateLung((lung).Owner, frameTime, lung, mech);
|
_lungSystem.UpdateLung(lung.Owner, lung, mech);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var (gas, amountNeeded) in needs)
|
foreach (var (gas, amountNeeded) in needs)
|
||||||
|
|||||||
@@ -1,18 +1,28 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Content.Server.Administration.Logs;
|
||||||
using Content.Server.Body.Components;
|
using Content.Server.Body.Components;
|
||||||
using Content.Server.Body.Systems;
|
using Content.Server.Body.Systems;
|
||||||
using Content.Server.Chemistry.Components.SolutionManager;
|
using Content.Server.Chemistry.Components.SolutionManager;
|
||||||
using Content.Server.Chemistry.EntitySystems;
|
using Content.Server.Chemistry.EntitySystems;
|
||||||
|
using Content.Server.CombatMode;
|
||||||
|
using Content.Server.DoAfter;
|
||||||
|
using Content.Shared.ActionBlocker;
|
||||||
|
using Content.Shared.Body.Components;
|
||||||
using Content.Shared.Chemistry.Components;
|
using Content.Shared.Chemistry.Components;
|
||||||
using Content.Shared.Chemistry.Reagent;
|
using Content.Shared.Chemistry.Reagent;
|
||||||
|
using Content.Shared.Database;
|
||||||
using Content.Shared.FixedPoint;
|
using Content.Shared.FixedPoint;
|
||||||
using Content.Shared.Interaction;
|
using Content.Shared.Interaction;
|
||||||
using Content.Shared.Interaction.Helpers;
|
using Content.Shared.Interaction.Helpers;
|
||||||
|
using Content.Shared.MobState.Components;
|
||||||
using Content.Shared.Popups;
|
using Content.Shared.Popups;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
using Robust.Shared.Localization;
|
using Robust.Shared.Localization;
|
||||||
|
using Robust.Shared.Player;
|
||||||
|
using Robust.Shared.Players;
|
||||||
using Robust.Shared.Serialization.Manager.Attributes;
|
using Robust.Shared.Serialization.Manager.Attributes;
|
||||||
using Robust.Shared.ViewVariables;
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
@@ -42,16 +52,26 @@ namespace Content.Server.Chemistry.Components
|
|||||||
/// Amount to inject or draw on each usage. If the injector is inject only, it will
|
/// Amount to inject or draw on each usage. If the injector is inject only, it will
|
||||||
/// attempt to inject it's entire contents upon use.
|
/// attempt to inject it's entire contents upon use.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ViewVariables]
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
[DataField("transferAmount")]
|
[DataField("transferAmount")]
|
||||||
private FixedPoint2 _transferAmount = FixedPoint2.New(5);
|
private FixedPoint2 _transferAmount = FixedPoint2.New(5);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initial storage volume of the injector
|
/// Injection delay (seconds) when the target is a mob.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ViewVariables]
|
/// <remarks>
|
||||||
[DataField("initialMaxVolume")]
|
/// The base delay has a minimum of 1 second, but this will still be modified if the target is incapacitated or
|
||||||
private FixedPoint2 _initialMaxVolume = FixedPoint2.New(15);
|
/// in combat mode.
|
||||||
|
/// </remarks>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
[DataField("delay")]
|
||||||
|
public float Delay = 5;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Token for interrupting a do-after action (e.g., injection another player). If not null, implies
|
||||||
|
/// component is currently "in use".
|
||||||
|
/// </summary>
|
||||||
|
public CancellationTokenSource? CancelToken;
|
||||||
|
|
||||||
private InjectorToggleMode _toggleState;
|
private InjectorToggleMode _toggleState;
|
||||||
|
|
||||||
@@ -112,9 +132,18 @@ namespace Content.Server.Chemistry.Components
|
|||||||
/// <param name="eventArgs"></param>
|
/// <param name="eventArgs"></param>
|
||||||
async Task<bool> IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
async Task<bool> IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
|
if (CancelToken != null)
|
||||||
|
{
|
||||||
|
CancelToken.Cancel();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!eventArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true))
|
if (!eventArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
if (!EntitySystem.Get<ActionBlockerSystem>().CanInteract(eventArgs.User))
|
||||||
|
return false;
|
||||||
|
|
||||||
var solutionsSys = EntitySystem.Get<SolutionContainerSystem>();
|
var solutionsSys = EntitySystem.Get<SolutionContainerSystem>();
|
||||||
//Make sure we have the attacking entity
|
//Make sure we have the attacking entity
|
||||||
if (eventArgs.Target is not {Valid: true} target ||
|
if (eventArgs.Target is not {Valid: true} target ||
|
||||||
@@ -123,6 +152,14 @@ namespace Content.Server.Chemistry.Components
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Is the target a mob? If yes, use a do-after to give them time to respond.
|
||||||
|
if (_entities.HasComponent<MobStateComponent>(target) ||
|
||||||
|
_entities.HasComponent<BloodstreamComponent>(target))
|
||||||
|
{
|
||||||
|
if (!await TryInjectDoAfter(eventArgs.User, target))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Handle injecting/drawing for solutions
|
// Handle injecting/drawing for solutions
|
||||||
if (ToggleState == InjectorToggleMode.Inject)
|
if (ToggleState == InjectorToggleMode.Inject)
|
||||||
{
|
{
|
||||||
@@ -162,6 +199,75 @@ namespace Content.Server.Chemistry.Components
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send informative pop-up messages and wait for a do-after to complete.
|
||||||
|
/// </summary>
|
||||||
|
public async Task<bool> TryInjectDoAfter(EntityUid user, EntityUid target)
|
||||||
|
{
|
||||||
|
var popupSys = EntitySystem.Get<SharedPopupSystem>();
|
||||||
|
|
||||||
|
// Create a pop-up for the user
|
||||||
|
popupSys.PopupEntity(Loc.GetString("injector-component-injecting-user"), target, Filter.Entities(user));
|
||||||
|
|
||||||
|
// Get entity for logging. Log with EntityUids when?
|
||||||
|
var logSys = EntitySystem.Get<AdminLogSystem>();
|
||||||
|
|
||||||
|
var actualDelay = MathF.Max(Delay, 1f);
|
||||||
|
if (user != target)
|
||||||
|
{
|
||||||
|
// Create a pop-up for the target
|
||||||
|
var userName = _entities.GetComponent<MetaDataComponent>(user).EntityName;
|
||||||
|
popupSys.PopupEntity(Loc.GetString("injector-component-injecting-target",
|
||||||
|
("user", userName)), user, Filter.Entities(target));
|
||||||
|
|
||||||
|
// Check if the target is incapacitated or in combat mode and modify time accordingly.
|
||||||
|
if (_entities.TryGetComponent<MobStateComponent>(target, out var mobState) &&
|
||||||
|
mobState.IsIncapacitated())
|
||||||
|
{
|
||||||
|
actualDelay /= 2;
|
||||||
|
}
|
||||||
|
else if (_entities.TryGetComponent<CombatModeComponent>(target, out var combat) &&
|
||||||
|
combat.IsInCombatMode)
|
||||||
|
{
|
||||||
|
// Slightly increase the delay when the target is in combat mode. Helps prevents cheese injections in
|
||||||
|
// combat with fast syringes & lag.
|
||||||
|
actualDelay += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add an admin log, using the "force feed" log type. It's not quite feeding, but the effect is the same.
|
||||||
|
if (ToggleState == InjectorToggleMode.Inject)
|
||||||
|
{
|
||||||
|
logSys.Add(LogType.ForceFeed,
|
||||||
|
$"{_entities.ToPrettyString(user)} is attempting to inject a solution into {_entities.ToPrettyString(target)}");
|
||||||
|
// TODO solution pretty string.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Self-injections take half as long.
|
||||||
|
actualDelay /= 2;
|
||||||
|
|
||||||
|
if (ToggleState == InjectorToggleMode.Inject)
|
||||||
|
logSys.Add(LogType.Ingestion,
|
||||||
|
$"{_entities.ToPrettyString(user)} is attempting to inject themselves with a solution.");
|
||||||
|
//TODO solution pretty string.
|
||||||
|
}
|
||||||
|
|
||||||
|
CancelToken = new();
|
||||||
|
var status = await EntitySystem.Get<DoAfterSystem>().WaitDoAfter(
|
||||||
|
new DoAfterEventArgs(user, actualDelay, CancelToken.Token, target)
|
||||||
|
{
|
||||||
|
BreakOnUserMove = true,
|
||||||
|
BreakOnDamage = true,
|
||||||
|
BreakOnStun = true,
|
||||||
|
BreakOnTargetMove = true,
|
||||||
|
MovementThreshold = 1.0f
|
||||||
|
});
|
||||||
|
CancelToken = null;
|
||||||
|
|
||||||
|
return status == DoAfterStatus.Finished;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called when use key is pressed when held in active hand
|
/// Called when use key is pressed when held in active hand
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
using Content.Server.Chemistry.Components;
|
using Content.Server.Chemistry.Components;
|
||||||
|
using Content.Shared.Hands;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace Content.Server.Chemistry.EntitySystems
|
namespace Content.Server.Chemistry.EntitySystems
|
||||||
{
|
{
|
||||||
@@ -12,6 +14,16 @@ namespace Content.Server.Chemistry.EntitySystems
|
|||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
|
||||||
SubscribeLocalEvent<InjectorComponent, SolutionChangedEvent>(OnSolutionChange);
|
SubscribeLocalEvent<InjectorComponent, SolutionChangedEvent>(OnSolutionChange);
|
||||||
|
SubscribeLocalEvent<InjectorComponent, HandDeselectedEvent>(OnInjectorDeselected);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnInjectorDeselected(EntityUid uid, InjectorComponent component, HandDeselectedEvent args)
|
||||||
|
{
|
||||||
|
if (component.CancelToken != null)
|
||||||
|
{
|
||||||
|
component.CancelToken.Cancel();
|
||||||
|
component.CancelToken = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnSolutionChange(EntityUid uid, InjectorComponent component, SolutionChangedEvent args)
|
private void OnSolutionChange(EntityUid uid, InjectorComponent component, SolutionChangedEvent args)
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Text;
|
||||||
using Content.Server.Chemistry.Components.SolutionManager;
|
using Content.Server.Chemistry.Components.SolutionManager;
|
||||||
|
using Content.Server.Database;
|
||||||
using Content.Shared.Chemistry.Components;
|
using Content.Shared.Chemistry.Components;
|
||||||
using Content.Shared.Chemistry.Reagent;
|
using Content.Shared.Chemistry.Reagent;
|
||||||
using Content.Shared.FixedPoint;
|
using Content.Shared.FixedPoint;
|
||||||
@@ -128,5 +130,27 @@ namespace Content.Server.Chemistry.EntitySystems
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string ToPrettyString(Solution solution)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.Append("Solution content: [");
|
||||||
|
var first = true;
|
||||||
|
foreach (var (id, quantity) in solution.Contents)
|
||||||
|
{
|
||||||
|
if (first)
|
||||||
|
{
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.Append(", ");
|
||||||
|
}
|
||||||
|
sb.AppendFormat("{0}: {1}u", id, quantity);
|
||||||
|
|
||||||
|
}
|
||||||
|
sb.Append(']');
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,12 +15,17 @@ public class Electrocute : ReagentEffect
|
|||||||
|
|
||||||
[DataField("electrocuteDamageScale")] public int ElectrocuteDamageScale = 5;
|
[DataField("electrocuteDamageScale")] public int ElectrocuteDamageScale = 5;
|
||||||
|
|
||||||
|
/// <remarks>
|
||||||
|
/// true - refresh electrocute time, false - accumulate electrocute time
|
||||||
|
/// </remarks>
|
||||||
|
[DataField("refresh")] public bool Refresh = true;
|
||||||
|
|
||||||
public override bool ShouldLog => true;
|
public override bool ShouldLog => true;
|
||||||
|
|
||||||
public override void Effect(ReagentEffectArgs args)
|
public override void Effect(ReagentEffectArgs args)
|
||||||
{
|
{
|
||||||
EntitySystem.Get<ElectrocutionSystem>().TryDoElectrocution(args.SolutionEntity, null,
|
EntitySystem.Get<ElectrocutionSystem>().TryDoElectrocution(args.SolutionEntity, null,
|
||||||
Math.Max((args.Quantity * ElectrocuteDamageScale).Int(), 1), TimeSpan.FromSeconds(ElectrocuteTime));
|
Math.Max((args.Quantity * ElectrocuteDamageScale).Int(), 1), TimeSpan.FromSeconds(ElectrocuteTime), Refresh);
|
||||||
|
|
||||||
args.Source?.RemoveReagent(args.Reagent.ID, args.Quantity);
|
args.Source?.RemoveReagent(args.Reagent.ID, args.Quantity);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,12 @@ namespace Content.Server.Chemistry.ReagentEffects.StatusEffects
|
|||||||
[DataField("time")]
|
[DataField("time")]
|
||||||
public float Time = 2.0f;
|
public float Time = 2.0f;
|
||||||
|
|
||||||
|
/// <remarks>
|
||||||
|
/// true - refresh status effect time, false - accumulate status effect time
|
||||||
|
/// </remarks>
|
||||||
|
[DataField("refresh")]
|
||||||
|
public bool Refresh = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Should this effect add the status effect, remove time from it, or set its cooldown?
|
/// Should this effect add the status effect, remove time from it, or set its cooldown?
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -41,7 +47,7 @@ namespace Content.Server.Chemistry.ReagentEffects.StatusEffects
|
|||||||
var statusSys = args.EntityManager.EntitySysManager.GetEntitySystem<StatusEffectsSystem>();
|
var statusSys = args.EntityManager.EntitySysManager.GetEntitySystem<StatusEffectsSystem>();
|
||||||
if (Type == StatusEffectMetabolismType.Add && Component != String.Empty)
|
if (Type == StatusEffectMetabolismType.Add && Component != String.Empty)
|
||||||
{
|
{
|
||||||
statusSys.TryAddStatusEffect(args.SolutionEntity, Key, TimeSpan.FromSeconds(Time), Component);
|
statusSys.TryAddStatusEffect(args.SolutionEntity, Key, TimeSpan.FromSeconds(Time), Refresh, Component);
|
||||||
}
|
}
|
||||||
else if (Type == StatusEffectMetabolismType.Remove)
|
else if (Type == StatusEffectMetabolismType.Remove)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -23,10 +23,16 @@ namespace Content.Server.Chemistry.ReagentEffects.StatusEffects
|
|||||||
[DataField("time")]
|
[DataField("time")]
|
||||||
public float Time = 2.0f;
|
public float Time = 2.0f;
|
||||||
|
|
||||||
|
/// <remarks>
|
||||||
|
/// true - refresh jitter time, false - accumulate jitter time
|
||||||
|
/// </remarks>
|
||||||
|
[DataField("refresh")]
|
||||||
|
public bool Refresh = true;
|
||||||
|
|
||||||
public override void Effect(ReagentEffectArgs args)
|
public override void Effect(ReagentEffectArgs args)
|
||||||
{
|
{
|
||||||
args.EntityManager.EntitySysManager.GetEntitySystem<SharedJitteringSystem>()
|
args.EntityManager.EntitySysManager.GetEntitySystem<SharedJitteringSystem>()
|
||||||
.DoJitter(args.SolutionEntity, TimeSpan.FromSeconds(Time), Amplitude, Frequency);
|
.DoJitter(args.SolutionEntity, TimeSpan.FromSeconds(Time), Refresh, Amplitude, Frequency);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Server.Cleanable;
|
using Content.Server.Cleanable;
|
||||||
using Content.Server.Coordinates.Helpers;
|
using Content.Server.Coordinates.Helpers;
|
||||||
|
using Content.Server.Decals;
|
||||||
using Content.Shared.Chemistry.Reaction;
|
using Content.Shared.Chemistry.Reaction;
|
||||||
using Content.Shared.Chemistry.Reagent;
|
using Content.Shared.Chemistry.Reagent;
|
||||||
using Content.Shared.FixedPoint;
|
using Content.Shared.FixedPoint;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
using Robust.Shared.Serialization.Manager.Attributes;
|
using Robust.Shared.Serialization.Manager.Attributes;
|
||||||
|
|
||||||
namespace Content.Server.Chemistry.TileReactions
|
namespace Content.Server.Chemistry.TileReactions
|
||||||
@@ -36,6 +38,12 @@ namespace Content.Server.Chemistry.TileReactions
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var decalSystem = EntitySystem.Get<DecalSystem>();
|
||||||
|
foreach (var uid in decalSystem.GetDecalsInRange(tile.GridIndex, tile.GridIndices+new Vector2(0.5f, 0.5f), validDelegate: x => x.Cleanable))
|
||||||
|
{
|
||||||
|
decalSystem.RemoveDecal(tile.GridIndex, uid);
|
||||||
|
}
|
||||||
|
|
||||||
return amount;
|
return amount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
using Content.Server.Fluids.Components;
|
using Content.Server.Fluids.Components;
|
||||||
|
using Content.Server.Fluids.EntitySystems;
|
||||||
using Content.Shared.Chemistry.Components;
|
using Content.Shared.Chemistry.Components;
|
||||||
using Content.Shared.Chemistry.Reaction;
|
using Content.Shared.Chemistry.Reaction;
|
||||||
using Content.Shared.Chemistry.Reagent;
|
using Content.Shared.Chemistry.Reagent;
|
||||||
using Content.Shared.FixedPoint;
|
using Content.Shared.FixedPoint;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Serialization.Manager.Attributes;
|
using Robust.Shared.Serialization.Manager.Attributes;
|
||||||
|
|
||||||
@@ -15,9 +17,12 @@ namespace Content.Server.Chemistry.TileReactions
|
|||||||
{
|
{
|
||||||
public FixedPoint2 TileReact(TileRef tile, ReagentPrototype reagent, FixedPoint2 reactVolume)
|
public FixedPoint2 TileReact(TileRef tile, ReagentPrototype reagent, FixedPoint2 reactVolume)
|
||||||
{
|
{
|
||||||
if (reactVolume < 5 || !tile.TryGetPuddle(null, out _)) return FixedPoint2.Zero;
|
var spillSystem = EntitySystem.Get<SpillableSystem>();
|
||||||
|
if (reactVolume < 5 || !spillSystem.TryGetPuddle(tile, out _)) return FixedPoint2.Zero;
|
||||||
|
|
||||||
return tile.SpillAt(new Solution(reagent.ID, reactVolume), "PuddleSmear", true, false, true) != null ? reactVolume : FixedPoint2.Zero;
|
return spillSystem.SpillAt(tile,new Solution(reagent.ID, reactVolume), "PuddleSmear", true, false, true) != null
|
||||||
|
? reactVolume
|
||||||
|
: FixedPoint2.Zero;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Content.Server.Fluids.Components;
|
using Content.Server.Fluids.Components;
|
||||||
|
using Content.Server.Fluids.EntitySystems;
|
||||||
using Content.Shared.Chemistry.Components;
|
using Content.Shared.Chemistry.Components;
|
||||||
using Content.Shared.Chemistry.Reaction;
|
using Content.Shared.Chemistry.Reaction;
|
||||||
using Content.Shared.Chemistry.Reagent;
|
using Content.Shared.Chemistry.Reagent;
|
||||||
@@ -26,7 +27,8 @@ namespace Content.Server.Chemistry.TileReactions
|
|||||||
if (reactVolume < 5) return FixedPoint2.Zero;
|
if (reactVolume < 5) return FixedPoint2.Zero;
|
||||||
|
|
||||||
// TODO Make this not puddle smear.
|
// TODO Make this not puddle smear.
|
||||||
var puddle = tile.SpillAt(new Solution(reagent.ID, reactVolume), "PuddleSmear", _overflow, false, true);
|
var puddle = EntitySystem.Get<SpillableSystem>()
|
||||||
|
.SpillAt(tile, new Solution(reagent.ID, reactVolume), "PuddleSmear", _overflow, false, true);
|
||||||
|
|
||||||
if (puddle != null)
|
if (puddle != null)
|
||||||
{
|
{
|
||||||
|
|||||||
60
Content.Server/Construction/Conditions/Locked.cs
Normal file
60
Content.Server/Construction/Conditions/Locked.cs
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Content.Server.Storage.Components;
|
||||||
|
using Content.Shared.Construction;
|
||||||
|
using Content.Shared.Examine;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Localization;
|
||||||
|
using Robust.Shared.Serialization.Manager.Attributes;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
|
namespace Content.Server.Construction.Conditions
|
||||||
|
{
|
||||||
|
[UsedImplicitly]
|
||||||
|
[DataDefinition]
|
||||||
|
public class Locked : IGraphCondition
|
||||||
|
{
|
||||||
|
[DataField("locked")]
|
||||||
|
public bool IsLocked { get; private set; } = true;
|
||||||
|
|
||||||
|
public bool Condition(EntityUid uid, IEntityManager entityManager)
|
||||||
|
{
|
||||||
|
if (!entityManager.TryGetComponent(uid, out LockComponent? lockcomp))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return lockcomp.Locked == IsLocked;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool DoExamine(ExaminedEvent args)
|
||||||
|
{
|
||||||
|
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||||
|
var entity = args.Examined;
|
||||||
|
|
||||||
|
if (!entMan.TryGetComponent(entity, out LockComponent? lockcomp)) return false;
|
||||||
|
|
||||||
|
switch (IsLocked)
|
||||||
|
{
|
||||||
|
case true when !lockcomp.Locked:
|
||||||
|
args.PushMarkup(Loc.GetString("construction-examine-condition-lock"));
|
||||||
|
return true;
|
||||||
|
case false when lockcomp.Locked:
|
||||||
|
args.PushMarkup(Loc.GetString("construction-examine-condition-unlock"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<ConstructionGuideEntry> GenerateGuideEntry()
|
||||||
|
{
|
||||||
|
yield return new ConstructionGuideEntry()
|
||||||
|
{
|
||||||
|
Localization = IsLocked
|
||||||
|
? "construction-step-condition-wire-panel-lock"
|
||||||
|
: "construction-step-condition-wire-panel-unlock"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
59
Content.Server/Construction/Conditions/StorageWelded.cs
Normal file
59
Content.Server/Construction/Conditions/StorageWelded.cs
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Server.Storage.Components;
|
||||||
|
using Content.Shared.Construction;
|
||||||
|
using Content.Shared.Examine;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Localization;
|
||||||
|
using Robust.Shared.Serialization.Manager.Attributes;
|
||||||
|
|
||||||
|
namespace Content.Server.Construction.Conditions
|
||||||
|
{
|
||||||
|
[UsedImplicitly]
|
||||||
|
[DataDefinition]
|
||||||
|
public class StorageWelded : IGraphCondition
|
||||||
|
{
|
||||||
|
[DataField("welded")]
|
||||||
|
public bool Welded { get; private set; } = true;
|
||||||
|
|
||||||
|
public bool Condition(EntityUid uid, IEntityManager entityManager)
|
||||||
|
{
|
||||||
|
if (!entityManager.TryGetComponent(uid, out EntityStorageComponent? entityStorageComponent))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return entityStorageComponent.IsWeldedShut == Welded;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool DoExamine(ExaminedEvent args)
|
||||||
|
{
|
||||||
|
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||||
|
var entity = args.Examined;
|
||||||
|
|
||||||
|
if (!entMan.TryGetComponent(entity, out EntityStorageComponent? entityStorage)) return false;
|
||||||
|
|
||||||
|
var metaData = entMan.GetComponent<MetaDataComponent>(entity);
|
||||||
|
|
||||||
|
if (entityStorage.IsWeldedShut != Welded)
|
||||||
|
{
|
||||||
|
if (Welded == true)
|
||||||
|
args.PushMarkup(Loc.GetString("construction-examine-condition-door-weld", ("entityName", metaData.EntityName)) + "\n");
|
||||||
|
else
|
||||||
|
args.PushMarkup(Loc.GetString("construction-examine-condition-door-unweld", ("entityName", metaData.EntityName)) + "\n");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<ConstructionGuideEntry> GenerateGuideEntry()
|
||||||
|
{
|
||||||
|
yield return new ConstructionGuideEntry()
|
||||||
|
{
|
||||||
|
Localization = Welded
|
||||||
|
? "construction-guide-condition-door-weld"
|
||||||
|
: "construction-guide-condition-door-unweld",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -59,7 +59,7 @@ namespace Content.Server.Conveyor
|
|||||||
signal != TwoWayLeverSignal.Middle)
|
signal != TwoWayLeverSignal.Middle)
|
||||||
{
|
{
|
||||||
args.Cancel();
|
args.Cancel();
|
||||||
_stunSystem.TryParalyze(uid, TimeSpan.FromSeconds(2f));
|
_stunSystem.TryParalyze(uid, TimeSpan.FromSeconds(2f), true);
|
||||||
component.Owner.PopupMessage(args.Attemptee, Loc.GetString("conveyor-component-failed-link"));
|
component.Owner.PopupMessage(args.Attemptee, Loc.GetString("conveyor-component-failed-link"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ using System.Linq;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Content.Server.Administration.Logs;
|
using Content.Server.Administration.Logs;
|
||||||
using Content.Server.UserInterface;
|
using Content.Server.UserInterface;
|
||||||
using Content.Shared.Administration.Logs;
|
|
||||||
using Content.Shared.Audio;
|
using Content.Shared.Audio;
|
||||||
using Content.Shared.Crayon;
|
using Content.Shared.Crayon;
|
||||||
|
using Content.Server.Decals;
|
||||||
|
using Content.Shared.Decals;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
using Content.Shared.Interaction;
|
using Content.Shared.Interaction;
|
||||||
using Content.Shared.Interaction.Helpers;
|
using Content.Shared.Interaction.Helpers;
|
||||||
@@ -60,11 +61,8 @@ namespace Content.Server.Crayon
|
|||||||
Charges = Capacity;
|
Charges = Capacity;
|
||||||
|
|
||||||
// Get the first one from the catalog and set it as default
|
// Get the first one from the catalog and set it as default
|
||||||
var decals = _prototypeManager.EnumeratePrototypes<CrayonDecalPrototype>().FirstOrDefault();
|
var decal = _prototypeManager.EnumeratePrototypes<DecalPrototype>().FirstOrDefault(x => x.Tags.Contains("crayon"));
|
||||||
if (decals != null)
|
SelectedState = decal?.ID ?? string.Empty;
|
||||||
{
|
|
||||||
SelectedState = decals.Decals.First();
|
|
||||||
}
|
|
||||||
Dirty();
|
Dirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,14 +72,10 @@ namespace Content.Server.Crayon
|
|||||||
{
|
{
|
||||||
case CrayonSelectMessage msg:
|
case CrayonSelectMessage msg:
|
||||||
// Check if the selected state is valid
|
// Check if the selected state is valid
|
||||||
var crayonDecals = _prototypeManager.EnumeratePrototypes<CrayonDecalPrototype>().FirstOrDefault();
|
if (_prototypeManager.TryIndex<DecalPrototype>(msg.State, out var prototype) && prototype.Tags.Contains("crayon"))
|
||||||
if (crayonDecals != null)
|
|
||||||
{
|
{
|
||||||
if (crayonDecals.Decals.Contains(msg.State))
|
SelectedState = msg.State;
|
||||||
{
|
Dirty();
|
||||||
SelectedState = msg.State;
|
|
||||||
Dirty();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -131,12 +125,8 @@ namespace Content.Server.Crayon
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var entity = IoCManager.Resolve<IEntityManager>().SpawnEntity("CrayonDecal", eventArgs.ClickLocation);
|
if(!EntitySystem.Get<DecalSystem>().TryAddDecal(SelectedState, eventArgs.ClickLocation.Offset(new Vector2(-0.5f,-0.5f)), out _, Color.FromName(_color), cleanable: true))
|
||||||
if (IoCManager.Resolve<IEntityManager>().TryGetComponent(entity, out AppearanceComponent? appearance))
|
return false;
|
||||||
{
|
|
||||||
appearance.SetData(CrayonVisuals.State, SelectedState);
|
|
||||||
appearance.SetData(CrayonVisuals.Color, _color);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_useSound != null)
|
if (_useSound != null)
|
||||||
SoundSystem.Play(Filter.Pvs(Owner), _useSound.GetSound(), Owner, AudioHelpers.WithVariation(0.125f));
|
SoundSystem.Play(Filter.Pvs(Owner), _useSound.GetSound(), Owner, AudioHelpers.WithVariation(0.125f));
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ namespace Content.Server.Damage.Systems
|
|||||||
component.LastHit = _gameTiming.CurTime;
|
component.LastHit = _gameTiming.CurTime;
|
||||||
|
|
||||||
if (_robustRandom.Prob(component.StunChance))
|
if (_robustRandom.Prob(component.StunChance))
|
||||||
_stunSystem.TryStun(uid, TimeSpan.FromSeconds(component.StunSeconds));
|
_stunSystem.TryStun(uid, TimeSpan.FromSeconds(component.StunSeconds), true);
|
||||||
|
|
||||||
var damageScale = (speed / component.MinimumSpeed) * component.Factor;
|
var damageScale = (speed / component.MinimumSpeed) * component.Factor;
|
||||||
|
|
||||||
|
|||||||
118
Content.Server/Decals/Commands/AddDecalCommand.cs
Normal file
118
Content.Server/Decals/Commands/AddDecalCommand.cs
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Server.Administration;
|
||||||
|
using Content.Shared.Administration;
|
||||||
|
using Content.Shared.Decals;
|
||||||
|
using Content.Shared.Maps;
|
||||||
|
using Robust.Shared.Console;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Server.Decals.Commands
|
||||||
|
{
|
||||||
|
[AdminCommand(AdminFlags.Mapping)]
|
||||||
|
public sealed class AddDecalCommand : IConsoleCommand
|
||||||
|
{
|
||||||
|
public string Command => "adddecal";
|
||||||
|
public string Description => "Creates a decal on the map";
|
||||||
|
public string Help => $"{Command} <id> <x position> <y position> <gridId> [angle=<angle> zIndex=<zIndex> color=<color>]";
|
||||||
|
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||||
|
{
|
||||||
|
if (args.Length < 4 || args.Length > 7)
|
||||||
|
{
|
||||||
|
shell.WriteError($"Received invalid amount of arguments arguments. Expected 4 to 7, got {args.Length}.\nUsage: {Help}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IoCManager.Resolve<IPrototypeManager>().HasIndex<DecalPrototype>(args[0]))
|
||||||
|
{
|
||||||
|
shell.WriteError($"Cannot find decalprototype '{args[0]}'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!float.TryParse(args[1], out var x))
|
||||||
|
{
|
||||||
|
shell.WriteError($"Failed parsing x-coordinate '{args[1]}'.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!float.TryParse(args[2], out var y))
|
||||||
|
{
|
||||||
|
shell.WriteError($"Failed parsing y-coordinate'{args[2]}'.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var mapManager = IoCManager.Resolve<IMapManager>();
|
||||||
|
if (!int.TryParse(args[3], out var gridIdRaw) || !mapManager.TryGetGrid(new GridId(gridIdRaw), out var grid))
|
||||||
|
{
|
||||||
|
shell.WriteError($"Failed parsing gridId '{args[3]}'.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var coordinates = new EntityCoordinates(grid.GridEntityId, new Vector2(x, y));
|
||||||
|
if (grid.GetTileRef(coordinates).IsSpace())
|
||||||
|
{
|
||||||
|
shell.WriteError($"Cannot create decal on space tile at {coordinates}.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color? color = null;
|
||||||
|
var zIndex = 0;
|
||||||
|
Angle? rotation = null;
|
||||||
|
if (args.Length > 4)
|
||||||
|
{
|
||||||
|
for (int i = 4; i < args.Length; i++)
|
||||||
|
{
|
||||||
|
var rawValue = args[i].Split('=');
|
||||||
|
if (rawValue.Length != 2)
|
||||||
|
{
|
||||||
|
shell.WriteError($"Failed parsing parameter: '{args[i]}'");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (rawValue[0])
|
||||||
|
{
|
||||||
|
case "angle":
|
||||||
|
if (!double.TryParse(rawValue[1], out var degrees))
|
||||||
|
{
|
||||||
|
shell.WriteError($"Failed parsing angle '{rawValue[1]}'.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
rotation = Angle.FromDegrees(degrees);
|
||||||
|
break;
|
||||||
|
case "zIndex":
|
||||||
|
if (!int.TryParse(rawValue[1], out zIndex))
|
||||||
|
{
|
||||||
|
shell.WriteError($"Failed parsing zIndex '{rawValue[1]}'.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "color":
|
||||||
|
if (!Color.TryFromName(rawValue[1], out var colorRaw))
|
||||||
|
{
|
||||||
|
shell.WriteError($"Failed parsing color '{rawValue[1]}'.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
color = colorRaw;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
shell.WriteError($"Unknown parameter key '{rawValue[0]}'.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(EntitySystem.Get<DecalSystem>().TryAddDecal(args[0], coordinates, out var uid, color, rotation, zIndex))
|
||||||
|
{
|
||||||
|
shell.WriteLine($"Successfully created decal {uid}.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
shell.WriteError($"Failed adding decal.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
162
Content.Server/Decals/Commands/EditDecalCommand.cs
Normal file
162
Content.Server/Decals/Commands/EditDecalCommand.cs
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
using Content.Server.Administration;
|
||||||
|
using Content.Shared.Administration;
|
||||||
|
using Robust.Shared.Console;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
|
||||||
|
namespace Content.Server.Decals;
|
||||||
|
|
||||||
|
[AdminCommand(AdminFlags.Mapping)]
|
||||||
|
public class EditDecalCommand : IConsoleCommand
|
||||||
|
{
|
||||||
|
public string Command => "editdecal";
|
||||||
|
public string Description => "Edits a decal.";
|
||||||
|
public string Help => $@"{Command} <gridId> <uid> <mode>\n
|
||||||
|
Possible modes are:\n
|
||||||
|
- position <x position> <y position>\n
|
||||||
|
- color <color>\n
|
||||||
|
- id <id>\n
|
||||||
|
- rotation <degrees>\n
|
||||||
|
- zindex <zIndex>\n
|
||||||
|
- clean <cleanable>
|
||||||
|
";
|
||||||
|
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||||
|
{
|
||||||
|
if (args.Length < 4)
|
||||||
|
{
|
||||||
|
shell.WriteError("Expected at least 5 arguments.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!int.TryParse(args[0], out var gridIdRaw))
|
||||||
|
{
|
||||||
|
shell.WriteError($"Failed parsing gridId '{args[3]}'.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!uint.TryParse(args[1], out var uid))
|
||||||
|
{
|
||||||
|
shell.WriteError($"Failed parsing uid '{args[1]}'.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var gridId = new GridId(gridIdRaw);
|
||||||
|
if (!IoCManager.Resolve<IMapManager>().GridExists(gridId))
|
||||||
|
{
|
||||||
|
shell.WriteError($"No grid with gridId {gridId} exists.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var decalSystem = EntitySystem.Get<DecalSystem>();
|
||||||
|
switch (args[2].ToLower())
|
||||||
|
{
|
||||||
|
case "position":
|
||||||
|
if(args.Length != 5)
|
||||||
|
{
|
||||||
|
shell.WriteError("Expected 6 arguments.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!float.TryParse(args[3], out var x) || !float.TryParse(args[4], out var y))
|
||||||
|
{
|
||||||
|
shell.WriteError("Failed parsing position.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!decalSystem.SetDecalPosition(gridId, uid, gridId, new Vector2(x, y)))
|
||||||
|
{
|
||||||
|
shell.WriteError("Failed changing decalposition.");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "color":
|
||||||
|
if(args.Length != 4)
|
||||||
|
{
|
||||||
|
shell.WriteError("Expected 5 arguments.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Color.TryFromName(args[3], out var color))
|
||||||
|
{
|
||||||
|
shell.WriteError("Failed parsing color.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!decalSystem.SetDecalColor(gridId, uid, color))
|
||||||
|
{
|
||||||
|
shell.WriteError("Failed changing decal color.");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "id":
|
||||||
|
if(args.Length != 4)
|
||||||
|
{
|
||||||
|
shell.WriteError("Expected 5 arguments.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!decalSystem.SetDecalId(gridId, uid, args[3]))
|
||||||
|
{
|
||||||
|
shell.WriteError("Failed changing decal id.");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "rotation":
|
||||||
|
if(args.Length != 4)
|
||||||
|
{
|
||||||
|
shell.WriteError("Expected 5 arguments.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!double.TryParse(args[3], out var degrees))
|
||||||
|
{
|
||||||
|
shell.WriteError("Failed parsing degrees.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!decalSystem.SetDecalRotation(gridId, uid, Angle.FromDegrees(degrees)))
|
||||||
|
{
|
||||||
|
shell.WriteError("Failed changing decal rotation.");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "zindex":
|
||||||
|
if(args.Length != 4)
|
||||||
|
{
|
||||||
|
shell.WriteError("Expected 5 arguments.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!int.TryParse(args[3], out var zIndex))
|
||||||
|
{
|
||||||
|
shell.WriteError("Failed parsing zIndex.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!decalSystem.SetDecalZIndex(gridId, uid, zIndex))
|
||||||
|
{
|
||||||
|
shell.WriteError("Failed changing decal zIndex.");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "clean":
|
||||||
|
if(args.Length != 4)
|
||||||
|
{
|
||||||
|
shell.WriteError("Expected 5 arguments.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!bool.TryParse(args[3], out var cleanable))
|
||||||
|
{
|
||||||
|
shell.WriteError("Failed parsing cleanable.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!decalSystem.SetDecalCleanable(gridId, uid, cleanable))
|
||||||
|
{
|
||||||
|
shell.WriteError("Failed changing decal cleanable flag.");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
shell.WriteError("Invalid mode.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
46
Content.Server/Decals/Commands/RemoveDecalCommand.cs
Normal file
46
Content.Server/Decals/Commands/RemoveDecalCommand.cs
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
using Content.Server.Administration;
|
||||||
|
using Content.Shared.Administration;
|
||||||
|
using Robust.Shared.Console;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
|
||||||
|
namespace Content.Server.Decals.Commands
|
||||||
|
{
|
||||||
|
[AdminCommand(AdminFlags.Mapping)]
|
||||||
|
public class RemoveDecalCommand : IConsoleCommand
|
||||||
|
{
|
||||||
|
public string Command => "rmdecal";
|
||||||
|
public string Description => "removes a decal";
|
||||||
|
public string Help => $"{Command} <uid> <gridId>";
|
||||||
|
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||||
|
{
|
||||||
|
if (args.Length != 2)
|
||||||
|
{
|
||||||
|
shell.WriteError($"Unexpected number of arguments.\nExpected two: {Help}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!uint.TryParse(args[0], out var uid))
|
||||||
|
{
|
||||||
|
shell.WriteError($"Failed parsing uid.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!int.TryParse(args[1], out var rawGridId) ||
|
||||||
|
!IoCManager.Resolve<IMapManager>().GridExists(new GridId(rawGridId)))
|
||||||
|
{
|
||||||
|
shell.WriteError("Failed parsing gridId.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var decalSystem = EntitySystem.Get<DecalSystem>();
|
||||||
|
if (decalSystem.RemoveDecal(new GridId(rawGridId), uid))
|
||||||
|
{
|
||||||
|
shell.WriteLine($"Successfully removed decal {uid}.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
shell.WriteError($"Failed trying to remove decal {uid}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
326
Content.Server/Decals/DecalSystem.cs
Normal file
326
Content.Server/Decals/DecalSystem.cs
Normal file
@@ -0,0 +1,326 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using Content.Shared.Decals;
|
||||||
|
using Content.Shared.Maps;
|
||||||
|
using Robust.Server.Player;
|
||||||
|
using Robust.Shared.Enums;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
using Robust.Shared.Player;
|
||||||
|
|
||||||
|
namespace Content.Server.Decals
|
||||||
|
{
|
||||||
|
public class DecalSystem : SharedDecalSystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||||
|
|
||||||
|
private readonly Dictionary<GridId, HashSet<Vector2i>> _dirtyChunks = new();
|
||||||
|
private readonly Dictionary<IPlayerSession, Dictionary<GridId, HashSet<Vector2i>>> _previousSentChunks = new();
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
|
||||||
|
MapManager.TileChanged += OnTileChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Shutdown()
|
||||||
|
{
|
||||||
|
base.Shutdown();
|
||||||
|
|
||||||
|
_playerManager.PlayerStatusChanged -= OnPlayerStatusChanged;
|
||||||
|
MapManager.TileChanged -= OnTileChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnTileChanged(object? sender, TileChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (!e.NewTile.IsSpace())
|
||||||
|
return;
|
||||||
|
|
||||||
|
var chunkCollection = ChunkCollection(e.NewTile.GridIndex);
|
||||||
|
var indices = GetChunkIndices(e.NewTile.GridIndices);
|
||||||
|
var toDelete = new HashSet<uint>();
|
||||||
|
if (chunkCollection.TryGetValue(indices, out var chunk))
|
||||||
|
{
|
||||||
|
foreach (var (uid, decal) in chunk)
|
||||||
|
{
|
||||||
|
if (new Vector2((int) Math.Floor(decal.Coordinates.X), (int) Math.Floor(decal.Coordinates.Y)) ==
|
||||||
|
e.NewTile.GridIndices)
|
||||||
|
{
|
||||||
|
toDelete.Add(uid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toDelete.Count == 0) return;
|
||||||
|
|
||||||
|
foreach (var uid in toDelete)
|
||||||
|
{
|
||||||
|
RemoveDecalInternal(e.NewTile.GridIndex, uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
DirtyChunk(e.NewTile.GridIndex, indices);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
|
||||||
|
{
|
||||||
|
switch (e.NewStatus)
|
||||||
|
{
|
||||||
|
case SessionStatus.InGame:
|
||||||
|
_previousSentChunks[e.Session] = new();
|
||||||
|
break;
|
||||||
|
case SessionStatus.Disconnected:
|
||||||
|
_previousSentChunks.Remove(e.Session);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void DirtyChunk(GridId id, Vector2i chunkIndices)
|
||||||
|
{
|
||||||
|
if(!_dirtyChunks.ContainsKey(id))
|
||||||
|
_dirtyChunks[id] = new HashSet<Vector2i>();
|
||||||
|
_dirtyChunks[id].Add(chunkIndices);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryAddDecal(string id, EntityCoordinates coordinates, [NotNullWhen(true)] out uint? uid, Color? color = null, Angle? rotation = null, int zIndex = 0, bool cleanable = false)
|
||||||
|
{
|
||||||
|
uid = 0;
|
||||||
|
if (!PrototypeManager.HasIndex<DecalPrototype>(id))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var gridId = coordinates.GetGridId(EntityManager);
|
||||||
|
if (MapManager.GetGrid(gridId).GetTileRef(coordinates).IsSpace())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
rotation ??= Angle.Zero;
|
||||||
|
var decal = new Decal(coordinates.Position, id, color, rotation.Value, zIndex, cleanable);
|
||||||
|
var chunkCollection = DecalGridChunkCollection(gridId);
|
||||||
|
uid = chunkCollection.NextUid++;
|
||||||
|
var chunkIndices = GetChunkIndices(decal.Coordinates);
|
||||||
|
if(!chunkCollection.ChunkCollection.ContainsKey(chunkIndices))
|
||||||
|
chunkCollection.ChunkCollection[chunkIndices] = new();
|
||||||
|
chunkCollection.ChunkCollection[chunkIndices][uid.Value] = decal;
|
||||||
|
ChunkIndex[gridId][uid.Value] = chunkIndices;
|
||||||
|
DirtyChunk(gridId, chunkIndices);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool RemoveDecal(GridId gridId, uint uid) => RemoveDecalInternal(gridId, uid);
|
||||||
|
|
||||||
|
public HashSet<uint> GetDecalsInRange(GridId gridId, Vector2 position, float distance = 0.75f, Func<Decal, bool>? validDelegate = null)
|
||||||
|
{
|
||||||
|
var uids = new HashSet<uint>();
|
||||||
|
var chunkCollection = ChunkCollection(gridId);
|
||||||
|
var chunkIndices = GetChunkIndices(position);
|
||||||
|
if (!chunkCollection.TryGetValue(chunkIndices, out var chunk))
|
||||||
|
return uids;
|
||||||
|
|
||||||
|
foreach (var (uid, decal) in chunk)
|
||||||
|
{
|
||||||
|
if ((position - decal.Coordinates-new Vector2(0.5f, 0.5f)).Length > distance)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (validDelegate == null || validDelegate(decal))
|
||||||
|
{
|
||||||
|
uids.Add(uid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return uids;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SetDecalPosition(GridId gridId, uint uid, EntityCoordinates coordinates)
|
||||||
|
{
|
||||||
|
return SetDecalPosition(gridId, uid, coordinates.GetGridId(EntityManager), coordinates.Position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SetDecalPosition(GridId gridId, uint uid, GridId newGridId, Vector2 position)
|
||||||
|
{
|
||||||
|
if (!ChunkIndex.TryGetValue(gridId, out var values) || !values.TryGetValue(uid, out var indices))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DirtyChunk(gridId, indices);
|
||||||
|
var chunkCollection = ChunkCollection(gridId);
|
||||||
|
var decal = chunkCollection[indices][uid];
|
||||||
|
if (newGridId == gridId)
|
||||||
|
{
|
||||||
|
chunkCollection[indices][uid] = decal.WithCoordinates(position);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoveDecalInternal(gridId, uid);
|
||||||
|
|
||||||
|
var newChunkCollection = ChunkCollection(newGridId);
|
||||||
|
var chunkIndices = GetChunkIndices(position);
|
||||||
|
if(!newChunkCollection.ContainsKey(chunkIndices))
|
||||||
|
newChunkCollection[chunkIndices] = new();
|
||||||
|
newChunkCollection[chunkIndices][uid] = decal.WithCoordinates(position);
|
||||||
|
ChunkIndex[newGridId][uid] = chunkIndices;
|
||||||
|
DirtyChunk(newGridId, chunkIndices);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SetDecalColor(GridId gridId, uint uid, Color? color)
|
||||||
|
{
|
||||||
|
if (!ChunkIndex.TryGetValue(gridId, out var values) || !values.TryGetValue(uid, out var indices))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var chunkCollection = ChunkCollection(gridId);
|
||||||
|
var decal = chunkCollection[indices][uid];
|
||||||
|
chunkCollection[indices][uid] = decal.WithColor(color);
|
||||||
|
DirtyChunk(gridId, indices);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SetDecalId(GridId gridId, uint uid, string id)
|
||||||
|
{
|
||||||
|
if (!ChunkIndex.TryGetValue(gridId, out var values) || !values.TryGetValue(uid, out var indices))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!PrototypeManager.HasIndex<DecalPrototype>(id))
|
||||||
|
throw new ArgumentOutOfRangeException($"Tried to set decal id to invalid prototypeid: {id}");
|
||||||
|
|
||||||
|
var chunkCollection = ChunkCollection(gridId);
|
||||||
|
var decal = chunkCollection[indices][uid];
|
||||||
|
chunkCollection[indices][uid] = decal.WithId(id);
|
||||||
|
DirtyChunk(gridId, indices);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SetDecalRotation(GridId gridId, uint uid, Angle angle)
|
||||||
|
{
|
||||||
|
if (!ChunkIndex.TryGetValue(gridId, out var values) || !values.TryGetValue(uid, out var indices))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var chunkCollection = ChunkCollection(gridId);
|
||||||
|
var decal = chunkCollection[indices][uid];
|
||||||
|
chunkCollection[indices][uid] = decal.WithRotation(angle);
|
||||||
|
DirtyChunk(gridId, indices);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SetDecalZIndex(GridId gridId, uint uid, int zIndex)
|
||||||
|
{
|
||||||
|
if (!ChunkIndex.TryGetValue(gridId, out var values) || !values.TryGetValue(uid, out var indices))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var chunkCollection = ChunkCollection(gridId);
|
||||||
|
var decal = chunkCollection[indices][uid];
|
||||||
|
chunkCollection[indices][uid] = decal.WithZIndex(zIndex);
|
||||||
|
DirtyChunk(gridId, indices);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SetDecalCleanable(GridId gridId, uint uid, bool cleanable)
|
||||||
|
{
|
||||||
|
if (!ChunkIndex.TryGetValue(gridId, out var values) || !values.TryGetValue(uid, out var indices))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var chunkCollection = ChunkCollection(gridId);
|
||||||
|
var decal = chunkCollection[indices][uid];
|
||||||
|
chunkCollection[indices][uid] = decal.WithCleanable(cleanable);
|
||||||
|
DirtyChunk(gridId, indices);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update(float frameTime)
|
||||||
|
{
|
||||||
|
base.Update(frameTime);
|
||||||
|
|
||||||
|
|
||||||
|
foreach (var session in Filter.Broadcast().Recipients)
|
||||||
|
{
|
||||||
|
if(session is not IPlayerSession playerSession || playerSession.Status != SessionStatus.InGame)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var chunks = GetChunksForSession(playerSession);
|
||||||
|
var updatedChunks = new Dictionary<GridId, HashSet<Vector2i>>();
|
||||||
|
foreach (var (gridId, gridChunks) in chunks)
|
||||||
|
{
|
||||||
|
var newChunks = new HashSet<Vector2i>(gridChunks);
|
||||||
|
if (_previousSentChunks[playerSession].TryGetValue(gridId, out var previousChunks))
|
||||||
|
{
|
||||||
|
newChunks.ExceptWith(previousChunks);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_dirtyChunks.TryGetValue(gridId, out var dirtyChunks))
|
||||||
|
{
|
||||||
|
gridChunks.IntersectWith(dirtyChunks);
|
||||||
|
newChunks.UnionWith(gridChunks);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newChunks.Count == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
updatedChunks[gridId] = newChunks;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(updatedChunks.Count == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
_previousSentChunks[playerSession] = chunks;
|
||||||
|
|
||||||
|
//send all gridChunks to client
|
||||||
|
SendChunkUpdates(playerSession, updatedChunks);
|
||||||
|
}
|
||||||
|
|
||||||
|
_dirtyChunks.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SendChunkUpdates(IPlayerSession session, Dictionary<GridId, HashSet<Vector2i>> updatedChunks)
|
||||||
|
{
|
||||||
|
var updatedDecals = new Dictionary<GridId, Dictionary<Vector2i, Dictionary<uint, Decal>>>();
|
||||||
|
foreach (var (gridId, chunks) in updatedChunks)
|
||||||
|
{
|
||||||
|
var gridChunks = new Dictionary<Vector2i, Dictionary<uint, Decal>>();
|
||||||
|
foreach (var indices in chunks)
|
||||||
|
{
|
||||||
|
gridChunks.Add(indices,
|
||||||
|
ChunkCollection(gridId).TryGetValue(indices, out var chunk)
|
||||||
|
? chunk
|
||||||
|
: new Dictionary<uint, Decal>());
|
||||||
|
}
|
||||||
|
updatedDecals[gridId] = gridChunks;
|
||||||
|
}
|
||||||
|
|
||||||
|
RaiseNetworkEvent(new DecalChunkUpdateEvent{Data = updatedDecals}, Filter.SinglePlayer(session));
|
||||||
|
}
|
||||||
|
|
||||||
|
private HashSet<EntityUid> GetSessionViewers(IPlayerSession session)
|
||||||
|
{
|
||||||
|
var viewers = new HashSet<EntityUid>();
|
||||||
|
if (session.Status != SessionStatus.InGame || session.AttachedEntity is null)
|
||||||
|
return viewers;
|
||||||
|
|
||||||
|
viewers.Add(session.AttachedEntity.Value);
|
||||||
|
|
||||||
|
foreach (var uid in session.ViewSubscriptions)
|
||||||
|
{
|
||||||
|
viewers.Add(uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
return viewers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Dictionary<GridId, HashSet<Vector2i>> GetChunksForSession(IPlayerSession session)
|
||||||
|
{
|
||||||
|
return GetChunksForViewers(GetSessionViewers(session));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using Content.Server.Chemistry.EntitySystems;
|
using Content.Server.Chemistry.EntitySystems;
|
||||||
using Content.Server.Fluids.Components;
|
using Content.Server.Fluids.Components;
|
||||||
|
using Content.Server.Fluids.EntitySystems;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.Serialization.Manager.Attributes;
|
using Robust.Shared.Serialization.Manager.Attributes;
|
||||||
@@ -20,10 +21,10 @@ namespace Content.Server.Destructible.Thresholds.Behaviors
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="owner">Entity on which behavior is executed</param>
|
/// <param name="owner">Entity on which behavior is executed</param>
|
||||||
/// <param name="system">system calling the behavior</param>
|
/// <param name="system">system calling the behavior</param>
|
||||||
/// <param name="entityManager"></param>
|
|
||||||
public void Execute(EntityUid owner, DestructibleSystem system)
|
public void Execute(EntityUid owner, DestructibleSystem system)
|
||||||
{
|
{
|
||||||
var solutionContainerSystem = EntitySystem.Get<SolutionContainerSystem>();
|
var solutionContainerSystem = EntitySystem.Get<SolutionContainerSystem>();
|
||||||
|
var spillableSystem = EntitySystem.Get<SpillableSystem>();
|
||||||
|
|
||||||
var coordinates = system.EntityManager.GetComponent<TransformComponent>(owner).Coordinates;
|
var coordinates = system.EntityManager.GetComponent<TransformComponent>(owner).Coordinates;
|
||||||
|
|
||||||
@@ -31,12 +32,12 @@ namespace Content.Server.Destructible.Thresholds.Behaviors
|
|||||||
solutionContainerSystem.TryGetSolution(owner, spillableComponent.SolutionName,
|
solutionContainerSystem.TryGetSolution(owner, spillableComponent.SolutionName,
|
||||||
out var compSolution))
|
out var compSolution))
|
||||||
{
|
{
|
||||||
compSolution.SpillAt(coordinates, "PuddleSmear", false);
|
spillableSystem.SpillAt(compSolution, coordinates, "PuddleSmear", false);
|
||||||
}
|
}
|
||||||
else if (Solution != null &&
|
else if (Solution != null &&
|
||||||
solutionContainerSystem.TryGetSolution(owner, Solution, out var behaviorSolution))
|
solutionContainerSystem.TryGetSolution(owner, Solution, out var behaviorSolution))
|
||||||
{
|
{
|
||||||
behaviorSolution.SpillAt(coordinates, "PuddleSmear", false);
|
spillableSystem.SpillAt(behaviorSolution, coordinates, "PuddleSmear", false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ namespace Content.Server.DoAfter
|
|||||||
public sealed class DoAfterSystem : EntitySystem
|
public sealed class DoAfterSystem : EntitySystem
|
||||||
{
|
{
|
||||||
// We cache these lists as to not allocate them every update tick...
|
// We cache these lists as to not allocate them every update tick...
|
||||||
private readonly List<DoAfter> _cancelled = new();
|
private readonly Queue<DoAfter> _cancelled = new();
|
||||||
private readonly List<DoAfter> _finished = new();
|
private readonly Queue<DoAfter> _finished = new();
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
@@ -52,17 +52,17 @@ namespace Content.Server.DoAfter
|
|||||||
case DoAfterStatus.Running:
|
case DoAfterStatus.Running:
|
||||||
break;
|
break;
|
||||||
case DoAfterStatus.Cancelled:
|
case DoAfterStatus.Cancelled:
|
||||||
_cancelled.Add(doAfter);
|
_cancelled.Enqueue(doAfter);
|
||||||
break;
|
break;
|
||||||
case DoAfterStatus.Finished:
|
case DoAfterStatus.Finished:
|
||||||
_finished.Add(doAfter);
|
_finished.Enqueue(doAfter);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new ArgumentOutOfRangeException();
|
throw new ArgumentOutOfRangeException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var doAfter in _cancelled)
|
while (_cancelled.TryDequeue(out var doAfter))
|
||||||
{
|
{
|
||||||
comp.Cancelled(doAfter);
|
comp.Cancelled(doAfter);
|
||||||
|
|
||||||
@@ -76,7 +76,7 @@ namespace Content.Server.DoAfter
|
|||||||
RaiseLocalEvent(doAfter.EventArgs.BroadcastCancelledEvent);
|
RaiseLocalEvent(doAfter.EventArgs.BroadcastCancelledEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var doAfter in _finished)
|
while (_finished.TryDequeue(out var doAfter))
|
||||||
{
|
{
|
||||||
comp.Finished(doAfter);
|
comp.Finished(doAfter);
|
||||||
|
|
||||||
@@ -89,10 +89,6 @@ namespace Content.Server.DoAfter
|
|||||||
if(doAfter.EventArgs.BroadcastFinishedEvent != null)
|
if(doAfter.EventArgs.BroadcastFinishedEvent != null)
|
||||||
RaiseLocalEvent(doAfter.EventArgs.BroadcastFinishedEvent);
|
RaiseLocalEvent(doAfter.EventArgs.BroadcastFinishedEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean the shared lists at the end, ensuring they'll be clean for the next time we need them.
|
|
||||||
_cancelled.Clear();
|
|
||||||
_finished.Clear();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -584,7 +584,7 @@ namespace Content.Server.Doors.Components
|
|||||||
if (IoCManager.Resolve<IEntityManager>().HasComponent<DamageableComponent>(e.Owner))
|
if (IoCManager.Resolve<IEntityManager>().HasComponent<DamageableComponent>(e.Owner))
|
||||||
EntitySystem.Get<DamageableSystem>().TryChangeDamage(e.Owner, CrushDamage);
|
EntitySystem.Get<DamageableSystem>().TryChangeDamage(e.Owner, CrushDamage);
|
||||||
|
|
||||||
EntitySystem.Get<StunSystem>().TryParalyze(e.Owner, TimeSpan.FromSeconds(DoorStunTime));
|
EntitySystem.Get<StunSystem>().TryParalyze(e.Owner, TimeSpan.FromSeconds(DoorStunTime), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we hit someone, open up after stun (opens right when stun ends)
|
// If we hit someone, open up after stun (opens right when stun ends)
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ namespace Content.Server.Electrocution
|
|||||||
[DataField("onHandInteract")]
|
[DataField("onHandInteract")]
|
||||||
public bool OnHandInteract { get; set; } = true;
|
public bool OnHandInteract { get; set; } = true;
|
||||||
|
|
||||||
|
[DataField("onInteractUsing")]
|
||||||
|
public bool OnInteractUsing { get; set; } = true;
|
||||||
|
|
||||||
[DataField("requirePower")]
|
[DataField("requirePower")]
|
||||||
public bool RequirePower { get; } = true;
|
public bool RequirePower { get; } = true;
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ namespace Content.Server.Electrocution
|
|||||||
}
|
}
|
||||||
|
|
||||||
entityManager.EntitySysManager.GetEntitySystem<ElectrocutionSystem>()
|
entityManager.EntitySysManager.GetEntitySystem<ElectrocutionSystem>()
|
||||||
.TryDoElectrocution(uid, null, damage, TimeSpan.FromSeconds(seconds));
|
.TryDoElectrocution(uid, null, damage, TimeSpan.FromSeconds(seconds), true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ namespace Content.Server.Electrocution
|
|||||||
SubscribeLocalEvent<ElectrifiedComponent, StartCollideEvent>(OnElectrifiedStartCollide);
|
SubscribeLocalEvent<ElectrifiedComponent, StartCollideEvent>(OnElectrifiedStartCollide);
|
||||||
SubscribeLocalEvent<ElectrifiedComponent, AttackedEvent>(OnElectrifiedAttacked);
|
SubscribeLocalEvent<ElectrifiedComponent, AttackedEvent>(OnElectrifiedAttacked);
|
||||||
SubscribeLocalEvent<ElectrifiedComponent, InteractHandEvent>(OnElectrifiedHandInteract);
|
SubscribeLocalEvent<ElectrifiedComponent, InteractHandEvent>(OnElectrifiedHandInteract);
|
||||||
|
SubscribeLocalEvent<ElectrifiedComponent, InteractUsingEvent>(OnElectrifiedInteractUsing);
|
||||||
SubscribeLocalEvent<RandomInsulationComponent, MapInitEvent>(OnRandomInsulationMapInit);
|
SubscribeLocalEvent<RandomInsulationComponent, MapInitEvent>(OnRandomInsulationMapInit);
|
||||||
|
|
||||||
UpdatesAfter.Add(typeof(PowerNetSystem));
|
UpdatesAfter.Add(typeof(PowerNetSystem));
|
||||||
@@ -140,6 +141,14 @@ namespace Content.Server.Electrocution
|
|||||||
TryDoElectrifiedAct(uid, args.User, electrified);
|
TryDoElectrifiedAct(uid, args.User, electrified);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnElectrifiedInteractUsing(EntityUid uid, ElectrifiedComponent electrified, InteractUsingEvent args)
|
||||||
|
{
|
||||||
|
if (!electrified.OnInteractUsing)
|
||||||
|
return;
|
||||||
|
|
||||||
|
TryDoElectrifiedAct(uid, args.User, electrified);
|
||||||
|
}
|
||||||
|
|
||||||
public bool TryDoElectrifiedAct(EntityUid uid, EntityUid targetUid,
|
public bool TryDoElectrifiedAct(EntityUid uid, EntityUid targetUid,
|
||||||
ElectrifiedComponent? electrified = null,
|
ElectrifiedComponent? electrified = null,
|
||||||
NodeContainerComponent? nodeContainer = null,
|
NodeContainerComponent? nodeContainer = null,
|
||||||
@@ -177,7 +186,7 @@ namespace Content.Server.Electrocution
|
|||||||
entity,
|
entity,
|
||||||
uid,
|
uid,
|
||||||
(int) (electrified.ShockDamage * MathF.Pow(RecursiveDamageMultiplier, depth)),
|
(int) (electrified.ShockDamage * MathF.Pow(RecursiveDamageMultiplier, depth)),
|
||||||
TimeSpan.FromSeconds(electrified.ShockTime * MathF.Pow(RecursiveTimeMultiplier, depth)),
|
TimeSpan.FromSeconds(electrified.ShockTime * MathF.Pow(RecursiveTimeMultiplier, depth)), true,
|
||||||
electrified.SiemensCoefficient);
|
electrified.SiemensCoefficient);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,7 +222,7 @@ namespace Content.Server.Electrocution
|
|||||||
node,
|
node,
|
||||||
(int) (electrified.ShockDamage * MathF.Pow(RecursiveDamageMultiplier, depth) * damageMult),
|
(int) (electrified.ShockDamage * MathF.Pow(RecursiveDamageMultiplier, depth) * damageMult),
|
||||||
TimeSpan.FromSeconds(electrified.ShockTime * MathF.Pow(RecursiveTimeMultiplier, depth) *
|
TimeSpan.FromSeconds(electrified.ShockTime * MathF.Pow(RecursiveTimeMultiplier, depth) *
|
||||||
timeMult),
|
timeMult), true,
|
||||||
electrified.SiemensCoefficient);
|
electrified.SiemensCoefficient);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,12 +244,12 @@ namespace Content.Server.Electrocution
|
|||||||
|
|
||||||
/// <returns>Whether the entity <see cref="uid"/> was stunned by the shock.</returns>
|
/// <returns>Whether the entity <see cref="uid"/> was stunned by the shock.</returns>
|
||||||
public bool TryDoElectrocution(
|
public bool TryDoElectrocution(
|
||||||
EntityUid uid, EntityUid? sourceUid, int shockDamage, TimeSpan time, float siemensCoefficient = 1f,
|
EntityUid uid, EntityUid? sourceUid, int shockDamage, TimeSpan time, bool refresh, float siemensCoefficient = 1f,
|
||||||
StatusEffectsComponent? statusEffects = null,
|
StatusEffectsComponent? statusEffects = null,
|
||||||
SharedAlertsComponent? alerts = null)
|
SharedAlertsComponent? alerts = null)
|
||||||
{
|
{
|
||||||
if (!DoCommonElectrocutionAttempt(uid, sourceUid, ref siemensCoefficient)
|
if (!DoCommonElectrocutionAttempt(uid, sourceUid, ref siemensCoefficient)
|
||||||
|| !DoCommonElectrocution(uid, sourceUid, shockDamage, time, siemensCoefficient, statusEffects, alerts))
|
|| !DoCommonElectrocution(uid, sourceUid, shockDamage, time, refresh, siemensCoefficient, statusEffects, alerts))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
RaiseLocalEvent(uid, new ElectrocutedEvent(uid, sourceUid, siemensCoefficient));
|
RaiseLocalEvent(uid, new ElectrocutedEvent(uid, sourceUid, siemensCoefficient));
|
||||||
@@ -254,6 +263,7 @@ namespace Content.Server.Electrocution
|
|||||||
Node node,
|
Node node,
|
||||||
int shockDamage,
|
int shockDamage,
|
||||||
TimeSpan time,
|
TimeSpan time,
|
||||||
|
bool refresh,
|
||||||
float siemensCoefficient = 1f,
|
float siemensCoefficient = 1f,
|
||||||
StatusEffectsComponent? statusEffects = null,
|
StatusEffectsComponent? statusEffects = null,
|
||||||
SharedAlertsComponent? alerts = null,
|
SharedAlertsComponent? alerts = null,
|
||||||
@@ -264,9 +274,9 @@ namespace Content.Server.Electrocution
|
|||||||
|
|
||||||
// Coefficient needs to be higher than this to do a powered electrocution!
|
// Coefficient needs to be higher than this to do a powered electrocution!
|
||||||
if(siemensCoefficient <= 0.5f)
|
if(siemensCoefficient <= 0.5f)
|
||||||
return DoCommonElectrocution(uid, sourceUid, shockDamage, time, siemensCoefficient, statusEffects, alerts);
|
return DoCommonElectrocution(uid, sourceUid, shockDamage, time, refresh, siemensCoefficient, statusEffects, alerts);
|
||||||
|
|
||||||
if (!DoCommonElectrocution(uid, sourceUid, null, time, siemensCoefficient, statusEffects, alerts))
|
if (!DoCommonElectrocution(uid, sourceUid, null, time, refresh, siemensCoefficient, statusEffects, alerts))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!Resolve(sourceUid, ref sourceTransform)) // This shouldn't really happen, but just in case...
|
if (!Resolve(sourceUid, ref sourceTransform)) // This shouldn't really happen, but just in case...
|
||||||
@@ -307,7 +317,7 @@ namespace Content.Server.Electrocution
|
|||||||
}
|
}
|
||||||
|
|
||||||
private bool DoCommonElectrocution(EntityUid uid, EntityUid? sourceUid,
|
private bool DoCommonElectrocution(EntityUid uid, EntityUid? sourceUid,
|
||||||
int? shockDamage, TimeSpan time, float siemensCoefficient = 1f,
|
int? shockDamage, TimeSpan time, bool refresh, float siemensCoefficient = 1f,
|
||||||
StatusEffectsComponent? statusEffects = null,
|
StatusEffectsComponent? statusEffects = null,
|
||||||
SharedAlertsComponent? alerts = null)
|
SharedAlertsComponent? alerts = null)
|
||||||
{
|
{
|
||||||
@@ -329,14 +339,14 @@ namespace Content.Server.Electrocution
|
|||||||
!_statusEffectsSystem.CanApplyEffect(uid, StatusEffectKey, statusEffects))
|
!_statusEffectsSystem.CanApplyEffect(uid, StatusEffectKey, statusEffects))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!_statusEffectsSystem.TryAddStatusEffect<ElectrocutedComponent>(uid, StatusEffectKey, time,
|
if (!_statusEffectsSystem.TryAddStatusEffect<ElectrocutedComponent>(uid, StatusEffectKey, time, refresh,
|
||||||
statusEffects, alerts))
|
statusEffects, alerts))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var shouldStun = siemensCoefficient > 0.5f;
|
var shouldStun = siemensCoefficient > 0.5f;
|
||||||
|
|
||||||
if (shouldStun)
|
if (shouldStun)
|
||||||
_stunSystem.TryParalyze(uid, time * ParalyzeTimeMultiplier, statusEffects, alerts);
|
_stunSystem.TryParalyze(uid, time * ParalyzeTimeMultiplier, refresh, statusEffects, alerts);
|
||||||
|
|
||||||
// TODO: Sparks here.
|
// TODO: Sparks here.
|
||||||
|
|
||||||
@@ -350,8 +360,8 @@ namespace Content.Server.Electrocution
|
|||||||
$"{statusEffects.Owner} took {actual.Total} powered electrocution damage");
|
$"{statusEffects.Owner} took {actual.Total} powered electrocution damage");
|
||||||
}
|
}
|
||||||
|
|
||||||
_stutteringSystem.DoStutter(uid, time * StutteringTimeMultiplier, statusEffects, alerts);
|
_stutteringSystem.DoStutter(uid, time * StutteringTimeMultiplier, refresh, statusEffects, alerts);
|
||||||
_jitteringSystem.DoJitter(uid, time * JitterTimeMultiplier, JitterAmplitude, JitterFrequency, true,
|
_jitteringSystem.DoJitter(uid, time * JitterTimeMultiplier, refresh, JitterAmplitude, JitterFrequency, true,
|
||||||
statusEffects, alerts);
|
statusEffects, alerts);
|
||||||
|
|
||||||
_popupSystem.PopupEntity(Loc.GetString("electrocuted-component-mob-shocked-popup-player"), uid,
|
_popupSystem.PopupEntity(Loc.GetString("electrocuted-component-mob-shocked-popup-player"), uid,
|
||||||
|
|||||||
@@ -294,6 +294,7 @@ namespace Content.Server.Explosion.EntitySystems
|
|||||||
int heavyImpactRange = 0,
|
int heavyImpactRange = 0,
|
||||||
int lightImpactRange = 0,
|
int lightImpactRange = 0,
|
||||||
int flashRange = 0,
|
int flashRange = 0,
|
||||||
|
EntityUid? user = null,
|
||||||
ExplosiveComponent? explosive = null,
|
ExplosiveComponent? explosive = null,
|
||||||
TransformComponent? transform = null)
|
TransformComponent? transform = null)
|
||||||
{
|
{
|
||||||
@@ -306,7 +307,7 @@ namespace Content.Server.Explosion.EntitySystems
|
|||||||
|
|
||||||
if (explosive is { Exploding: false })
|
if (explosive is { Exploding: false })
|
||||||
{
|
{
|
||||||
_triggers.Explode(entity, explosive);
|
_triggers.Explode(entity, explosive, user);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -322,7 +323,7 @@ namespace Content.Server.Explosion.EntitySystems
|
|||||||
|
|
||||||
var epicenter = transform.Coordinates;
|
var epicenter = transform.Coordinates;
|
||||||
|
|
||||||
SpawnExplosion(epicenter, devastationRange, heavyImpactRange, lightImpactRange, flashRange);
|
SpawnExplosion(epicenter, devastationRange, heavyImpactRange, lightImpactRange, flashRange, entity, user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -331,7 +332,9 @@ namespace Content.Server.Explosion.EntitySystems
|
|||||||
int devastationRange = 0,
|
int devastationRange = 0,
|
||||||
int heavyImpactRange = 0,
|
int heavyImpactRange = 0,
|
||||||
int lightImpactRange = 0,
|
int lightImpactRange = 0,
|
||||||
int flashRange = 0)
|
int flashRange = 0,
|
||||||
|
EntityUid? entity = null,
|
||||||
|
EntityUid? user = null)
|
||||||
{
|
{
|
||||||
var mapId = epicenter.GetMapId(EntityManager);
|
var mapId = epicenter.GetMapId(EntityManager);
|
||||||
if (mapId == MapId.Nullspace)
|
if (mapId == MapId.Nullspace)
|
||||||
@@ -339,8 +342,22 @@ namespace Content.Server.Explosion.EntitySystems
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_logSystem.Add(LogType.Damaged, LogImpact.High ,
|
// logging
|
||||||
$"Spawned explosion at {epicenter} with range {devastationRange}/{heavyImpactRange}/{lightImpactRange}/{flashRange}");
|
var text = $"{epicenter} with range {devastationRange}/{heavyImpactRange}/{lightImpactRange}/{flashRange}";
|
||||||
|
if (entity == null)
|
||||||
|
{
|
||||||
|
_logSystem.Add(LogType.Explosion, LogImpact.High, $"Explosion spawned at {text}");
|
||||||
|
}
|
||||||
|
else if (user == null)
|
||||||
|
{
|
||||||
|
_logSystem.Add(LogType.Explosion, LogImpact.High,
|
||||||
|
$"{entity.Value} exploded at {text}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logSystem.Add(LogType.Explosion, LogImpact.High,
|
||||||
|
$"{entity.Value} caused {entity.Value} to explode at {text}");
|
||||||
|
}
|
||||||
|
|
||||||
var maxRange = MathHelper.Max(devastationRange, heavyImpactRange, lightImpactRange, 0);
|
var maxRange = MathHelper.Max(devastationRange, heavyImpactRange, lightImpactRange, 0);
|
||||||
var epicenterMapPos = epicenter.ToMapPos(EntityManager);
|
var epicenterMapPos = epicenter.ToMapPos(EntityManager);
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using Content.Server.Administration.Logs;
|
||||||
using Content.Server.Doors.Components;
|
using Content.Server.Doors.Components;
|
||||||
using Content.Server.Explosion.Components;
|
using Content.Server.Explosion.Components;
|
||||||
using Content.Server.Flash;
|
using Content.Server.Flash;
|
||||||
using Content.Server.Flash.Components;
|
using Content.Server.Flash.Components;
|
||||||
|
using Content.Server.Projectiles.Components;
|
||||||
using Content.Shared.Audio;
|
using Content.Shared.Audio;
|
||||||
|
using Content.Shared.Database;
|
||||||
using Content.Shared.Doors;
|
using Content.Shared.Doors;
|
||||||
|
using Content.Shared.Throwing;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Shared.Audio;
|
using Robust.Shared.Audio;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
@@ -21,9 +25,9 @@ namespace Content.Server.Explosion.EntitySystems
|
|||||||
public class TriggerEvent : HandledEntityEventArgs
|
public class TriggerEvent : HandledEntityEventArgs
|
||||||
{
|
{
|
||||||
public EntityUid Triggered { get; }
|
public EntityUid Triggered { get; }
|
||||||
public EntityUid User { get; }
|
public EntityUid? User { get; }
|
||||||
|
|
||||||
public TriggerEvent(EntityUid triggered, EntityUid user = default)
|
public TriggerEvent(EntityUid triggered, EntityUid? user = null)
|
||||||
{
|
{
|
||||||
Triggered = triggered;
|
Triggered = triggered;
|
||||||
User = user;
|
User = user;
|
||||||
@@ -35,6 +39,7 @@ namespace Content.Server.Explosion.EntitySystems
|
|||||||
{
|
{
|
||||||
[Dependency] private readonly ExplosionSystem _explosions = default!;
|
[Dependency] private readonly ExplosionSystem _explosions = default!;
|
||||||
[Dependency] private readonly FlashSystem _flashSystem = default!;
|
[Dependency] private readonly FlashSystem _flashSystem = default!;
|
||||||
|
[Dependency] private readonly AdminLogSystem _logSystem = default!;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
@@ -53,11 +58,11 @@ namespace Content.Server.Explosion.EntitySystems
|
|||||||
{
|
{
|
||||||
if (!EntityManager.TryGetComponent(uid, out ExplosiveComponent? explosiveComponent)) return;
|
if (!EntityManager.TryGetComponent(uid, out ExplosiveComponent? explosiveComponent)) return;
|
||||||
|
|
||||||
Explode(uid, explosiveComponent);
|
Explode(uid, explosiveComponent, args.User);
|
||||||
}
|
}
|
||||||
|
|
||||||
// You really shouldn't call this directly (TODO Change that when ExplosionHelper gets changed).
|
// You really shouldn't call this directly (TODO Change that when ExplosionHelper gets changed).
|
||||||
public void Explode(EntityUid uid, ExplosiveComponent component)
|
public void Explode(EntityUid uid, ExplosiveComponent component, EntityUid? user = null)
|
||||||
{
|
{
|
||||||
if (component.Exploding)
|
if (component.Exploding)
|
||||||
{
|
{
|
||||||
@@ -65,7 +70,12 @@ namespace Content.Server.Explosion.EntitySystems
|
|||||||
}
|
}
|
||||||
|
|
||||||
component.Exploding = true;
|
component.Exploding = true;
|
||||||
_explosions.SpawnExplosion(uid, component.DevastationRange, component.HeavyImpactRange, component.LightImpactRange, component.FlashRange);
|
_explosions.SpawnExplosion(uid,
|
||||||
|
component.DevastationRange,
|
||||||
|
component.HeavyImpactRange,
|
||||||
|
component.LightImpactRange,
|
||||||
|
component.FlashRange,
|
||||||
|
user);
|
||||||
EntityManager.QueueDeleteEntity(uid);
|
EntityManager.QueueDeleteEntity(uid);
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
@@ -113,16 +123,23 @@ namespace Content.Server.Explosion.EntitySystems
|
|||||||
|
|
||||||
private void HandleCollide(EntityUid uid, TriggerOnCollideComponent component, StartCollideEvent args)
|
private void HandleCollide(EntityUid uid, TriggerOnCollideComponent component, StartCollideEvent args)
|
||||||
{
|
{
|
||||||
Trigger(component.Owner);
|
EntityUid? user = null;
|
||||||
|
if (EntityManager.TryGetComponent(uid, out ProjectileComponent projectile))
|
||||||
|
user = projectile.Shooter;
|
||||||
|
else if (EntityManager.TryGetComponent(uid, out ThrownItemComponent thrown))
|
||||||
|
user = thrown.Thrower;
|
||||||
|
|
||||||
|
Trigger(component.Owner, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Trigger(EntityUid trigger, EntityUid user = default)
|
|
||||||
|
public void Trigger(EntityUid trigger, EntityUid? user = null)
|
||||||
{
|
{
|
||||||
var triggerEvent = new TriggerEvent(trigger, user);
|
var triggerEvent = new TriggerEvent(trigger, user);
|
||||||
EntityManager.EventBus.RaiseLocalEvent(trigger, triggerEvent);
|
EntityManager.EventBus.RaiseLocalEvent(trigger, triggerEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void HandleTimerTrigger(TimeSpan delay, EntityUid triggered, EntityUid user = default)
|
public void HandleTimerTrigger(TimeSpan delay, EntityUid triggered, EntityUid? user = null)
|
||||||
{
|
{
|
||||||
if (delay.TotalSeconds <= 0)
|
if (delay.TotalSeconds <= 0)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ namespace Content.Server.Flash
|
|||||||
flashable.Dirty();
|
flashable.Dirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
_stunSystem.TrySlowdown(target, TimeSpan.FromSeconds(flashDuration/1000f),
|
_stunSystem.TrySlowdown(target, TimeSpan.FromSeconds(flashDuration/1000f), true,
|
||||||
slowTo, slowTo);
|
slowTo, slowTo);
|
||||||
|
|
||||||
if (displayPopup && user != null && target != user)
|
if (displayPopup && user != null && target != user)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Content.Server.Chemistry.EntitySystems;
|
using Content.Server.Chemistry.EntitySystems;
|
||||||
using Content.Server.DoAfter;
|
using Content.Server.DoAfter;
|
||||||
|
using Content.Server.Fluids.EntitySystems;
|
||||||
using Content.Shared.Chemistry.Components;
|
using Content.Shared.Chemistry.Components;
|
||||||
using Content.Shared.FixedPoint;
|
using Content.Shared.FixedPoint;
|
||||||
using Content.Shared.Interaction;
|
using Content.Shared.Interaction;
|
||||||
@@ -89,6 +90,7 @@ namespace Content.Server.Fluids.Components
|
|||||||
* will spill some of the mop's solution onto the puddle which will evaporate eventually.
|
* will spill some of the mop's solution onto the puddle which will evaporate eventually.
|
||||||
*/
|
*/
|
||||||
var solutionSystem = EntitySystem.Get<SolutionContainerSystem>();
|
var solutionSystem = EntitySystem.Get<SolutionContainerSystem>();
|
||||||
|
var spillableSystem = EntitySystem.Get<SpillableSystem>();
|
||||||
|
|
||||||
if (!solutionSystem.TryGetSolution(Owner, SolutionName, out var contents ) ||
|
if (!solutionSystem.TryGetSolution(Owner, SolutionName, out var contents ) ||
|
||||||
Mopping ||
|
Mopping ||
|
||||||
@@ -106,8 +108,8 @@ namespace Content.Server.Fluids.Components
|
|||||||
if (eventArgs.Target is not {Valid: true} target)
|
if (eventArgs.Target is not {Valid: true} target)
|
||||||
{
|
{
|
||||||
// Drop the liquid on the mop on to the ground
|
// Drop the liquid on the mop on to the ground
|
||||||
solutionSystem.SplitSolution(Owner, contents, FixedPoint2.Min(ResidueAmount, CurrentVolume))
|
var solution = solutionSystem.SplitSolution(Owner, contents, FixedPoint2.Min(ResidueAmount, CurrentVolume));
|
||||||
.SpillAt(eventArgs.ClickLocation, "PuddleSmear");
|
spillableSystem.SpillAt(solution, eventArgs.ClickLocation, "PuddleSmear");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,9 +149,9 @@ namespace Content.Server.Fluids.Components
|
|||||||
|
|
||||||
// After cleaning the puddle, make a new puddle with solution from the mop as a "wet floor". Then evaporate it slowly.
|
// After cleaning the puddle, make a new puddle with solution from the mop as a "wet floor". Then evaporate it slowly.
|
||||||
// we do this WITHOUT adding to the existing puddle. Otherwise we have might have water puddles with the vomit sprite.
|
// we do this WITHOUT adding to the existing puddle. Otherwise we have might have water puddles with the vomit sprite.
|
||||||
solutionSystem.SplitSolution(Owner, contents, transferAmount)
|
var splitSolution = solutionSystem.SplitSolution(Owner, contents, transferAmount)
|
||||||
.SplitSolution(ResidueAmount)
|
.SplitSolution(ResidueAmount);
|
||||||
.SpillAt(eventArgs.ClickLocation, "PuddleSmear", combine: false);
|
spillableSystem.SpillAt(splitSolution, eventArgs.ClickLocation, "PuddleSmear", combine: false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,184 +0,0 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Linq;
|
|
||||||
using Content.Server.Chemistry.EntitySystems;
|
|
||||||
using Content.Server.Coordinates.Helpers;
|
|
||||||
using Content.Server.Fluids.EntitySystems;
|
|
||||||
using Content.Shared.Chemistry.Components;
|
|
||||||
using Content.Shared.Chemistry.Reagent;
|
|
||||||
using Content.Shared.FixedPoint;
|
|
||||||
using Robust.Server.GameObjects;
|
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
using Robust.Shared.IoC;
|
|
||||||
using Robust.Shared.Map;
|
|
||||||
using Robust.Shared.Prototypes;
|
|
||||||
|
|
||||||
namespace Content.Server.Fluids.Components
|
|
||||||
{
|
|
||||||
// TODO: Kill these with fire
|
|
||||||
public static class SpillExtensions
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Spills the specified solution at the entity's location if possible.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="entity">
|
|
||||||
/// The entity to use as a location to spill the solution at.
|
|
||||||
/// </param>
|
|
||||||
/// <param name="solution">Initial solution for the prototype.</param>
|
|
||||||
/// <param name="prototype">The prototype to use.</param>
|
|
||||||
/// <param name="sound">Play the spill sound.</param>
|
|
||||||
/// <returns>The puddle if one was created, null otherwise.</returns>
|
|
||||||
/// <param name="combine">Whether to attempt to merge with existing puddles</param>
|
|
||||||
public static PuddleComponent? SpillAt(this Solution solution, EntityUid entity, string prototype,
|
|
||||||
bool sound = true, bool combine = true)
|
|
||||||
{
|
|
||||||
return solution.SpillAt(IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(entity).Coordinates, prototype, sound, combine: combine);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Spills the specified solution at the entity's location if possible.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="entity">
|
|
||||||
/// The entity to use as a location to spill the solution at.
|
|
||||||
/// </param>
|
|
||||||
/// <param name="solution">Initial solution for the prototype.</param>
|
|
||||||
/// <param name="prototype">The prototype to use.</param>
|
|
||||||
/// <param name="puddle">The puddle if one was created, null otherwise.</param>
|
|
||||||
/// <param name="sound">Play the spill sound.</param>
|
|
||||||
/// <param name="combine">Whether to attempt to merge with existing puddles</param>
|
|
||||||
/// <returns>True if a puddle was created, false otherwise.</returns>
|
|
||||||
public static bool TrySpillAt(this Solution solution, EntityUid entity, string prototype,
|
|
||||||
[NotNullWhen(true)] out PuddleComponent? puddle, bool sound = true, bool combine = true)
|
|
||||||
{
|
|
||||||
puddle = solution.SpillAt(entity, prototype, sound, combine: combine);
|
|
||||||
return puddle != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Spills solution at the specified grid coordinates.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="solution">Initial solution for the prototype.</param>
|
|
||||||
/// <param name="coordinates">The coordinates to spill the solution at.</param>
|
|
||||||
/// <param name="prototype">The prototype to use.</param>
|
|
||||||
/// <param name="sound">Whether or not to play the spill sound.</param>
|
|
||||||
/// <param name="combine">Whether to attempt to merge with existing puddles</param>
|
|
||||||
/// <returns>The puddle if one was created, null otherwise.</returns>
|
|
||||||
public static PuddleComponent? SpillAt(this Solution solution, EntityCoordinates coordinates, string prototype,
|
|
||||||
bool overflow = true, bool sound = true, bool combine = true)
|
|
||||||
{
|
|
||||||
if (solution.TotalVolume == 0) return null;
|
|
||||||
|
|
||||||
var mapManager = IoCManager.Resolve<IMapManager>();
|
|
||||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
|
||||||
|
|
||||||
if (!mapManager.TryGetGrid(coordinates.GetGridId(entityManager), out var mapGrid))
|
|
||||||
return null; // Let's not spill to space.
|
|
||||||
|
|
||||||
return SpillAt(mapGrid.GetTileRef(coordinates), solution, prototype, overflow, sound, combine: combine);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Spills the specified solution at the entity's location if possible.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="coordinates">The coordinates to spill the solution at.</param>
|
|
||||||
/// <param name="solution">Initial solution for the prototype.</param>
|
|
||||||
/// <param name="prototype">The prototype to use.</param>
|
|
||||||
/// <param name="puddle">The puddle if one was created, null otherwise.</param>
|
|
||||||
/// <param name="sound">Play the spill sound.</param>
|
|
||||||
/// <param name="combine">Whether to attempt to merge with existing puddles</param>
|
|
||||||
/// <returns>True if a puddle was created, false otherwise.</returns>
|
|
||||||
public static bool TrySpillAt(this Solution solution, EntityCoordinates coordinates, string prototype,
|
|
||||||
[NotNullWhen(true)] out PuddleComponent? puddle, bool sound = true, bool combine = true)
|
|
||||||
{
|
|
||||||
puddle = solution.SpillAt(coordinates, prototype, sound, combine: combine);
|
|
||||||
return puddle != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool TryGetPuddle(this TileRef tileRef, GridTileLookupSystem? gridTileLookupSystem,
|
|
||||||
[NotNullWhen(true)] out PuddleComponent? puddle)
|
|
||||||
{
|
|
||||||
foreach (var entity in tileRef.GetEntitiesInTileFast(gridTileLookupSystem))
|
|
||||||
{
|
|
||||||
if (IoCManager.Resolve<IEntityManager>().TryGetComponent(entity, out PuddleComponent? p))
|
|
||||||
{
|
|
||||||
puddle = p;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
puddle = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static PuddleComponent? SpillAt(this TileRef tileRef, Solution solution, string prototype,
|
|
||||||
bool overflow = true, bool sound = true, bool noTileReact = false, bool combine = true)
|
|
||||||
{
|
|
||||||
if (solution.TotalVolume <= 0) return null;
|
|
||||||
|
|
||||||
// If space return early, let that spill go out into the void
|
|
||||||
if (tileRef.Tile.IsEmpty) return null;
|
|
||||||
|
|
||||||
var mapManager = IoCManager.Resolve<IMapManager>();
|
|
||||||
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
|
||||||
var serverEntityManager = IoCManager.Resolve<IServerEntityManager>();
|
|
||||||
|
|
||||||
var gridId = tileRef.GridIndex;
|
|
||||||
if (!mapManager.TryGetGrid(gridId, out var mapGrid)) return null; // Let's not spill to invalid grids.
|
|
||||||
|
|
||||||
if (!noTileReact)
|
|
||||||
{
|
|
||||||
// First, do all tile reactions
|
|
||||||
foreach (var reagent in solution.Contents.ToArray())
|
|
||||||
{
|
|
||||||
var proto = prototypeManager.Index<ReagentPrototype>(reagent.ReagentId);
|
|
||||||
proto.ReactionTile(tileRef, reagent.Quantity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tile reactions used up everything.
|
|
||||||
if (solution.CurrentVolume == FixedPoint2.Zero)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
// Get normalized co-ordinate for spill location and spill it in the centre
|
|
||||||
// TODO: Does SnapGrid or something else already do this?
|
|
||||||
var spillGridCoords = mapGrid.GridTileToWorld(tileRef.GridIndices);
|
|
||||||
|
|
||||||
var spillEntities = IoCManager.Resolve<IEntityLookup>()
|
|
||||||
.GetEntitiesIntersecting(mapGrid.ParentMapId, spillGridCoords.Position).ToArray();
|
|
||||||
foreach (var spillEntity in spillEntities)
|
|
||||||
{
|
|
||||||
if (EntitySystem.Get<SolutionContainerSystem>()
|
|
||||||
.TryGetRefillableSolution(spillEntity, out var solutionContainerComponent))
|
|
||||||
{
|
|
||||||
EntitySystem.Get<SolutionContainerSystem>().Refill(spillEntity, solutionContainerComponent,
|
|
||||||
solution.SplitSolution(FixedPoint2.Min(
|
|
||||||
solutionContainerComponent.AvailableVolume,
|
|
||||||
solutionContainerComponent.MaxSpillRefill))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var puddleSystem = EntitySystem.Get<PuddleSystem>();
|
|
||||||
|
|
||||||
if (combine)
|
|
||||||
{
|
|
||||||
foreach (var spillEntity in spillEntities)
|
|
||||||
{
|
|
||||||
if (!IoCManager.Resolve<IEntityManager>().TryGetComponent(spillEntity, out PuddleComponent? puddleComponent)) continue;
|
|
||||||
|
|
||||||
if (!overflow && puddleSystem.WouldOverflow(puddleComponent.Owner, solution, puddleComponent)) return null;
|
|
||||||
|
|
||||||
if (!puddleSystem.TryAddSolution(puddleComponent.Owner, solution, sound)) continue;
|
|
||||||
|
|
||||||
return puddleComponent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var puddleEnt = serverEntityManager.SpawnEntity(prototype, spillGridCoords);
|
|
||||||
var newPuddleComponent = IoCManager.Resolve<IEntityManager>().GetComponent<PuddleComponent>(puddleEnt);
|
|
||||||
|
|
||||||
puddleSystem.TryAddSolution(newPuddleComponent.Owner, solution, sound);
|
|
||||||
|
|
||||||
return newPuddleComponent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -38,7 +38,6 @@ namespace Content.Server.Fluids.EntitySystems
|
|||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
|
||||||
SubscribeLocalEvent<PuddleComponent, UnanchoredEvent>(OnUnanchored);
|
SubscribeLocalEvent<PuddleComponent, UnanchoredEvent>(OnUnanchored);
|
||||||
SubscribeLocalEvent<SpillableComponent, GetOtherVerbsEvent>(AddSpillVerb);
|
|
||||||
SubscribeLocalEvent<PuddleComponent, ExaminedEvent>(HandlePuddleExamined);
|
SubscribeLocalEvent<PuddleComponent, ExaminedEvent>(HandlePuddleExamined);
|
||||||
SubscribeLocalEvent<PuddleComponent, SolutionChangedEvent>(OnUpdate);
|
SubscribeLocalEvent<PuddleComponent, SolutionChangedEvent>(OnUpdate);
|
||||||
SubscribeLocalEvent<PuddleComponent, ComponentInit>(OnInit);
|
SubscribeLocalEvent<PuddleComponent, ComponentInit>(OnInit);
|
||||||
@@ -88,26 +87,6 @@ namespace Content.Server.Fluids.EntitySystems
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddSpillVerb(EntityUid uid, SpillableComponent component, GetOtherVerbsEvent args)
|
|
||||||
{
|
|
||||||
if (!args.CanAccess || !args.CanInteract)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!_solutionContainerSystem.TryGetDrainableSolution(args.Target, out var solution))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (solution.DrainAvailable == FixedPoint2.Zero)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Verb verb = new();
|
|
||||||
verb.Text = Loc.GetString("spill-target-verb-get-data-text");
|
|
||||||
// TODO VERB ICONS spill icon? pouring out a glass/beaker?
|
|
||||||
verb.Act = () => _solutionContainerSystem.SplitSolution(args.Target,
|
|
||||||
solution, solution.DrainAvailable).SpillAt(EntityManager.GetComponent<TransformComponent>(args.Target).Coordinates, "PuddleSmear");
|
|
||||||
verb.Impact = LogImpact.Medium; // dangerous reagent reaction are logged separately.
|
|
||||||
args.Verbs.Add(verb);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandlePuddleExamined(EntityUid uid, PuddleComponent component, ExaminedEvent args)
|
private void HandlePuddleExamined(EntityUid uid, PuddleComponent component, ExaminedEvent args)
|
||||||
{
|
{
|
||||||
if (EntityManager.TryGetComponent<SlipperyComponent>(uid, out var slippery) && slippery.Slippery)
|
if (EntityManager.TryGetComponent<SlipperyComponent>(uid, out var slippery) && slippery.Slippery)
|
||||||
|
|||||||
@@ -1,12 +1,22 @@
|
|||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Linq;
|
||||||
|
using Content.Server.Administration.Logs;
|
||||||
using Content.Server.Chemistry.EntitySystems;
|
using Content.Server.Chemistry.EntitySystems;
|
||||||
using Content.Server.Construction.Components;
|
using Content.Server.Coordinates.Helpers;
|
||||||
using Content.Server.Fluids.Components;
|
using Content.Server.Fluids.Components;
|
||||||
using Content.Shared.Examine;
|
using Content.Shared.Chemistry.Components;
|
||||||
|
using Content.Shared.Chemistry.Reagent;
|
||||||
|
using Content.Shared.Database;
|
||||||
|
using Content.Shared.FixedPoint;
|
||||||
using Content.Shared.Throwing;
|
using Content.Shared.Throwing;
|
||||||
using Content.Shared.Verbs;
|
using Content.Shared.Verbs;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Localization;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
namespace Content.Server.Fluids.EntitySystems;
|
namespace Content.Server.Fluids.EntitySystems;
|
||||||
|
|
||||||
@@ -14,20 +24,178 @@ namespace Content.Server.Fluids.EntitySystems;
|
|||||||
public class SpillableSystem : EntitySystem
|
public class SpillableSystem : EntitySystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
|
[Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
|
||||||
|
[Dependency] private readonly PuddleSystem _puddleSystem = default!;
|
||||||
|
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||||
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
|
[Dependency] private readonly IEntityLookup _entityLookup = default!;
|
||||||
|
[Dependency] private readonly GridTileLookupSystem _gridTileLookupSystem = default!;
|
||||||
|
[Dependency] private readonly AdminLogSystem _logSystem = default!;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
SubscribeLocalEvent<SpillableComponent, LandEvent>(SpillOnLand);
|
SubscribeLocalEvent<SpillableComponent, LandEvent>(SpillOnLand);
|
||||||
|
SubscribeLocalEvent<SpillableComponent, GetOtherVerbsEvent>(AddSpillVerb);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SpillOnLand(EntityUid uid, SpillableComponent component, LandEvent args) {
|
/// <summary>
|
||||||
if (args.User != null && _solutionContainerSystem.TryGetSolution(uid, component.SolutionName, out var solutionComponent))
|
/// Spills the specified solution at the entity's location if possible.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="uid">
|
||||||
|
/// The entity to use as a location to spill the solution at.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="solution">Initial solution for the prototype.</param>
|
||||||
|
/// <param name="prototype">The prototype to use.</param>
|
||||||
|
/// <param name="sound">Play the spill sound.</param>
|
||||||
|
/// <param name="combine">Whether to attempt to merge with existing puddles</param>
|
||||||
|
/// <param name="transformComponent">Optional Transform component</param>
|
||||||
|
/// <returns>The puddle if one was created, null otherwise.</returns>
|
||||||
|
public PuddleComponent? SpillAt(EntityUid uid, Solution solution, string prototype,
|
||||||
|
bool sound = true, bool combine = true, TransformComponent? transformComponent = null)
|
||||||
|
{
|
||||||
|
return !Resolve(uid, ref transformComponent, false)
|
||||||
|
? null
|
||||||
|
: SpillAt(solution, transformComponent.Coordinates, prototype, sound: sound, combine: combine);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SpillOnLand(EntityUid uid, SpillableComponent component, LandEvent args)
|
||||||
|
{
|
||||||
|
if (!_solutionContainerSystem.TryGetSolution(uid, component.SolutionName, out var solution)) return;
|
||||||
|
|
||||||
|
if (args.User != null)
|
||||||
{
|
{
|
||||||
_solutionContainerSystem
|
_logSystem.Add(LogType.Landed,
|
||||||
.Drain(uid, solutionComponent, solutionComponent.DrainAvailable)
|
$"{EntityManager.ToPrettyString(uid)} spilled {SolutionContainerSystem.ToPrettyString(solution)} on landing");
|
||||||
.SpillAt(EntityManager.GetComponent<TransformComponent>(uid).Coordinates, "PuddleSmear");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var drainedSolution = _solutionContainerSystem.Drain(uid, solution, solution.DrainAvailable);
|
||||||
|
SpillAt(drainedSolution, EntityManager.GetComponent<TransformComponent>(uid).Coordinates, "PuddleSmear");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void AddSpillVerb(EntityUid uid, SpillableComponent component, GetOtherVerbsEvent args)
|
||||||
|
{
|
||||||
|
if (!args.CanAccess || !args.CanInteract)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!_solutionContainerSystem.TryGetDrainableSolution(args.Target, out var solution))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (solution.DrainAvailable == FixedPoint2.Zero)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Verb verb = new();
|
||||||
|
verb.Text = Loc.GetString("spill-target-verb-get-data-text");
|
||||||
|
// TODO VERB ICONS spill icon? pouring out a glass/beaker?
|
||||||
|
verb.Act = () =>
|
||||||
|
{
|
||||||
|
var puddleSolution = _solutionContainerSystem.SplitSolution(args.Target,
|
||||||
|
solution, solution.DrainAvailable);
|
||||||
|
SpillAt(puddleSolution, Transform(args.Target).Coordinates, "PuddleSmear");
|
||||||
|
};
|
||||||
|
verb.Impact = LogImpact.Medium; // dangerous reagent reaction are logged separately.
|
||||||
|
args.Verbs.Add(verb);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Spills solution at the specified grid coordinates.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="solution">Initial solution for the prototype.</param>
|
||||||
|
/// <param name="coordinates">The coordinates to spill the solution at.</param>
|
||||||
|
/// <param name="prototype">The prototype to use.</param>
|
||||||
|
/// <param name="overflow">If the puddle overflow will be calculated. Defaults to true.</param>
|
||||||
|
/// <param name="sound">Whether or not to play the spill sound.</param>
|
||||||
|
/// <param name="combine">Whether to attempt to merge with existing puddles</param>
|
||||||
|
/// <returns>The puddle if one was created, null otherwise.</returns>
|
||||||
|
public PuddleComponent? SpillAt(Solution solution, EntityCoordinates coordinates, string prototype,
|
||||||
|
bool overflow = true, bool sound = true, bool combine = true)
|
||||||
|
{
|
||||||
|
if (solution.TotalVolume == 0) return null;
|
||||||
|
|
||||||
|
|
||||||
|
if (!_mapManager.TryGetGrid(coordinates.GetGridId(EntityManager), out var mapGrid))
|
||||||
|
return null; // Let's not spill to space.
|
||||||
|
|
||||||
|
return SpillAt(mapGrid.GetTileRef(coordinates), solution, prototype, overflow, sound,
|
||||||
|
combine: combine);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetPuddle(TileRef tileRef, [NotNullWhen(true)] out PuddleComponent? puddle)
|
||||||
|
{
|
||||||
|
foreach (var entity in tileRef.GetEntitiesInTileFast(_gridTileLookupSystem))
|
||||||
|
{
|
||||||
|
if (EntityManager.TryGetComponent(entity, out PuddleComponent? p))
|
||||||
|
{
|
||||||
|
puddle = p;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
puddle = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PuddleComponent? SpillAt(TileRef tileRef, Solution solution, string prototype,
|
||||||
|
bool overflow = true, bool sound = true, bool noTileReact = false, bool combine = true)
|
||||||
|
{
|
||||||
|
if (solution.TotalVolume <= 0) return null;
|
||||||
|
|
||||||
|
// If space return early, let that spill go out into the void
|
||||||
|
if (tileRef.Tile.IsEmpty) return null;
|
||||||
|
|
||||||
|
var gridId = tileRef.GridIndex;
|
||||||
|
if (!_mapManager.TryGetGrid(gridId, out var mapGrid)) return null; // Let's not spill to invalid grids.
|
||||||
|
|
||||||
|
if (!noTileReact)
|
||||||
|
{
|
||||||
|
// First, do all tile reactions
|
||||||
|
foreach (var (reagentId, quantity) in solution.Contents)
|
||||||
|
{
|
||||||
|
var proto = _prototypeManager.Index<ReagentPrototype>(reagentId);
|
||||||
|
proto.ReactionTile(tileRef, quantity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tile reactions used up everything.
|
||||||
|
if (solution.CurrentVolume == FixedPoint2.Zero)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// Get normalized co-ordinate for spill location and spill it in the centre
|
||||||
|
// TODO: Does SnapGrid or something else already do this?
|
||||||
|
var spillGridCoords = mapGrid.GridTileToWorld(tileRef.GridIndices);
|
||||||
|
|
||||||
|
var spillEntities = _entityLookup.GetEntitiesIntersecting(mapGrid.ParentMapId, spillGridCoords.Position).ToArray();
|
||||||
|
foreach (var spillEntity in spillEntities)
|
||||||
|
{
|
||||||
|
if (_solutionContainerSystem.TryGetRefillableSolution(spillEntity, out var solutionContainerComponent))
|
||||||
|
{
|
||||||
|
_solutionContainerSystem.Refill(spillEntity, solutionContainerComponent,
|
||||||
|
solution.SplitSolution(FixedPoint2.Min(
|
||||||
|
solutionContainerComponent.AvailableVolume,
|
||||||
|
solutionContainerComponent.MaxSpillRefill))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (combine)
|
||||||
|
{
|
||||||
|
foreach (var spillEntity in spillEntities)
|
||||||
|
{
|
||||||
|
if (!EntityManager.TryGetComponent(spillEntity, out PuddleComponent? puddleComponent)) continue;
|
||||||
|
|
||||||
|
if (!overflow && _puddleSystem.WouldOverflow(puddleComponent.Owner, solution, puddleComponent))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (!_puddleSystem.TryAddSolution(puddleComponent.Owner, solution, sound)) continue;
|
||||||
|
|
||||||
|
return puddleComponent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var puddleEnt = EntityManager.SpawnEntity(prototype, spillGridCoords);
|
||||||
|
var newPuddleComponent = EntityManager.GetComponent<PuddleComponent>(puddleEnt);
|
||||||
|
|
||||||
|
_puddleSystem.TryAddSolution(newPuddleComponent.Owner, solution, sound);
|
||||||
|
|
||||||
|
return newPuddleComponent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ public sealed partial class InstrumentSystem : SharedInstrumentSystem
|
|||||||
|
|
||||||
if (instrument.InstrumentPlayer.AttachedEntity is {Valid: true} mob)
|
if (instrument.InstrumentPlayer.AttachedEntity is {Valid: true} mob)
|
||||||
{
|
{
|
||||||
_stunSystem.TryParalyze(mob, TimeSpan.FromSeconds(1));
|
_stunSystem.TryParalyze(mob, TimeSpan.FromSeconds(1), true);
|
||||||
|
|
||||||
instrument.Owner.PopupMessage(mob, "instrument-component-finger-cramps-max-message");
|
instrument.Owner.PopupMessage(mob, "instrument-component-finger-cramps-max-message");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using Content.Shared.Chemistry.Reagent;
|
|
||||||
using Content.Shared.Sound;
|
using Content.Shared.Sound;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
@@ -7,6 +6,7 @@ using Robust.Shared.ViewVariables;
|
|||||||
using Content.Server.Nutrition.EntitySystems;
|
using Content.Server.Nutrition.EntitySystems;
|
||||||
using Content.Shared.FixedPoint;
|
using Content.Shared.FixedPoint;
|
||||||
using Robust.Shared.Analyzers;
|
using Robust.Shared.Analyzers;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
namespace Content.Server.Nutrition.Components
|
namespace Content.Server.Nutrition.Components
|
||||||
{
|
{
|
||||||
@@ -50,8 +50,9 @@ namespace Content.Server.Nutrition.Components
|
|||||||
public float ForceFeedDelay = 3;
|
public float ForceFeedDelay = 3;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// If true, this drink has some DoAfter active (someone is being force fed).
|
/// Token for interrupting a do-after action (e.g., force feeding). If not null, implies component is
|
||||||
|
/// currently "in use".
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool InUse = false;
|
public CancellationTokenSource? CancelToken;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Threading;
|
||||||
using Content.Server.Chemistry.EntitySystems;
|
using Content.Server.Chemistry.EntitySystems;
|
||||||
using Content.Server.Nutrition.EntitySystems;
|
using Content.Server.Nutrition.EntitySystems;
|
||||||
using Content.Shared.FixedPoint;
|
using Content.Shared.FixedPoint;
|
||||||
@@ -55,9 +56,10 @@ namespace Content.Server.Nutrition.Components
|
|||||||
public float ForceFeedDelay = 3;
|
public float ForceFeedDelay = 3;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// If true, this food has some DoAfter active (someone is being force fed).
|
/// Token for interrupting a do-after action (e.g., force feeding). If not null, implies component is
|
||||||
|
/// currently "in use".
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool InUse = false;
|
public CancellationTokenSource? CancelToken;
|
||||||
|
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
public int UsesRemaining
|
public int UsesRemaining
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Analyzers;
|
||||||
|
using Robust.Shared.ViewVariables;
|
||||||
|
using Robust.Shared.Serialization.Manager.Attributes;
|
||||||
|
|
||||||
|
namespace Content.Server.Nutrition.EntitySystems;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Component that denotes a piece of clothing that blocks the mouth or otherwise prevents eating & drinking.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// In the event that more head-wear & mask functionality is added (like identity systems, or raising/lowering of
|
||||||
|
/// masks), then this component might become redundant.
|
||||||
|
/// </remarks>
|
||||||
|
[RegisterComponent, Friend(typeof(FoodSystem), typeof(DrinkSystem))]
|
||||||
|
public class IngestionBlockerComponent : Component
|
||||||
|
{
|
||||||
|
public override string Name => "IngestionBlocker";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Is this component currently blocking consumption.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
[DataField("enabled")]
|
||||||
|
public bool Enabled { get; set; } = true;
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using Content.Server.Chemistry.EntitySystems;
|
using Content.Server.Chemistry.EntitySystems;
|
||||||
using Content.Server.Fluids.Components;
|
using Content.Server.Fluids.Components;
|
||||||
|
using Content.Server.Fluids.EntitySystems;
|
||||||
using Content.Server.Nutrition.Components;
|
using Content.Server.Nutrition.Components;
|
||||||
using Content.Server.Popups;
|
using Content.Server.Popups;
|
||||||
using Content.Shared.Audio;
|
using Content.Shared.Audio;
|
||||||
@@ -20,6 +21,7 @@ namespace Content.Server.Nutrition.EntitySystems
|
|||||||
public class CreamPieSystem : SharedCreamPieSystem
|
public class CreamPieSystem : SharedCreamPieSystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly SolutionContainerSystem _solutionsSystem = default!;
|
[Dependency] private readonly SolutionContainerSystem _solutionsSystem = default!;
|
||||||
|
[Dependency] private readonly SpillableSystem _spillableSystem = default!;
|
||||||
|
|
||||||
protected override void SplattedCreamPie(EntityUid uid, CreamPieComponent creamPie)
|
protected override void SplattedCreamPie(EntityUid uid, CreamPieComponent creamPie)
|
||||||
{
|
{
|
||||||
@@ -27,7 +29,7 @@ namespace Content.Server.Nutrition.EntitySystems
|
|||||||
|
|
||||||
if (IoCManager.Resolve<IEntityManager>().TryGetComponent<FoodComponent?>(creamPie.Owner, out var foodComp) && _solutionsSystem.TryGetSolution(creamPie.Owner, foodComp.SolutionName, out var solution))
|
if (IoCManager.Resolve<IEntityManager>().TryGetComponent<FoodComponent?>(creamPie.Owner, out var foodComp) && _solutionsSystem.TryGetSolution(creamPie.Owner, foodComp.SolutionName, out var solution))
|
||||||
{
|
{
|
||||||
solution.SpillAt(creamPie.Owner, "PuddleSmear", false);
|
_spillableSystem.SpillAt(creamPie.Owner, solution, "PuddleSmear", false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using Content.Server.Chemistry.Components.SolutionManager;
|
|||||||
using Content.Server.Chemistry.EntitySystems;
|
using Content.Server.Chemistry.EntitySystems;
|
||||||
using Content.Server.DoAfter;
|
using Content.Server.DoAfter;
|
||||||
using Content.Server.Fluids.Components;
|
using Content.Server.Fluids.Components;
|
||||||
|
using Content.Server.Fluids.EntitySystems;
|
||||||
using Content.Server.Nutrition.Components;
|
using Content.Server.Nutrition.Components;
|
||||||
using Content.Server.Popups;
|
using Content.Server.Popups;
|
||||||
using Content.Shared.ActionBlocker;
|
using Content.Shared.ActionBlocker;
|
||||||
@@ -14,6 +15,7 @@ using Content.Shared.Chemistry.Reagent;
|
|||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
using Content.Shared.Examine;
|
using Content.Shared.Examine;
|
||||||
using Content.Shared.FixedPoint;
|
using Content.Shared.FixedPoint;
|
||||||
|
using Content.Shared.Hands;
|
||||||
using Content.Shared.Interaction;
|
using Content.Shared.Interaction;
|
||||||
using Content.Shared.Interaction.Helpers;
|
using Content.Shared.Interaction.Helpers;
|
||||||
using Content.Shared.Nutrition.Components;
|
using Content.Shared.Nutrition.Components;
|
||||||
@@ -42,6 +44,7 @@ namespace Content.Server.Nutrition.EntitySystems
|
|||||||
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
|
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
|
||||||
[Dependency] private readonly SharedAdminLogSystem _logSystem = default!;
|
[Dependency] private readonly SharedAdminLogSystem _logSystem = default!;
|
||||||
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
|
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
|
||||||
|
[Dependency] private readonly SpillableSystem _spillableSystem = default!;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
@@ -51,12 +54,26 @@ namespace Content.Server.Nutrition.EntitySystems
|
|||||||
SubscribeLocalEvent<DrinkComponent, ComponentInit>(OnDrinkInit);
|
SubscribeLocalEvent<DrinkComponent, ComponentInit>(OnDrinkInit);
|
||||||
SubscribeLocalEvent<DrinkComponent, LandEvent>(HandleLand);
|
SubscribeLocalEvent<DrinkComponent, LandEvent>(HandleLand);
|
||||||
SubscribeLocalEvent<DrinkComponent, UseInHandEvent>(OnUse);
|
SubscribeLocalEvent<DrinkComponent, UseInHandEvent>(OnUse);
|
||||||
|
SubscribeLocalEvent<DrinkComponent, HandDeselectedEvent>(OnDrinkDeselected);
|
||||||
SubscribeLocalEvent<DrinkComponent, AfterInteractEvent>(AfterInteract);
|
SubscribeLocalEvent<DrinkComponent, AfterInteractEvent>(AfterInteract);
|
||||||
SubscribeLocalEvent<DrinkComponent, ExaminedEvent>(OnExamined);
|
SubscribeLocalEvent<DrinkComponent, ExaminedEvent>(OnExamined);
|
||||||
SubscribeLocalEvent<SharedBodyComponent, ForceDrinkEvent>(OnForceDrink);
|
SubscribeLocalEvent<SharedBodyComponent, ForceDrinkEvent>(OnForceDrink);
|
||||||
SubscribeLocalEvent<ForceDrinkCancelledEvent>(OnForceDrinkCancelled);
|
SubscribeLocalEvent<ForceDrinkCancelledEvent>(OnForceDrinkCancelled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If the user is currently forcing someone do drink, this cancels the attempt if they swap hands or
|
||||||
|
/// otherwise loose the item. Prevents force-feeding dual-wielding.
|
||||||
|
/// </summary>
|
||||||
|
private void OnDrinkDeselected(EntityUid uid, DrinkComponent component, HandDeselectedEvent args)
|
||||||
|
{
|
||||||
|
if (component.CancelToken != null)
|
||||||
|
{
|
||||||
|
component.CancelToken.Cancel();
|
||||||
|
component.CancelToken = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public bool IsEmpty(EntityUid uid, DrinkComponent? component = null)
|
public bool IsEmpty(EntityUid uid, DrinkComponent? component = null)
|
||||||
{
|
{
|
||||||
if(!Resolve(uid, ref component))
|
if(!Resolve(uid, ref component))
|
||||||
@@ -185,7 +202,7 @@ namespace Content.Server.Nutrition.EntitySystems
|
|||||||
UpdateAppearance(component);
|
UpdateAppearance(component);
|
||||||
|
|
||||||
var solution = _solutionContainerSystem.Drain(uid, interactions, interactions.DrainAvailable);
|
var solution = _solutionContainerSystem.Drain(uid, interactions, interactions.DrainAvailable);
|
||||||
solution.SpillAt(uid, "PuddleSmear");
|
_spillableSystem.SpillAt(uid, solution, "PuddleSmear");
|
||||||
|
|
||||||
SoundSystem.Play(Filter.Pvs(uid), component.BurstSound.GetSound(), uid, AudioParams.Default.WithVolume(-4));
|
SoundSystem.Play(Filter.Pvs(uid), component.BurstSound.GetSound(), uid, AudioParams.Default.WithVolume(-4));
|
||||||
}
|
}
|
||||||
@@ -235,6 +252,14 @@ namespace Content.Server.Nutrition.EntitySystems
|
|||||||
if (!Resolve(uid, ref drink))
|
if (!Resolve(uid, ref drink))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
// if currently being used to force-feed, cancel that action.
|
||||||
|
if (drink.CancelToken != null)
|
||||||
|
{
|
||||||
|
drink.CancelToken.Cancel();
|
||||||
|
drink.CancelToken = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!drink.Opened)
|
if (!drink.Opened)
|
||||||
{
|
{
|
||||||
_popupSystem.PopupEntity(Loc.GetString("drink-component-try-use-drink-not-open",
|
_popupSystem.PopupEntity(Loc.GetString("drink-component-try-use-drink-not-open",
|
||||||
@@ -260,13 +285,8 @@ namespace Content.Server.Nutrition.EntitySystems
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_foodSystem.IsMouthBlocked(userUid, out var blocker))
|
if (_foodSystem.IsMouthBlocked(userUid, userUid))
|
||||||
{
|
|
||||||
var name = EntityManager.GetComponent<MetaDataComponent>(blocker.Value).EntityName;
|
|
||||||
_popupSystem.PopupEntity(Loc.GetString("food-system-remove-mask", ("entity", name)),
|
|
||||||
userUid, Filter.Entities(userUid));
|
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
|
|
||||||
var transferAmount = FixedPoint2.Min(drink.TransferAmount, drinkSolution.DrainAvailable);
|
var transferAmount = FixedPoint2.Min(drink.TransferAmount, drinkSolution.DrainAvailable);
|
||||||
var drain = _solutionContainerSystem.Drain(uid, drinkSolution, transferAmount);
|
var drain = _solutionContainerSystem.Drain(uid, drinkSolution, transferAmount);
|
||||||
@@ -281,7 +301,7 @@ namespace Content.Server.Nutrition.EntitySystems
|
|||||||
|
|
||||||
if (EntityManager.HasComponent<RefillableSolutionComponent>(uid))
|
if (EntityManager.HasComponent<RefillableSolutionComponent>(uid))
|
||||||
{
|
{
|
||||||
drain.SpillAt(userUid, "PuddleSmear");
|
_spillableSystem.SpillAt(userUid, drain, "PuddleSmear");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -312,8 +332,12 @@ namespace Content.Server.Nutrition.EntitySystems
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
// cannot stack do-afters
|
// cannot stack do-afters
|
||||||
if (drink.InUse)
|
if (drink.CancelToken != null)
|
||||||
return false;
|
{
|
||||||
|
drink.CancelToken.Cancel();
|
||||||
|
drink.CancelToken = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!EntityManager.HasComponent<SharedBodyComponent>(targetUid))
|
if (!EntityManager.HasComponent<SharedBodyComponent>(targetUid))
|
||||||
return false;
|
return false;
|
||||||
@@ -333,13 +357,8 @@ namespace Content.Server.Nutrition.EntitySystems
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_foodSystem.IsMouthBlocked(targetUid, out var blocker))
|
if (_foodSystem.IsMouthBlocked(targetUid, userUid))
|
||||||
{
|
|
||||||
var name = EntityManager.GetComponent<MetaDataComponent>(blocker.Value).EntityName;
|
|
||||||
_popupSystem.PopupEntity(Loc.GetString("food-system-remove-mask", ("entity", name)),
|
|
||||||
userUid, Filter.Entities(userUid));
|
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
|
|
||||||
EntityManager.TryGetComponent(userUid, out MetaDataComponent? meta);
|
EntityManager.TryGetComponent(userUid, out MetaDataComponent? meta);
|
||||||
var userName = meta?.EntityName ?? string.Empty;
|
var userName = meta?.EntityName ?? string.Empty;
|
||||||
@@ -347,7 +366,8 @@ namespace Content.Server.Nutrition.EntitySystems
|
|||||||
_popupSystem.PopupEntity(Loc.GetString("drink-component-force-feed", ("user", userName)),
|
_popupSystem.PopupEntity(Loc.GetString("drink-component-force-feed", ("user", userName)),
|
||||||
userUid, Filter.Entities(targetUid));
|
userUid, Filter.Entities(targetUid));
|
||||||
|
|
||||||
_doAfterSystem.DoAfter(new DoAfterEventArgs(userUid, drink.ForceFeedDelay, target: targetUid)
|
drink.CancelToken = new();
|
||||||
|
_doAfterSystem.DoAfter(new DoAfterEventArgs(userUid, drink.ForceFeedDelay, drink.CancelToken.Token, targetUid)
|
||||||
{
|
{
|
||||||
BreakOnUserMove = true,
|
BreakOnUserMove = true,
|
||||||
BreakOnDamage = true,
|
BreakOnDamage = true,
|
||||||
@@ -355,13 +375,12 @@ namespace Content.Server.Nutrition.EntitySystems
|
|||||||
BreakOnTargetMove = true,
|
BreakOnTargetMove = true,
|
||||||
MovementThreshold = 1.0f,
|
MovementThreshold = 1.0f,
|
||||||
TargetFinishedEvent = new ForceDrinkEvent(userUid, drink, drinkSolution),
|
TargetFinishedEvent = new ForceDrinkEvent(userUid, drink, drinkSolution),
|
||||||
BroadcastCancelledEvent = new ForceDrinkCancelledEvent(drink)
|
BroadcastCancelledEvent = new ForceDrinkCancelledEvent(drink),
|
||||||
});
|
});
|
||||||
|
|
||||||
// logging
|
// logging
|
||||||
_logSystem.Add(LogType.ForceFeed, LogImpact.Medium, $"{userUid} is forcing {targetUid} to drink {uid}");
|
_logSystem.Add(LogType.ForceFeed, LogImpact.Medium, $"{userUid} is forcing {targetUid} to drink {uid}");
|
||||||
|
|
||||||
drink.InUse = true;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -370,7 +389,10 @@ namespace Content.Server.Nutrition.EntitySystems
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void OnForceDrink(EntityUid uid, SharedBodyComponent body, ForceDrinkEvent args)
|
private void OnForceDrink(EntityUid uid, SharedBodyComponent body, ForceDrinkEvent args)
|
||||||
{
|
{
|
||||||
args.Drink.InUse = false;
|
if (args.Drink.Deleted)
|
||||||
|
return;
|
||||||
|
|
||||||
|
args.Drink.CancelToken = null;
|
||||||
var transferAmount = FixedPoint2.Min(args.Drink.TransferAmount, args.DrinkSolution.DrainAvailable);
|
var transferAmount = FixedPoint2.Min(args.Drink.TransferAmount, args.DrinkSolution.DrainAvailable);
|
||||||
var drained = _solutionContainerSystem.Drain((args.Drink).Owner, args.DrinkSolution, transferAmount);
|
var drained = _solutionContainerSystem.Drain((args.Drink).Owner, args.DrinkSolution, transferAmount);
|
||||||
|
|
||||||
@@ -379,7 +401,7 @@ namespace Content.Server.Nutrition.EntitySystems
|
|||||||
_popupSystem.PopupEntity(Loc.GetString("drink-component-try-use-drink-cannot-drink-other"),
|
_popupSystem.PopupEntity(Loc.GetString("drink-component-try-use-drink-cannot-drink-other"),
|
||||||
uid, Filter.Entities(args.User));
|
uid, Filter.Entities(args.User));
|
||||||
|
|
||||||
drained.SpillAt(uid, "PuddleSmear");
|
_spillableSystem.SpillAt(uid, drained, "PuddleSmear");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -392,7 +414,7 @@ namespace Content.Server.Nutrition.EntitySystems
|
|||||||
_popupSystem.PopupEntity(Loc.GetString("drink-component-try-use-drink-had-enough-other"),
|
_popupSystem.PopupEntity(Loc.GetString("drink-component-try-use-drink-had-enough-other"),
|
||||||
uid, Filter.Entities(args.User));
|
uid, Filter.Entities(args.User));
|
||||||
|
|
||||||
drained.SpillAt(uid, "PuddleSmear");
|
_spillableSystem.SpillAt(uid, drained, "PuddleSmear");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -416,31 +438,7 @@ namespace Content.Server.Nutrition.EntitySystems
|
|||||||
|
|
||||||
private void OnForceDrinkCancelled(ForceDrinkCancelledEvent args)
|
private void OnForceDrinkCancelled(ForceDrinkCancelledEvent args)
|
||||||
{
|
{
|
||||||
args.Drink.InUse = false;
|
args.Drink.CancelToken = null;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class ForceDrinkEvent : EntityEventArgs
|
|
||||||
{
|
|
||||||
public readonly EntityUid User;
|
|
||||||
public readonly DrinkComponent Drink;
|
|
||||||
public readonly Solution DrinkSolution;
|
|
||||||
|
|
||||||
public ForceDrinkEvent(EntityUid user, DrinkComponent drink, Solution drinkSolution)
|
|
||||||
{
|
|
||||||
User = user;
|
|
||||||
Drink = drink;
|
|
||||||
DrinkSolution = drinkSolution;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class ForceDrinkCancelledEvent : EntityEventArgs
|
|
||||||
{
|
|
||||||
public readonly DrinkComponent Drink;
|
|
||||||
|
|
||||||
public ForceDrinkCancelledEvent( DrinkComponent drink)
|
|
||||||
{
|
|
||||||
Drink = drink;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ using Content.Server.Popups;
|
|||||||
using Content.Shared.ActionBlocker;
|
using Content.Shared.ActionBlocker;
|
||||||
using Content.Shared.Administration.Logs;
|
using Content.Shared.Administration.Logs;
|
||||||
using Content.Shared.Body.Components;
|
using Content.Shared.Body.Components;
|
||||||
using Content.Shared.Chemistry.Components;
|
|
||||||
using Content.Shared.Chemistry.Reagent;
|
using Content.Shared.Chemistry.Reagent;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
using Content.Shared.FixedPoint;
|
using Content.Shared.FixedPoint;
|
||||||
@@ -27,7 +26,11 @@ using Robust.Shared.GameObjects;
|
|||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
using Robust.Shared.Localization;
|
using Robust.Shared.Localization;
|
||||||
using Robust.Shared.Player;
|
using Robust.Shared.Player;
|
||||||
|
using System.Collections.Generic;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
|
using Content.Server.Inventory.Components;
|
||||||
|
using Content.Shared.Inventory;
|
||||||
|
using Content.Shared.Hands;
|
||||||
|
|
||||||
namespace Content.Server.Nutrition.EntitySystems
|
namespace Content.Server.Nutrition.EntitySystems
|
||||||
{
|
{
|
||||||
@@ -51,9 +54,24 @@ namespace Content.Server.Nutrition.EntitySystems
|
|||||||
|
|
||||||
SubscribeLocalEvent<FoodComponent, UseInHandEvent>(OnUseFoodInHand);
|
SubscribeLocalEvent<FoodComponent, UseInHandEvent>(OnUseFoodInHand);
|
||||||
SubscribeLocalEvent<FoodComponent, AfterInteractEvent>(OnFeedFood);
|
SubscribeLocalEvent<FoodComponent, AfterInteractEvent>(OnFeedFood);
|
||||||
|
SubscribeLocalEvent<FoodComponent, HandDeselectedEvent>(OnFoodDeselected);
|
||||||
SubscribeLocalEvent<FoodComponent, GetInteractionVerbsEvent>(AddEatVerb);
|
SubscribeLocalEvent<FoodComponent, GetInteractionVerbsEvent>(AddEatVerb);
|
||||||
SubscribeLocalEvent<SharedBodyComponent, ForceFeedEvent>(OnForceFeed);
|
SubscribeLocalEvent<SharedBodyComponent, ForceFeedEvent>(OnForceFeed);
|
||||||
SubscribeLocalEvent<ForceFeedCancelledEvent>(OnForceFeedCancelled);
|
SubscribeLocalEvent<ForceFeedCancelledEvent>(OnForceFeedCancelled);
|
||||||
|
SubscribeLocalEvent<InventoryComponent, IngestionAttemptEvent>(OnInventoryIngestAttempt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If the user is currently force feeding someone, this cancels the attempt if they swap hands or otherwise
|
||||||
|
/// loose the item. Prevents force-feeding dual-wielding.
|
||||||
|
/// </summary>
|
||||||
|
private void OnFoodDeselected(EntityUid uid, FoodComponent component, HandDeselectedEvent args)
|
||||||
|
{
|
||||||
|
if (component.CancelToken != null)
|
||||||
|
{
|
||||||
|
component.CancelToken.Cancel();
|
||||||
|
component.CancelToken = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -119,6 +137,14 @@ namespace Content.Server.Nutrition.EntitySystems
|
|||||||
if (!Resolve(uid, ref food))
|
if (!Resolve(uid, ref food))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
// if currently being used to force-feed, cancel that action.
|
||||||
|
if (food.CancelToken != null)
|
||||||
|
{
|
||||||
|
food.CancelToken.Cancel();
|
||||||
|
food.CancelToken = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (uid == user || //Suppresses self-eating
|
if (uid == user || //Suppresses self-eating
|
||||||
EntityManager.TryGetComponent<MobStateComponent>(uid, out var mobState) && mobState.IsAlive()) // Suppresses eating alive mobs
|
EntityManager.TryGetComponent<MobStateComponent>(uid, out var mobState) && mobState.IsAlive()) // Suppresses eating alive mobs
|
||||||
return false;
|
return false;
|
||||||
@@ -137,11 +163,8 @@ namespace Content.Server.Nutrition.EntitySystems
|
|||||||
!_bodySystem.TryGetComponentsOnMechanisms<StomachComponent>(user, out var stomachs, body))
|
!_bodySystem.TryGetComponentsOnMechanisms<StomachComponent>(user, out var stomachs, body))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (IsMouthBlocked(user, out var blocker))
|
if (IsMouthBlocked(user, user))
|
||||||
{
|
{
|
||||||
var name = EntityManager.GetComponent<MetaDataComponent>(blocker.Value).EntityName;
|
|
||||||
_popupSystem.PopupEntity(Loc.GetString("food-system-remove-mask", ("entity", name)),
|
|
||||||
user, Filter.Entities(user));
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,6 +241,9 @@ namespace Content.Server.Nutrition.EntitySystems
|
|||||||
|
|
||||||
private void AddEatVerb(EntityUid uid, FoodComponent component, GetInteractionVerbsEvent ev)
|
private void AddEatVerb(EntityUid uid, FoodComponent component, GetInteractionVerbsEvent ev)
|
||||||
{
|
{
|
||||||
|
if (component.CancelToken != null)
|
||||||
|
return;
|
||||||
|
|
||||||
if (uid == ev.User ||
|
if (uid == ev.User ||
|
||||||
!ev.CanInteract ||
|
!ev.CanInteract ||
|
||||||
!ev.CanAccess ||
|
!ev.CanAccess ||
|
||||||
@@ -250,6 +276,14 @@ namespace Content.Server.Nutrition.EntitySystems
|
|||||||
if (!Resolve(uid, ref food))
|
if (!Resolve(uid, ref food))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
// if currently being used to force-feed, cancel that action.
|
||||||
|
if (food.CancelToken != null)
|
||||||
|
{
|
||||||
|
food.CancelToken.Cancel();
|
||||||
|
food.CancelToken = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!EntityManager.HasComponent<SharedBodyComponent>(target))
|
if (!EntityManager.HasComponent<SharedBodyComponent>(target))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@@ -264,11 +298,8 @@ namespace Content.Server.Nutrition.EntitySystems
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsMouthBlocked(target, out var blocker))
|
if (IsMouthBlocked(target, user))
|
||||||
{
|
{
|
||||||
var name = EntityManager.GetComponent<MetaDataComponent>(blocker.Value).EntityName;
|
|
||||||
_popupSystem.PopupEntity(Loc.GetString("food-system-remove-mask", ("entity", name)),
|
|
||||||
user, Filter.Entities(user));
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,7 +312,8 @@ namespace Content.Server.Nutrition.EntitySystems
|
|||||||
_popupSystem.PopupEntity(Loc.GetString("food-system-force-feed", ("user", userName)),
|
_popupSystem.PopupEntity(Loc.GetString("food-system-force-feed", ("user", userName)),
|
||||||
user, Filter.Entities(target));
|
user, Filter.Entities(target));
|
||||||
|
|
||||||
_doAfterSystem.DoAfter(new DoAfterEventArgs(user, food.ForceFeedDelay, target: target)
|
food.CancelToken = new();
|
||||||
|
_doAfterSystem.DoAfter(new DoAfterEventArgs(user, food.ForceFeedDelay, food.CancelToken.Token, target)
|
||||||
{
|
{
|
||||||
BreakOnUserMove = true,
|
BreakOnUserMove = true,
|
||||||
BreakOnDamage = true,
|
BreakOnDamage = true,
|
||||||
@@ -295,13 +327,15 @@ namespace Content.Server.Nutrition.EntitySystems
|
|||||||
// logging
|
// logging
|
||||||
_logSystem.Add(LogType.ForceFeed, LogImpact.Medium, $"{user} is forcing {target} to eat {uid}");
|
_logSystem.Add(LogType.ForceFeed, LogImpact.Medium, $"{user} is forcing {target} to eat {uid}");
|
||||||
|
|
||||||
food.InUse = true;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnForceFeed(EntityUid uid, SharedBodyComponent body, ForceFeedEvent args)
|
private void OnForceFeed(EntityUid uid, SharedBodyComponent body, ForceFeedEvent args)
|
||||||
{
|
{
|
||||||
args.Food.InUse = false;
|
if (args.Food.Deleted)
|
||||||
|
return;
|
||||||
|
|
||||||
|
args.Food.CancelToken = null;
|
||||||
|
|
||||||
if (!_bodySystem.TryGetComponentsOnMechanisms<StomachComponent>(uid, out var stomachs, body))
|
if (!_bodySystem.TryGetComponentsOnMechanisms<StomachComponent>(uid, out var stomachs, body))
|
||||||
return;
|
return;
|
||||||
@@ -361,7 +395,7 @@ namespace Content.Server.Nutrition.EntitySystems
|
|||||||
if (!Resolve(uid, ref food) || !Resolve(target, ref body, false))
|
if (!Resolve(uid, ref food) || !Resolve(target, ref body, false))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (IsMouthBlocked(target, out _))
|
if (IsMouthBlocked(target))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!_solutionContainerSystem.TryGetSolution(uid, food.SolutionName, out var foodSolution))
|
if (!_solutionContainerSystem.TryGetSolution(uid, food.SolutionName, out var foodSolution))
|
||||||
@@ -438,66 +472,57 @@ namespace Content.Server.Nutrition.EntitySystems
|
|||||||
|
|
||||||
private void OnForceFeedCancelled(ForceFeedCancelledEvent args)
|
private void OnForceFeedCancelled(ForceFeedCancelledEvent args)
|
||||||
{
|
{
|
||||||
args.Food.InUse = false;
|
args.Food.CancelToken = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Is an entity's mouth accessible, or is it blocked by something like a mask? Does not actually check if
|
/// Block ingestion attempts based on the equipped mask or head-wear
|
||||||
/// the user has a mouth. Body system when?
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsMouthBlocked(EntityUid uid, [NotNullWhen(true)] out EntityUid? blockingEntity,
|
private void OnInventoryIngestAttempt(EntityUid uid, InventoryComponent component, IngestionAttemptEvent args)
|
||||||
InventoryComponent? inventory = null)
|
|
||||||
{
|
{
|
||||||
blockingEntity = null;
|
if (args.Cancelled)
|
||||||
|
return;
|
||||||
|
|
||||||
if (!Resolve(uid, ref inventory, false))
|
IngestionBlockerComponent blocker;
|
||||||
return false;
|
|
||||||
|
|
||||||
// check masks
|
if (component.TryGetSlotItem(EquipmentSlotDefines.Slots.MASK, out ItemComponent? mask) &&
|
||||||
if (inventory.TryGetSlotItem(EquipmentSlotDefines.Slots.MASK, out ItemComponent? mask))
|
EntityManager.TryGetComponent(mask.Owner, out blocker) &&
|
||||||
|
blocker.Enabled)
|
||||||
{
|
{
|
||||||
// For now, lets just assume that any masks always covers the mouth
|
args.Blocker = mask.Owner;
|
||||||
// TODO MASKS if the ability is added to raise/lower masks, this needs to be updated.
|
args.Cancel();
|
||||||
blockingEntity = mask.Owner;
|
return;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// check helmets. Note that not all helmets cover the face.
|
if (component.TryGetSlotItem(EquipmentSlotDefines.Slots.HEAD, out ItemComponent? head) &&
|
||||||
if (inventory.TryGetSlotItem(EquipmentSlotDefines.Slots.HEAD, out ItemComponent? head) &&
|
EntityManager.TryGetComponent(head.Owner, out blocker) &&
|
||||||
EntityManager.TryGetComponent(((IComponent) head).Owner, out TagComponent tag) &&
|
blocker.Enabled)
|
||||||
tag.HasTag("ConcealsFace"))
|
|
||||||
{
|
{
|
||||||
blockingEntity = head.Owner;
|
args.Blocker = head.Owner;
|
||||||
return true;
|
args.Cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check whether the target's mouth is blocked by equipment (masks or head-wear).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="uid">The target whose equipment is checked</param>
|
||||||
|
/// <param name="popupUid">Optional entity that will receive an informative pop-up identifying the blocking
|
||||||
|
/// piece of equipment.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public bool IsMouthBlocked(EntityUid uid, EntityUid? popupUid = null)
|
||||||
|
{
|
||||||
|
var attempt = new IngestionAttemptEvent();
|
||||||
|
RaiseLocalEvent(uid, attempt, false);
|
||||||
|
if (attempt.Cancelled && attempt.Blocker != null && popupUid != null)
|
||||||
|
{
|
||||||
|
var name = EntityManager.GetComponent<MetaDataComponent>(attempt.Blocker.Value).EntityName;
|
||||||
|
_popupSystem.PopupEntity(Loc.GetString("food-system-remove-mask", ("entity", name)),
|
||||||
|
uid, Filter.Entities(popupUid.Value));
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return attempt.Cancelled;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class ForceFeedEvent : EntityEventArgs
|
|
||||||
{
|
|
||||||
public readonly EntityUid User;
|
|
||||||
public readonly FoodComponent Food;
|
|
||||||
public readonly Solution FoodSolution;
|
|
||||||
public readonly List<UtensilComponent> Utensils;
|
|
||||||
|
|
||||||
public ForceFeedEvent(EntityUid user, FoodComponent food, Solution foodSolution, List<UtensilComponent> utensils)
|
|
||||||
{
|
|
||||||
User = user;
|
|
||||||
Food = food;
|
|
||||||
FoodSolution = foodSolution;
|
|
||||||
Utensils = utensils;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class ForceFeedCancelledEvent : EntityEventArgs
|
|
||||||
{
|
|
||||||
public readonly FoodComponent Food;
|
|
||||||
|
|
||||||
public ForceFeedCancelledEvent(FoodComponent food)
|
|
||||||
{
|
|
||||||
Food = food;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
79
Content.Server/Nutrition/IngestionEvents.cs
Normal file
79
Content.Server/Nutrition/IngestionEvents.cs
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
using Content.Server.Nutrition.Components;
|
||||||
|
using Content.Shared.Chemistry.Components;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Content.Server.Nutrition;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised directed at the consumer when attempting to ingest something.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class IngestionAttemptEvent : CancellableEntityEventArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The equipment that is blocking consumption. Should only be non-null if the event was canceled.
|
||||||
|
/// </summary>
|
||||||
|
public EntityUid? Blocker = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised directed at the food after a successful force-feed do-after.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class ForceFeedEvent : EntityEventArgs
|
||||||
|
{
|
||||||
|
public readonly EntityUid User;
|
||||||
|
public readonly FoodComponent Food;
|
||||||
|
public readonly Solution FoodSolution;
|
||||||
|
public readonly List<UtensilComponent> Utensils;
|
||||||
|
|
||||||
|
public ForceFeedEvent(EntityUid user, FoodComponent food, Solution foodSolution, List<UtensilComponent> utensils)
|
||||||
|
{
|
||||||
|
User = user;
|
||||||
|
Food = food;
|
||||||
|
FoodSolution = foodSolution;
|
||||||
|
Utensils = utensils;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised directed at the food after a failed force-feed do-after.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class ForceFeedCancelledEvent : EntityEventArgs
|
||||||
|
{
|
||||||
|
public readonly FoodComponent Food;
|
||||||
|
|
||||||
|
public ForceFeedCancelledEvent(FoodComponent food)
|
||||||
|
{
|
||||||
|
Food = food;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised directed at the drink after a successful force-drink do-after.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class ForceDrinkEvent : EntityEventArgs
|
||||||
|
{
|
||||||
|
public readonly EntityUid User;
|
||||||
|
public readonly DrinkComponent Drink;
|
||||||
|
public readonly Solution DrinkSolution;
|
||||||
|
|
||||||
|
public ForceDrinkEvent(EntityUid user, DrinkComponent drink, Solution drinkSolution)
|
||||||
|
{
|
||||||
|
User = user;
|
||||||
|
Drink = drink;
|
||||||
|
DrinkSolution = drinkSolution;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised directed at the food after a failed force-dink do-after.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class ForceDrinkCancelledEvent : EntityEventArgs
|
||||||
|
{
|
||||||
|
public readonly DrinkComponent Drink;
|
||||||
|
|
||||||
|
public ForceDrinkCancelledEvent(DrinkComponent drink)
|
||||||
|
{
|
||||||
|
Drink = drink;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -242,7 +242,8 @@ namespace Content.Server.PneumaticCannon
|
|||||||
if(EntityManager.TryGetComponent<StatusEffectsComponent?>(data.User, out var status)
|
if(EntityManager.TryGetComponent<StatusEffectsComponent?>(data.User, out var status)
|
||||||
&& comp.Power == PneumaticCannonPower.High)
|
&& comp.Power == PneumaticCannonPower.High)
|
||||||
{
|
{
|
||||||
_stun.TryParalyze(data.User, TimeSpan.FromSeconds(comp.HighPowerStunTime), status);
|
_stun.TryParalyze(data.User, TimeSpan.FromSeconds(comp.HighPowerStunTime), true, status);
|
||||||
|
|
||||||
data.User.PopupMessage(Loc.GetString("pneumatic-cannon-component-power-stun",
|
data.User.PopupMessage(Loc.GetString("pneumatic-cannon-component-power-stun",
|
||||||
("cannon", comp.Owner)));
|
("cannon", comp.Owner)));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,13 +28,13 @@ namespace Content.Server.Speech.EntitySystems
|
|||||||
SubscribeLocalEvent<StutteringAccentComponent, AccentGetEvent>(OnAccent);
|
SubscribeLocalEvent<StutteringAccentComponent, AccentGetEvent>(OnAccent);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void DoStutter(EntityUid uid, TimeSpan time, StatusEffectsComponent? status = null, SharedAlertsComponent? alerts = null)
|
public override void DoStutter(EntityUid uid, TimeSpan time, bool refresh, StatusEffectsComponent? status = null, SharedAlertsComponent? alerts = null)
|
||||||
{
|
{
|
||||||
if (!Resolve(uid, ref status, false))
|
if (!Resolve(uid, ref status, false))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!_statusEffectsSystem.HasStatusEffect(uid, StutterKey, status))
|
if (!_statusEffectsSystem.HasStatusEffect(uid, StutterKey, status))
|
||||||
_statusEffectsSystem.TryAddStatusEffect<StutteringAccentComponent>(uid, StutterKey, time, status, alerts);
|
_statusEffectsSystem.TryAddStatusEffect<StutteringAccentComponent>(uid, StutterKey, time, refresh, status, alerts);
|
||||||
else
|
else
|
||||||
_statusEffectsSystem.TryAddTime(uid, StutterKey, time, status);
|
_statusEffectsSystem.TryAddTime(uid, StutterKey, time, status);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -365,18 +365,12 @@ namespace Content.Server.Storage.Components
|
|||||||
// Trying to add while open just dumps it on the ground below us.
|
// Trying to add while open just dumps it on the ground below us.
|
||||||
if (Open)
|
if (Open)
|
||||||
{
|
{
|
||||||
IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(entity).WorldPosition = IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(Owner).WorldPosition;
|
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||||
|
entMan.GetComponent<TransformComponent>(entity).WorldPosition = entMan.GetComponent<TransformComponent>(Owner).WorldPosition;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Contents.Insert(entity)) return false;
|
return Contents.Insert(entity);
|
||||||
|
|
||||||
IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(entity).LocalPosition = Vector2.Zero;
|
|
||||||
if (IoCManager.Resolve<IEntityManager>().TryGetComponent(entity, out IPhysBody? body))
|
|
||||||
{
|
|
||||||
body.CanCollide = false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ using Robust.Shared.GameObjects;
|
|||||||
|
|
||||||
namespace Content.Server.Storage.Components
|
namespace Content.Server.Storage.Components
|
||||||
{
|
{
|
||||||
public interface IStorageComponent
|
public interface IStorageComponent : IComponent
|
||||||
{
|
{
|
||||||
bool Remove(EntityUid entity);
|
bool Remove(EntityUid entity);
|
||||||
bool Insert(EntityUid entity);
|
bool Insert(EntityUid entity);
|
||||||
|
|||||||
@@ -48,10 +48,18 @@ namespace Content.Server.Storage.Components
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||||
|
var transform = entMan.GetComponent<TransformComponent>(Owner);
|
||||||
|
|
||||||
for (var i = 0; i < storageItem.Amount; i++)
|
for (var i = 0; i < storageItem.Amount; i++)
|
||||||
{
|
{
|
||||||
storage.Insert(
|
|
||||||
IoCManager.Resolve<IEntityManager>().SpawnEntity(storageItem.PrototypeId, IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(Owner).Coordinates));
|
var ent = entMan.SpawnEntity(storageItem.PrototypeId, transform.Coordinates);
|
||||||
|
|
||||||
|
if (storage.Insert(ent)) continue;
|
||||||
|
|
||||||
|
Logger.ErrorS("storage", $"Tried to StorageFill {storageItem.PrototypeId} inside {Owner} but can't.");
|
||||||
|
entMan.DeleteEntity(ent);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(storageItem.GroupId)) alreadySpawnedGroups.Add(storageItem.GroupId);
|
if (!string.IsNullOrEmpty(storageItem.GroupId)) alreadySpawnedGroups.Add(storageItem.GroupId);
|
||||||
|
|||||||
@@ -34,12 +34,12 @@ namespace Content.Server.Stunnable
|
|||||||
// Let the actual methods log errors for these.
|
// Let the actual methods log errors for these.
|
||||||
Resolve(otherUid, ref alerts, ref standingState, ref appearance, false);
|
Resolve(otherUid, ref alerts, ref standingState, ref appearance, false);
|
||||||
|
|
||||||
_stunSystem.TryStun(otherUid, TimeSpan.FromSeconds(component.StunAmount), status, alerts);
|
_stunSystem.TryStun(otherUid, TimeSpan.FromSeconds(component.StunAmount), true, status, alerts);
|
||||||
|
|
||||||
_stunSystem.TryKnockdown(otherUid, TimeSpan.FromSeconds(component.KnockdownAmount),
|
_stunSystem.TryKnockdown(otherUid, TimeSpan.FromSeconds(component.KnockdownAmount), true,
|
||||||
status, alerts);
|
status, alerts);
|
||||||
|
|
||||||
_stunSystem.TrySlowdown(otherUid, TimeSpan.FromSeconds(component.SlowdownAmount),
|
_stunSystem.TrySlowdown(otherUid, TimeSpan.FromSeconds(component.SlowdownAmount), true,
|
||||||
component.WalkSpeedMultiplier, component.RunSpeedMultiplier, status, alerts);
|
component.WalkSpeedMultiplier, component.RunSpeedMultiplier, status, alerts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ namespace Content.Server.Stunnable
|
|||||||
if (args.Handled || !_random.Prob(args.PushProbability))
|
if (args.Handled || !_random.Prob(args.PushProbability))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!TryParalyze(uid, TimeSpan.FromSeconds(4f), status))
|
if (!TryParalyze(uid, TimeSpan.FromSeconds(4f), true, status))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var source = args.Source;
|
var source = args.Source;
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ namespace Content.Server.Stunnable
|
|||||||
|
|
||||||
private void StunEntity(EntityUid entity, StunbatonComponent comp)
|
private void StunEntity(EntityUid entity, StunbatonComponent comp)
|
||||||
{
|
{
|
||||||
if (!IoCManager.Resolve<IEntityManager>().TryGetComponent(entity, out StatusEffectsComponent? status) || !comp.Activated) return;
|
if (!EntityManager.TryGetComponent(entity, out StatusEffectsComponent? status) || !comp.Activated) return;
|
||||||
|
|
||||||
// TODO: Make slowdown inflicted customizable.
|
// TODO: Make slowdown inflicted customizable.
|
||||||
|
|
||||||
@@ -128,23 +128,23 @@ namespace Content.Server.Stunnable
|
|||||||
if (!EntityManager.HasComponent<SlowedDownComponent>(entity))
|
if (!EntityManager.HasComponent<SlowedDownComponent>(entity))
|
||||||
{
|
{
|
||||||
if (_robustRandom.Prob(comp.ParalyzeChanceNoSlowdown))
|
if (_robustRandom.Prob(comp.ParalyzeChanceNoSlowdown))
|
||||||
_stunSystem.TryParalyze(entity, TimeSpan.FromSeconds(comp.ParalyzeTime), status);
|
_stunSystem.TryParalyze(entity, TimeSpan.FromSeconds(comp.ParalyzeTime), true, status);
|
||||||
else
|
else
|
||||||
_stunSystem.TrySlowdown(entity, TimeSpan.FromSeconds(comp.SlowdownTime), 0.5f, 0.5f, status);
|
_stunSystem.TrySlowdown(entity, TimeSpan.FromSeconds(comp.SlowdownTime), true, 0.5f, 0.5f, status);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (_robustRandom.Prob(comp.ParalyzeChanceWithSlowdown))
|
if (_robustRandom.Prob(comp.ParalyzeChanceWithSlowdown))
|
||||||
_stunSystem.TryParalyze(entity, TimeSpan.FromSeconds(comp.ParalyzeTime), status);
|
_stunSystem.TryParalyze(entity, TimeSpan.FromSeconds(comp.ParalyzeTime), true, status);
|
||||||
else
|
else
|
||||||
_stunSystem.TrySlowdown(entity, TimeSpan.FromSeconds(comp.SlowdownTime), 0.5f, 0.5f, status);
|
_stunSystem.TrySlowdown(entity, TimeSpan.FromSeconds(comp.SlowdownTime), true, 0.5f, 0.5f, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
var slowdownTime = TimeSpan.FromSeconds(comp.SlowdownTime);
|
var slowdownTime = TimeSpan.FromSeconds(comp.SlowdownTime);
|
||||||
_jitterSystem.DoJitter(entity, slowdownTime, status:status);
|
_jitterSystem.DoJitter(entity, slowdownTime, true, status:status);
|
||||||
_stutteringSystem.DoStutter(entity, slowdownTime, status);
|
_stutteringSystem.DoStutter(entity, slowdownTime, true, status);
|
||||||
|
|
||||||
if (!IoCManager.Resolve<IEntityManager>().TryGetComponent<PowerCellSlotComponent?>(comp.Owner, out var slot) || slot.Cell == null || !(slot.Cell.CurrentCharge < comp.EnergyPerUse))
|
if (!EntityManager.TryGetComponent<PowerCellSlotComponent?>(comp.Owner, out var slot) || slot.Cell == null || !(slot.Cell.CurrentCharge < comp.EnergyPerUse))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
SoundSystem.Play(Filter.Pvs(comp.Owner), comp.SparksSound.GetSound(), comp.Owner, AudioHelpers.WithVariation(0.25f));
|
SoundSystem.Play(Filter.Pvs(comp.Owner), comp.SparksSound.GetSound(), comp.Owner, AudioHelpers.WithVariation(0.25f));
|
||||||
@@ -158,8 +158,8 @@ namespace Content.Server.Stunnable
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!IoCManager.Resolve<IEntityManager>().TryGetComponent<SpriteComponent?>(comp.Owner, out var sprite) ||
|
if (!EntityManager.TryGetComponent<SpriteComponent?>(comp.Owner, out var sprite) ||
|
||||||
!IoCManager.Resolve<IEntityManager>().TryGetComponent<ItemComponent?>(comp.Owner, out var item)) return;
|
!EntityManager.TryGetComponent<ItemComponent?>(comp.Owner, out var item)) return;
|
||||||
|
|
||||||
SoundSystem.Play(Filter.Pvs(comp.Owner), comp.SparksSound.GetSound(), comp.Owner, AudioHelpers.WithVariation(0.25f));
|
SoundSystem.Play(Filter.Pvs(comp.Owner), comp.SparksSound.GetSound(), comp.Owner, AudioHelpers.WithVariation(0.25f));
|
||||||
item.EquippedPrefix = "off";
|
item.EquippedPrefix = "off";
|
||||||
|
|||||||
@@ -30,8 +30,8 @@ namespace Content.Server.Throwing
|
|||||||
/// <param name="direction">A vector pointing from the entity to its destination.</param>
|
/// <param name="direction">A vector pointing from the entity to its destination.</param>
|
||||||
/// <param name="strength">How much the direction vector should be multiplied for velocity.</param>
|
/// <param name="strength">How much the direction vector should be multiplied for velocity.</param>
|
||||||
/// <param name="user"></param>
|
/// <param name="user"></param>
|
||||||
/// <param name="pushbackRatio">The ratio of impulse applied to the thrower</param>
|
/// <param name="pushbackRatio">The ratio of impulse applied to the thrower - defaults to 10 because otherwise it's not enough to properly recover from getting spaced</param>
|
||||||
internal static void TryThrow(this EntityUid entity, Vector2 direction, float strength = 1.0f, EntityUid? user = null, float pushbackRatio = 1.0f)
|
internal static void TryThrow(this EntityUid entity, Vector2 direction, float strength = 1.0f, EntityUid? user = null, float pushbackRatio = 10.0f)
|
||||||
{
|
{
|
||||||
var entities = IoCManager.Resolve<IEntityManager>();
|
var entities = IoCManager.Resolve<IEntityManager>();
|
||||||
if (entities.GetComponent<MetaDataComponent>(entity).EntityDeleted ||
|
if (entities.GetComponent<MetaDataComponent>(entity).EntityDeleted ||
|
||||||
|
|||||||
@@ -3,9 +3,11 @@ using Content.Server.Weapon.Ranged.Barrels.Components;
|
|||||||
using Content.Shared.ActionBlocker;
|
using Content.Shared.ActionBlocker;
|
||||||
using Content.Shared.Popups;
|
using Content.Shared.Popups;
|
||||||
using Content.Shared.Verbs;
|
using Content.Shared.Verbs;
|
||||||
|
using Robust.Shared.Containers;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
using Robust.Shared.Localization;
|
using Robust.Shared.Localization;
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace Content.Server.Weapon.Ranged.Barrels
|
namespace Content.Server.Weapon.Ranged.Barrels
|
||||||
{
|
{
|
||||||
@@ -19,8 +21,8 @@ namespace Content.Server.Weapon.Ranged.Barrels
|
|||||||
|
|
||||||
SubscribeLocalEvent<RevolverBarrelComponent, GetAlternativeVerbsEvent>(AddSpinVerb);
|
SubscribeLocalEvent<RevolverBarrelComponent, GetAlternativeVerbsEvent>(AddSpinVerb);
|
||||||
|
|
||||||
SubscribeLocalEvent<ServerBatteryBarrelComponent, GetAlternativeVerbsEvent>(AddEjectCellVerb);
|
SubscribeLocalEvent<ServerBatteryBarrelComponent, EntInsertedIntoContainerMessage>(OnCellSlotUpdated);
|
||||||
SubscribeLocalEvent<ServerBatteryBarrelComponent, GetInteractionVerbsEvent>(AddInsertCellVerb);
|
SubscribeLocalEvent<ServerBatteryBarrelComponent, EntRemovedFromContainerMessage>(OnCellSlotUpdated);
|
||||||
|
|
||||||
SubscribeLocalEvent<BoltActionBarrelComponent, GetInteractionVerbsEvent>(AddToggleBoltVerb);
|
SubscribeLocalEvent<BoltActionBarrelComponent, GetInteractionVerbsEvent>(AddToggleBoltVerb);
|
||||||
|
|
||||||
@@ -28,6 +30,12 @@ namespace Content.Server.Weapon.Ranged.Barrels
|
|||||||
SubscribeLocalEvent<ServerMagazineBarrelComponent, GetAlternativeVerbsEvent>(AddEjectMagazineVerb);
|
SubscribeLocalEvent<ServerMagazineBarrelComponent, GetAlternativeVerbsEvent>(AddEjectMagazineVerb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnCellSlotUpdated(EntityUid uid, ServerBatteryBarrelComponent component, ContainerModifiedMessage args)
|
||||||
|
{
|
||||||
|
if (args.Container.ID == component.CellSlot.ID)
|
||||||
|
component.UpdateAppearance();
|
||||||
|
}
|
||||||
|
|
||||||
private void AddSpinVerb(EntityUid uid, RevolverBarrelComponent component, GetAlternativeVerbsEvent args)
|
private void AddSpinVerb(EntityUid uid, RevolverBarrelComponent component, GetAlternativeVerbsEvent args)
|
||||||
{
|
{
|
||||||
if (args.Hands == null || !args.CanAccess || !args.CanInteract)
|
if (args.Hands == null || !args.CanAccess || !args.CanInteract)
|
||||||
@@ -62,44 +70,6 @@ namespace Content.Server.Weapon.Ranged.Barrels
|
|||||||
args.Verbs.Add(verb);
|
args.Verbs.Add(verb);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO VERBS EJECTABLES Standardize eject/insert verbs into a single system?
|
|
||||||
// Really, why isn't this just PowerCellSlotComponent?
|
|
||||||
private void AddEjectCellVerb(EntityUid uid, ServerBatteryBarrelComponent component, GetAlternativeVerbsEvent args)
|
|
||||||
{
|
|
||||||
if (args.Hands == null ||
|
|
||||||
!args.CanAccess ||
|
|
||||||
!args.CanInteract ||
|
|
||||||
!component.PowerCellRemovable ||
|
|
||||||
component.PowerCell == null ||
|
|
||||||
!_actionBlockerSystem.CanPickup(args.User))
|
|
||||||
return;
|
|
||||||
|
|
||||||
Verb verb = new()
|
|
||||||
{
|
|
||||||
Text = EntityManager.GetComponent<MetaDataComponent>(component.PowerCell.Owner).EntityName,
|
|
||||||
Category = VerbCategory.Eject,
|
|
||||||
Act = () => component.TryEjectCell(args.User)
|
|
||||||
};
|
|
||||||
args.Verbs.Add(verb);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AddInsertCellVerb(EntityUid uid, ServerBatteryBarrelComponent component, GetInteractionVerbsEvent args)
|
|
||||||
{
|
|
||||||
if (args.Using is not {Valid: true} @using ||
|
|
||||||
!args.CanAccess ||
|
|
||||||
!args.CanInteract ||
|
|
||||||
component.PowerCell != null ||
|
|
||||||
!EntityManager.HasComponent<BatteryComponent>(@using) ||
|
|
||||||
!_actionBlockerSystem.CanDrop(args.User))
|
|
||||||
return;
|
|
||||||
|
|
||||||
Verb verb = new();
|
|
||||||
verb.Text = EntityManager.GetComponent<MetaDataComponent>(@using).EntityName;
|
|
||||||
verb.Category = VerbCategory.Insert;
|
|
||||||
verb.Act = () => component.TryInsertPowerCell(@using);
|
|
||||||
args.Verbs.Add(verb);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AddEjectMagazineVerb(EntityUid uid, ServerMagazineBarrelComponent component, GetAlternativeVerbsEvent args)
|
private void AddEjectMagazineVerb(EntityUid uid, ServerMagazineBarrelComponent component, GetAlternativeVerbsEvent args)
|
||||||
{
|
{
|
||||||
if (args.Hands == null ||
|
if (args.Hands == null ||
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components
|
|||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
[NetworkedComponent()]
|
[NetworkedComponent()]
|
||||||
#pragma warning disable 618
|
#pragma warning disable 618
|
||||||
public sealed class BoltActionBarrelComponent : ServerRangedBarrelComponent, IMapInit, IExamine
|
public sealed class BoltActionBarrelComponent : ServerRangedBarrelComponent, IUse, IInteractUsing, IMapInit, IExamine
|
||||||
#pragma warning restore 618
|
#pragma warning restore 618
|
||||||
{
|
{
|
||||||
// Originally I had this logic shared with PumpBarrel and used a couple of variables to control things
|
// Originally I had this logic shared with PumpBarrel and used a couple of variables to control things
|
||||||
@@ -270,7 +270,7 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool UseEntity(UseEntityEventArgs eventArgs)
|
public bool UseEntity(UseEntityEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
if (BoltOpen)
|
if (BoltOpen)
|
||||||
{
|
{
|
||||||
@@ -284,7 +284,7 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<bool> InteractUsing(InteractUsingEventArgs eventArgs)
|
public async Task<bool> InteractUsing(InteractUsingEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
return TryInsertBullet(eventArgs.User, eventArgs.Using);
|
return TryInsertBullet(eventArgs.User, eventArgs.Using);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
[NetworkedComponent()]
|
[NetworkedComponent()]
|
||||||
public sealed class PumpBarrelComponent : ServerRangedBarrelComponent, IMapInit, ISerializationHooks
|
public sealed class PumpBarrelComponent : ServerRangedBarrelComponent, IUse, IInteractUsing, IMapInit, ISerializationHooks
|
||||||
{
|
{
|
||||||
public override string Name => "PumpBarrel";
|
public override string Name => "PumpBarrel";
|
||||||
|
|
||||||
@@ -224,13 +224,13 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool UseEntity(UseEntityEventArgs eventArgs)
|
public bool UseEntity(UseEntityEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
Cycle(true);
|
Cycle(true);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<bool> InteractUsing(InteractUsingEventArgs eventArgs)
|
public async Task<bool> InteractUsing(InteractUsingEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
return TryInsertBullet(eventArgs);
|
return TryInsertBullet(eventArgs);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components
|
|||||||
{
|
{
|
||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
[NetworkedComponent()]
|
[NetworkedComponent()]
|
||||||
public sealed class RevolverBarrelComponent : ServerRangedBarrelComponent, ISerializationHooks
|
public sealed class RevolverBarrelComponent : ServerRangedBarrelComponent, IUse, IInteractUsing, ISerializationHooks
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IRobustRandom _random = default!;
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
|
|
||||||
@@ -251,7 +251,7 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components
|
|||||||
/// <param name="eventArgs"></param>
|
/// <param name="eventArgs"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
/// <exception cref="NotImplementedException"></exception>
|
/// <exception cref="NotImplementedException"></exception>
|
||||||
public override bool UseEntity(UseEntityEventArgs eventArgs)
|
public bool UseEntity(UseEntityEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
EjectAllSlots();
|
EjectAllSlots();
|
||||||
Dirty();
|
Dirty();
|
||||||
@@ -259,7 +259,7 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<bool> InteractUsing(InteractUsingEventArgs eventArgs)
|
public async Task<bool> InteractUsing(InteractUsingEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
return TryInsertBullet(eventArgs.User, eventArgs.Using);
|
return TryInsertBullet(eventArgs.User, eventArgs.Using);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,15 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Content.Server.Hands.Components;
|
|
||||||
using Content.Server.Items;
|
|
||||||
using Content.Server.Power.Components;
|
using Content.Server.Power.Components;
|
||||||
using Content.Server.Projectiles.Components;
|
using Content.Server.Projectiles.Components;
|
||||||
using Content.Shared.Interaction;
|
using Content.Shared.Containers.ItemSlots;
|
||||||
using Content.Shared.Sound;
|
|
||||||
using Content.Shared.Weapons.Ranged.Barrels.Components;
|
using Content.Shared.Weapons.Ranged.Barrels.Components;
|
||||||
using Robust.Shared.Audio;
|
|
||||||
using Robust.Shared.Containers;
|
using Robust.Shared.Containers;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.GameStates;
|
using Robust.Shared.GameStates;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Player;
|
using Robust.Shared.Player;
|
||||||
|
using Robust.Shared.Players;
|
||||||
using Robust.Shared.Serialization.Manager.Attributes;
|
using Robust.Shared.Serialization.Manager.Attributes;
|
||||||
using Robust.Shared.ViewVariables;
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
@@ -27,6 +23,9 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components
|
|||||||
|
|
||||||
public override string Name => "BatteryBarrel";
|
public override string Name => "BatteryBarrel";
|
||||||
|
|
||||||
|
[DataField("cellSlot", required: true)]
|
||||||
|
public ItemSlot CellSlot = new();
|
||||||
|
|
||||||
// The minimum change we need before we can fire
|
// The minimum change we need before we can fire
|
||||||
[DataField("lowerChargeLimit")]
|
[DataField("lowerChargeLimit")]
|
||||||
[ViewVariables] private float _lowerChargeLimit = 10;
|
[ViewVariables] private float _lowerChargeLimit = 10;
|
||||||
@@ -36,23 +35,14 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components
|
|||||||
[DataField("ammoPrototype")]
|
[DataField("ammoPrototype")]
|
||||||
[ViewVariables] private string? _ammoPrototype;
|
[ViewVariables] private string? _ammoPrototype;
|
||||||
|
|
||||||
[ViewVariables] public EntityUid? PowerCellEntity => _powerCellContainer.ContainedEntity;
|
public BatteryComponent? PowerCell => _entities.GetComponentOrNull<BatteryComponent>(CellSlot.Item);
|
||||||
public BatteryComponent? PowerCell => _powerCellContainer.ContainedEntity == null
|
|
||||||
? null
|
|
||||||
: _entities.GetComponentOrNull<BatteryComponent>(_powerCellContainer.ContainedEntity.Value);
|
|
||||||
|
|
||||||
private ContainerSlot _powerCellContainer = default!;
|
|
||||||
private ContainerSlot _ammoContainer = default!;
|
private ContainerSlot _ammoContainer = default!;
|
||||||
[DataField("powerCellPrototype")]
|
|
||||||
private string? _powerCellPrototype;
|
|
||||||
[DataField("powerCellRemovable")]
|
|
||||||
[ViewVariables] public bool PowerCellRemovable;
|
|
||||||
|
|
||||||
public override int ShotsLeft
|
public override int ShotsLeft
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (_powerCellContainer.ContainedEntity is not {Valid: true} powerCell)
|
if (CellSlot.Item is not {} powerCell)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -65,7 +55,7 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (_powerCellContainer.ContainedEntity is not {Valid: true} powerCell)
|
if (CellSlot.Item is not {} powerCell)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -76,12 +66,6 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components
|
|||||||
|
|
||||||
private AppearanceComponent? _appearanceComponent;
|
private AppearanceComponent? _appearanceComponent;
|
||||||
|
|
||||||
// Sounds
|
|
||||||
[DataField("soundPowerCellInsert", required: true)]
|
|
||||||
private SoundSpecifier _soundPowerCellInsert = default!;
|
|
||||||
[DataField("soundPowerCellEject", required: true)]
|
|
||||||
private SoundSpecifier _soundPowerCellEject = default!;
|
|
||||||
|
|
||||||
public override ComponentState GetComponentState()
|
public override ComponentState GetComponentState()
|
||||||
{
|
{
|
||||||
(int, int)? count = (ShotsLeft, Capacity);
|
(int, int)? count = (ShotsLeft, Capacity);
|
||||||
@@ -94,12 +78,8 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components
|
|||||||
protected override void Initialize()
|
protected override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
_powerCellContainer = Owner.EnsureContainer<ContainerSlot>($"{Name}-powercell-container", out var existing);
|
|
||||||
if (!existing && _powerCellPrototype != null)
|
EntitySystem.Get<ItemSlotsSystem>().AddItemSlot(Owner, $"{Name}-powercell-container", CellSlot);
|
||||||
{
|
|
||||||
var powerCellEntity = _entities.SpawnEntity(_powerCellPrototype, _entities.GetComponent<TransformComponent>(Owner).Coordinates);
|
|
||||||
_powerCellContainer.Insert(powerCellEntity);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_ammoPrototype != null)
|
if (_ammoPrototype != null)
|
||||||
{
|
{
|
||||||
@@ -113,6 +93,12 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components
|
|||||||
Dirty();
|
Dirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnRemove()
|
||||||
|
{
|
||||||
|
base.OnRemove();
|
||||||
|
EntitySystem.Get<ItemSlotsSystem>().RemoveItemSlot(Owner, CellSlot);
|
||||||
|
}
|
||||||
|
|
||||||
protected override void Startup()
|
protected override void Startup()
|
||||||
{
|
{
|
||||||
base.Startup();
|
base.Startup();
|
||||||
@@ -121,7 +107,7 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components
|
|||||||
|
|
||||||
public void UpdateAppearance()
|
public void UpdateAppearance()
|
||||||
{
|
{
|
||||||
_appearanceComponent?.SetData(MagazineBarrelVisuals.MagLoaded, _powerCellContainer.ContainedEntity != null);
|
_appearanceComponent?.SetData(MagazineBarrelVisuals.MagLoaded, CellSlot.HasItem);
|
||||||
_appearanceComponent?.SetData(AmmoVisuals.AmmoCount, ShotsLeft);
|
_appearanceComponent?.SetData(AmmoVisuals.AmmoCount, ShotsLeft);
|
||||||
_appearanceComponent?.SetData(AmmoVisuals.AmmoMax, Capacity);
|
_appearanceComponent?.SetData(AmmoVisuals.AmmoMax, Capacity);
|
||||||
Dirty();
|
Dirty();
|
||||||
@@ -143,7 +129,7 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components
|
|||||||
|
|
||||||
public override EntityUid? TakeProjectile(EntityCoordinates spawnAt)
|
public override EntityUid? TakeProjectile(EntityCoordinates spawnAt)
|
||||||
{
|
{
|
||||||
var powerCellEntity = _powerCellContainer.ContainedEntity;
|
var powerCellEntity = CellSlot.Item;
|
||||||
|
|
||||||
if (powerCellEntity == null)
|
if (powerCellEntity == null)
|
||||||
{
|
{
|
||||||
@@ -198,76 +184,5 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components
|
|||||||
UpdateAppearance();
|
UpdateAppearance();
|
||||||
return entity.Value;
|
return entity.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryInsertPowerCell(EntityUid entity)
|
|
||||||
{
|
|
||||||
if (_powerCellContainer.ContainedEntity != null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_entities.HasComponent<BatteryComponent>(entity))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
SoundSystem.Play(Filter.Pvs(Owner), _soundPowerCellInsert.GetSound(), Owner, AudioParams.Default.WithVolume(-2));
|
|
||||||
|
|
||||||
_powerCellContainer.Insert(entity);
|
|
||||||
|
|
||||||
Dirty();
|
|
||||||
UpdateAppearance();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool UseEntity(UseEntityEventArgs eventArgs)
|
|
||||||
{
|
|
||||||
if (!PowerCellRemovable)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return PowerCellEntity != default && TryEjectCell(eventArgs.User);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TryEjectCell(EntityUid user)
|
|
||||||
{
|
|
||||||
if (PowerCell == null || !PowerCellRemovable)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_entities.TryGetComponent(user, out HandsComponent? hands))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var cell = PowerCell;
|
|
||||||
if (!_powerCellContainer.Remove(cell.Owner))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Dirty();
|
|
||||||
UpdateAppearance();
|
|
||||||
|
|
||||||
if (!hands.PutInHand(_entities.GetComponent<ItemComponent>(cell.Owner)))
|
|
||||||
{
|
|
||||||
_entities.GetComponent<TransformComponent>(cell.Owner).Coordinates = _entities.GetComponent<TransformComponent>(user).Coordinates;
|
|
||||||
}
|
|
||||||
|
|
||||||
SoundSystem.Play(Filter.Pvs(Owner), _soundPowerCellEject.GetSound(), Owner, AudioParams.Default.WithVolume(-2));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<bool> InteractUsing(InteractUsingEventArgs eventArgs)
|
|
||||||
{
|
|
||||||
if (!_entities.HasComponent<BatteryComponent>(eventArgs.Using))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return TryInsertPowerCell(eventArgs.Using);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components
|
|||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
[NetworkedComponent()]
|
[NetworkedComponent()]
|
||||||
#pragma warning disable 618
|
#pragma warning disable 618
|
||||||
public sealed class ServerMagazineBarrelComponent : ServerRangedBarrelComponent, IExamine
|
public sealed class ServerMagazineBarrelComponent : ServerRangedBarrelComponent, IUse, IInteractUsing, IExamine
|
||||||
#pragma warning restore 618
|
#pragma warning restore 618
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IEntityManager _entities = default!;
|
[Dependency] private readonly IEntityManager _entities = default!;
|
||||||
@@ -248,7 +248,7 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components
|
|||||||
_appearanceComponent?.SetData(AmmoVisuals.AmmoMax, Capacity);
|
_appearanceComponent?.SetData(AmmoVisuals.AmmoMax, Capacity);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool UseEntity(UseEntityEventArgs eventArgs)
|
public bool UseEntity(UseEntityEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
// Behavior:
|
// Behavior:
|
||||||
// If bolt open just close it
|
// If bolt open just close it
|
||||||
@@ -391,7 +391,7 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components
|
|||||||
UpdateAppearance();
|
UpdateAppearance();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<bool> InteractUsing(InteractUsingEventArgs eventArgs)
|
public async Task<bool> InteractUsing(InteractUsingEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
if (CanInsertMagazine(eventArgs.User, eventArgs.Using, quiet: false))
|
if (CanInsertMagazine(eventArgs.User, eventArgs.Using, quiet: false))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components
|
|||||||
/// Only difference between them is how they retrieve a projectile to shoot (battery, magazine, etc.)
|
/// Only difference between them is how they retrieve a projectile to shoot (battery, magazine, etc.)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
#pragma warning disable 618
|
#pragma warning disable 618
|
||||||
public abstract class ServerRangedBarrelComponent : SharedRangedBarrelComponent, IUse, IInteractUsing, IExamine, ISerializationHooks
|
public abstract class ServerRangedBarrelComponent : SharedRangedBarrelComponent, IExamine, ISerializationHooks
|
||||||
#pragma warning restore 618
|
#pragma warning restore 618
|
||||||
{
|
{
|
||||||
// There's still some of py01 and PJB's work left over, especially in underlying shooting logic,
|
// There's still some of py01 and PJB's work left over, especially in underlying shooting logic,
|
||||||
@@ -133,9 +133,9 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnAdd()
|
protected override void Initialize()
|
||||||
{
|
{
|
||||||
base.OnAdd();
|
base.Initialize();
|
||||||
|
|
||||||
Owner.EnsureComponentWarn(out ServerRangedWeaponComponent rangedWeaponComponent);
|
Owner.EnsureComponentWarn(out ServerRangedWeaponComponent rangedWeaponComponent);
|
||||||
|
|
||||||
@@ -167,10 +167,6 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components
|
|||||||
return angle;
|
return angle;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract bool UseEntity(UseEntityEventArgs eventArgs);
|
|
||||||
|
|
||||||
public abstract Task<bool> InteractUsing(InteractUsingEventArgs eventArgs);
|
|
||||||
|
|
||||||
public void ChangeFireSelector(FireRateSelector rateSelector)
|
public void ChangeFireSelector(FireRateSelector rateSelector)
|
||||||
{
|
{
|
||||||
if ((rateSelector & AllRateSelectors) != 0)
|
if ((rateSelector & AllRateSelectors) != 0)
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ namespace Content.Server.Weapon.Ranged
|
|||||||
{
|
{
|
||||||
//Wound them
|
//Wound them
|
||||||
EntitySystem.Get<DamageableSystem>().TryChangeDamage(user, ClumsyDamage);
|
EntitySystem.Get<DamageableSystem>().TryChangeDamage(user, ClumsyDamage);
|
||||||
EntitySystem.Get<StunSystem>().TryParalyze(user, TimeSpan.FromSeconds(3f));
|
EntitySystem.Get<StunSystem>().TryParalyze(user, TimeSpan.FromSeconds(3f), true);
|
||||||
|
|
||||||
// Apply salt to the wound ("Honk!")
|
// Apply salt to the wound ("Honk!")
|
||||||
SoundSystem.Play(
|
SoundSystem.Play(
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Content.Shared.Database;
|
namespace Content.Shared.Database;
|
||||||
|
|
||||||
// DO NOT CHANGE THE NUMERIC VALUES OF THESE
|
// DO NOT CHANGE THE NUMERIC VALUES OF THESE
|
||||||
public enum LogType
|
public enum LogType
|
||||||
@@ -40,12 +40,13 @@ public enum LogType
|
|||||||
Pickup = 36,
|
Pickup = 36,
|
||||||
Drop = 37,
|
Drop = 37,
|
||||||
BulletHit = 38,
|
BulletHit = 38,
|
||||||
ForceFeed = 40,
|
ForceFeed = 40, // involuntary
|
||||||
|
Ingestion = 53, // voluntary
|
||||||
MeleeHit = 41,
|
MeleeHit = 41,
|
||||||
HitScanHit = 42,
|
HitScanHit = 42,
|
||||||
Suicide = 43,
|
Suicide = 43,
|
||||||
Explosion = 44,
|
Explosion = 44,
|
||||||
Radiation = 45,
|
Radiation = 45, // Unused
|
||||||
Barotrauma = 46,
|
Barotrauma = 46,
|
||||||
Flammable = 47,
|
Flammable = 47,
|
||||||
Asphyxiation = 48,
|
Asphyxiation = 48,
|
||||||
|
|||||||
@@ -300,12 +300,13 @@ namespace Content.Shared.CCVar
|
|||||||
CVarDef.Create("hud.fps_counter_visible", false, CVar.CLIENTONLY | CVar.ARCHIVE);
|
CVarDef.Create("hud.fps_counter_visible", false, CVar.CLIENTONLY | CVar.ARCHIVE);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* AI
|
* NPCs
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public static readonly CVarDef<int> AIMaxUpdates =
|
public static readonly CVarDef<int> NPCMaxUpdates =
|
||||||
CVarDef.Create("ai.maxupdates", 64);
|
CVarDef.Create("npc.max_updates", 64);
|
||||||
|
|
||||||
|
public static readonly CVarDef<bool> NPCEnabled = CVarDef.Create("npc.enabled", true);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Net
|
* Net
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using Content.Shared.Sound;
|
using Content.Shared.Sound;
|
||||||
using Content.Shared.Whitelist;
|
using Content.Shared.Whitelist;
|
||||||
using Robust.Shared.Analyzers;
|
using Robust.Shared.Analyzers;
|
||||||
|
using Robust.Shared.Audio;
|
||||||
using Robust.Shared.Containers;
|
using Robust.Shared.Containers;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
@@ -81,6 +82,12 @@ namespace Content.Shared.Containers.ItemSlots
|
|||||||
public SoundSpecifier? EjectSound;
|
public SoundSpecifier? EjectSound;
|
||||||
// maybe default to /Audio/Machines/id_swipe.ogg?
|
// maybe default to /Audio/Machines/id_swipe.ogg?
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Options used for playing the insert/eject sounds.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("soundOptions")]
|
||||||
|
public AudioParams SoundOptions = AudioParams.Default;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The name of this item slot. This will be shown to the user in the verb menu.
|
/// The name of this item slot. This will be shown to the user in the verb menu.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -116,6 +123,18 @@ namespace Content.Shared.Containers.ItemSlots
|
|||||||
[DataField("ejectOnInteract")]
|
[DataField("ejectOnInteract")]
|
||||||
public bool EjectOnInteract = false;
|
public bool EjectOnInteract = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If true, and if this slot is attached to an item, then it will attempt to eject slot when to the slot is
|
||||||
|
/// used in the user's hands.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Desirable for things like ranged weapons ('Z' to eject), but not desirable for others (e.g., PDA uses
|
||||||
|
/// 'Z' to open UI). Unlike <see cref="EjectOnInteract"/>, this will not make any changes to the context
|
||||||
|
/// menu, nor will it disable alt-click interactions.
|
||||||
|
/// </remarks>
|
||||||
|
[DataField("ejectOnUse")]
|
||||||
|
public bool EjectOnUse = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Override the insert verb text. Defaults to [insert category] -> [item-name]. If not null, the verb will
|
/// Override the insert verb text. Defaults to [insert category] -> [item-name]. If not null, the verb will
|
||||||
/// not be given a category.
|
/// not be given a category.
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ namespace Content.Shared.Containers.ItemSlots
|
|||||||
|
|
||||||
SubscribeLocalEvent<ItemSlotsComponent, InteractUsingEvent>(OnInteractUsing);
|
SubscribeLocalEvent<ItemSlotsComponent, InteractUsingEvent>(OnInteractUsing);
|
||||||
SubscribeLocalEvent<ItemSlotsComponent, InteractHandEvent>(OnInteractHand);
|
SubscribeLocalEvent<ItemSlotsComponent, InteractHandEvent>(OnInteractHand);
|
||||||
|
SubscribeLocalEvent<ItemSlotsComponent, UseInHandEvent>(OnUseInHand);
|
||||||
|
|
||||||
SubscribeLocalEvent<ItemSlotsComponent, GetAlternativeVerbsEvent>(AddEjectVerbs);
|
SubscribeLocalEvent<ItemSlotsComponent, GetAlternativeVerbsEvent>(AddEjectVerbs);
|
||||||
SubscribeLocalEvent<ItemSlotsComponent, GetInteractionVerbsEvent>(AddInteractionVerbsVerbs);
|
SubscribeLocalEvent<ItemSlotsComponent, GetInteractionVerbsEvent>(AddInteractionVerbsVerbs);
|
||||||
@@ -125,6 +126,25 @@ namespace Content.Shared.Containers.ItemSlots
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempt to eject an item from the first valid item slot.
|
||||||
|
/// </summary>
|
||||||
|
private void OnUseInHand(EntityUid uid, ItemSlotsComponent itemSlots, UseInHandEvent args)
|
||||||
|
{
|
||||||
|
if (args.Handled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var slot in itemSlots.Slots.Values)
|
||||||
|
{
|
||||||
|
if (slot.Locked || !slot.EjectOnUse || slot.Item == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
args.Handled = true;
|
||||||
|
TryEjectToHands(uid, slot, args.User);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tries to insert a held item in any fitting item slot. If a valid slot already contains an item, it will
|
/// Tries to insert a held item in any fitting item slot. If a valid slot already contains an item, it will
|
||||||
/// swap it out and place the old one in the user's hand.
|
/// swap it out and place the old one in the user's hand.
|
||||||
@@ -172,7 +192,7 @@ namespace Content.Shared.Containers.ItemSlots
|
|||||||
// ContainerSlot automatically raises a directed EntInsertedIntoContainerMessage
|
// ContainerSlot automatically raises a directed EntInsertedIntoContainerMessage
|
||||||
|
|
||||||
if (slot.InsertSound != null)
|
if (slot.InsertSound != null)
|
||||||
SoundSystem.Play(Filter.Pvs(uid), slot.InsertSound.GetSound(), uid);
|
SoundSystem.Play(Filter.Pvs(uid), slot.InsertSound.GetSound(), uid, slot.SoundOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -267,7 +287,7 @@ namespace Content.Shared.Containers.ItemSlots
|
|||||||
// ContainerSlot automatically raises a directed EntRemovedFromContainerMessage
|
// ContainerSlot automatically raises a directed EntRemovedFromContainerMessage
|
||||||
|
|
||||||
if (slot.EjectSound != null)
|
if (slot.EjectSound != null)
|
||||||
SoundSystem.Play(Filter.Pvs(uid), slot.EjectSound.GetSound(), uid);
|
SoundSystem.Play(Filter.Pvs(uid), slot.EjectSound.GetSound(), uid, slot.SoundOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -317,7 +337,7 @@ namespace Content.Shared.Containers.ItemSlots
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (user != null && EntityManager.TryGetComponent(user.Value, out SharedHandsComponent? hands))
|
if (user != null && EntityManager.TryGetComponent(user.Value, out SharedHandsComponent? hands))
|
||||||
hands.TryPutInAnyHand(item.Value);
|
hands.TryPutInActiveHandOrAny(item.Value);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,16 +72,4 @@ namespace Content.Shared.Crayon
|
|||||||
Color = color;
|
Color = color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Serializable, NetSerializable, Prototype("crayonDecal")]
|
|
||||||
public class CrayonDecalPrototype : IPrototype
|
|
||||||
{
|
|
||||||
[ViewVariables]
|
|
||||||
[DataField("id", required: true)]
|
|
||||||
public string ID { get; } = default!;
|
|
||||||
|
|
||||||
[DataField("spritePath")] public string SpritePath { get; } = string.Empty;
|
|
||||||
|
|
||||||
[DataField("decals")] public List<string> Decals { get; } = new();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Content.Shared.Acts;
|
using Content.Shared.Acts;
|
||||||
using Content.Shared.Damage.Prototypes;
|
using Content.Shared.Damage.Prototypes;
|
||||||
using Content.Shared.Damage;
|
|
||||||
using Content.Shared.FixedPoint;
|
using Content.Shared.FixedPoint;
|
||||||
using Content.Shared.Radiation;
|
using Content.Shared.Radiation;
|
||||||
using Robust.Shared.Analyzers;
|
using Robust.Shared.Analyzers;
|
||||||
@@ -13,8 +12,6 @@ using Robust.Shared.Serialization.Manager.Attributes;
|
|||||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||||
using Robust.Shared.ViewVariables;
|
using Robust.Shared.ViewVariables;
|
||||||
using Content.Shared.Administration.Logs;
|
|
||||||
using Content.Shared.Database;
|
|
||||||
|
|
||||||
namespace Content.Shared.Damage
|
namespace Content.Shared.Damage
|
||||||
{
|
{
|
||||||
@@ -95,11 +92,7 @@ namespace Content.Shared.Damage
|
|||||||
damage.DamageDict.Add(typeID, damageValue);
|
damage.DamageDict.Add(typeID, damageValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
var actual = EntitySystem.Get<DamageableSystem>().TryChangeDamage(Owner, damage);
|
EntitySystem.Get<DamageableSystem>().TryChangeDamage(Owner, damage);
|
||||||
|
|
||||||
// should logging be disabled during rad storms? a lot of entities are going to be damaged.
|
|
||||||
if (actual != null && !actual.Empty)
|
|
||||||
EntitySystem.Get<SharedAdminLogSystem>().Add(LogType.Radiation, $"{Owner} took {actual.Total} radiation damage");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO EXPLOSION Remove this.
|
// TODO EXPLOSION Remove this.
|
||||||
@@ -120,11 +113,7 @@ namespace Content.Shared.Damage
|
|||||||
damage.DamageDict.Add(typeID, damageValue);
|
damage.DamageDict.Add(typeID, damageValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
var actual = EntitySystem.Get<DamageableSystem>().TryChangeDamage(Owner, damage);
|
EntitySystem.Get<DamageableSystem>().TryChangeDamage(Owner, damage);
|
||||||
|
|
||||||
// will logging handle nukes?
|
|
||||||
if (actual != null && !actual.Empty)
|
|
||||||
EntitySystem.Get<SharedAdminLogSystem>().Add(LogType.Explosion, $"{Owner} took {actual.Total} explosion damage");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Shared.Administration.Logs;
|
|
||||||
using Content.Shared.Damage.Prototypes;
|
using Content.Shared.Damage.Prototypes;
|
||||||
using Content.Shared.Database;
|
|
||||||
using Content.Shared.FixedPoint;
|
using Content.Shared.FixedPoint;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.GameStates;
|
using Robust.Shared.GameStates;
|
||||||
@@ -15,8 +13,6 @@ namespace Content.Shared.Damage
|
|||||||
{
|
{
|
||||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
|
|
||||||
[Dependency] private readonly SharedAdminLogSystem _logs = default!;
|
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
SubscribeLocalEvent<DamageableComponent, ComponentInit>(DamageableInit);
|
SubscribeLocalEvent<DamageableComponent, ComponentInit>(DamageableInit);
|
||||||
@@ -24,45 +20,6 @@ namespace Content.Shared.Damage
|
|||||||
SubscribeLocalEvent<DamageableComponent, ComponentGetState>(DamageableGetState);
|
SubscribeLocalEvent<DamageableComponent, ComponentGetState>(DamageableGetState);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Update the total damage value and optionally add to admin logs
|
|
||||||
/// </summary>
|
|
||||||
protected virtual void SetTotalDamage(DamageableComponent damageable, FixedPoint2 @new, bool logChange)
|
|
||||||
{
|
|
||||||
var owner = damageable.Owner;
|
|
||||||
var old = damageable.TotalDamage;
|
|
||||||
|
|
||||||
if (@new == old)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
damageable.TotalDamage = @new;
|
|
||||||
|
|
||||||
if (!logChange)
|
|
||||||
return;
|
|
||||||
|
|
||||||
LogType logType;
|
|
||||||
string type;
|
|
||||||
FixedPoint2 change;
|
|
||||||
|
|
||||||
if (@new > old)
|
|
||||||
{
|
|
||||||
logType = LogType.Damaged;
|
|
||||||
type = "received";
|
|
||||||
change = @new - old;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
logType = LogType.Healed;
|
|
||||||
type = "healed";
|
|
||||||
change = old - @new;
|
|
||||||
}
|
|
||||||
|
|
||||||
_logs.Add(logType, $"{owner} {type} {change} damage. Old: {old} | New: {@new}");
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initialize a damageable component
|
/// Initialize a damageable component
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -111,7 +68,7 @@ namespace Content.Shared.Damage
|
|||||||
public void SetDamage(DamageableComponent damageable, DamageSpecifier damage)
|
public void SetDamage(DamageableComponent damageable, DamageSpecifier damage)
|
||||||
{
|
{
|
||||||
damageable.Damage = damage;
|
damageable.Damage = damage;
|
||||||
DamageChanged(damageable, false);
|
DamageChanged(damageable);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -121,11 +78,11 @@ namespace Content.Shared.Damage
|
|||||||
/// This updates cached damage information, flags the component as dirty, and raises a damage changed event.
|
/// This updates cached damage information, flags the component as dirty, and raises a damage changed event.
|
||||||
/// The damage changed event is used by other systems, such as damage thresholds.
|
/// The damage changed event is used by other systems, such as damage thresholds.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public void DamageChanged(DamageableComponent component, bool logChange, DamageSpecifier? damageDelta = null,
|
public void DamageChanged(DamageableComponent component, DamageSpecifier? damageDelta = null,
|
||||||
bool interruptsDoAfters = true)
|
bool interruptsDoAfters = true)
|
||||||
{
|
{
|
||||||
component.DamagePerGroup = component.Damage.GetDamagePerGroup();
|
component.DamagePerGroup = component.Damage.GetDamagePerGroup();
|
||||||
SetTotalDamage(component, component.Damage.Total, logChange);
|
component.TotalDamage = component.Damage.Total;
|
||||||
component.Dirty();
|
component.Dirty();
|
||||||
|
|
||||||
if (EntityManager.TryGetComponent<AppearanceComponent>(component.Owner, out var appearance) && damageDelta != null)
|
if (EntityManager.TryGetComponent<AppearanceComponent>(component.Owner, out var appearance) && damageDelta != null)
|
||||||
@@ -146,7 +103,7 @@ namespace Content.Shared.Damage
|
|||||||
/// null if the user had no applicable components that can take damage.
|
/// null if the user had no applicable components that can take damage.
|
||||||
/// </returns>
|
/// </returns>
|
||||||
public DamageSpecifier? TryChangeDamage(EntityUid? uid, DamageSpecifier damage, bool ignoreResistances = false,
|
public DamageSpecifier? TryChangeDamage(EntityUid? uid, DamageSpecifier damage, bool ignoreResistances = false,
|
||||||
bool interruptsDoAfters = true, bool logChange = false)
|
bool interruptsDoAfters = true)
|
||||||
{
|
{
|
||||||
if (!EntityManager.TryGetComponent<DamageableComponent>(uid, out var damageable))
|
if (!EntityManager.TryGetComponent<DamageableComponent>(uid, out var damageable))
|
||||||
{
|
{
|
||||||
@@ -195,7 +152,7 @@ namespace Content.Shared.Damage
|
|||||||
|
|
||||||
if (!delta.Empty)
|
if (!delta.Empty)
|
||||||
{
|
{
|
||||||
DamageChanged(damageable, logChange, delta, interruptsDoAfters);
|
DamageChanged(damageable, delta, interruptsDoAfters);
|
||||||
}
|
}
|
||||||
|
|
||||||
return delta;
|
return delta;
|
||||||
@@ -222,7 +179,7 @@ namespace Content.Shared.Damage
|
|||||||
|
|
||||||
// Setting damage does not count as 'dealing' damage, even if it is set to a larger value, so we pass an
|
// Setting damage does not count as 'dealing' damage, even if it is set to a larger value, so we pass an
|
||||||
// empty damage delta.
|
// empty damage delta.
|
||||||
DamageChanged(component, false, new DamageSpecifier());
|
DamageChanged(component, new DamageSpecifier());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DamageableGetState(EntityUid uid, DamageableComponent component, ref ComponentGetState args)
|
private void DamageableGetState(EntityUid uid, DamageableComponent component, ref ComponentGetState args)
|
||||||
@@ -247,7 +204,7 @@ namespace Content.Shared.Damage
|
|||||||
if (!delta.Empty)
|
if (!delta.Empty)
|
||||||
{
|
{
|
||||||
component.Damage = newDamage;
|
component.Damage = newDamage;
|
||||||
DamageChanged(component, false, delta);
|
DamageChanged(component, delta);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
38
Content.Shared/Decals/Decal.cs
Normal file
38
Content.Shared/Decals/Decal.cs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
using System;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
using Robust.Shared.Serialization.Manager.Attributes;
|
||||||
|
|
||||||
|
namespace Content.Shared.Decals
|
||||||
|
{
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
[DataDefinition]
|
||||||
|
public class Decal
|
||||||
|
{
|
||||||
|
[DataField("coordinates")] public readonly Vector2 Coordinates = Vector2.Zero;
|
||||||
|
[DataField("id")] public readonly string Id = string.Empty;
|
||||||
|
[DataField("color")] public readonly Color? Color;
|
||||||
|
[DataField("angle")] public readonly Angle Angle = Angle.Zero;
|
||||||
|
[DataField("zIndex")] public readonly int ZIndex;
|
||||||
|
[DataField("cleanable")] public bool Cleanable;
|
||||||
|
|
||||||
|
public Decal() {}
|
||||||
|
|
||||||
|
public Decal(Vector2 coordinates, string id, Color? color, Angle angle, int zIndex, bool cleanable)
|
||||||
|
{
|
||||||
|
Coordinates = coordinates;
|
||||||
|
Id = id;
|
||||||
|
Color = color;
|
||||||
|
Angle = angle;
|
||||||
|
ZIndex = zIndex;
|
||||||
|
Cleanable = cleanable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Decal WithCoordinates(Vector2 coordinates) => new(coordinates, Id, Color, Angle, ZIndex, Cleanable);
|
||||||
|
public Decal WithId(string id) => new(Coordinates, id, Color, Angle, ZIndex, Cleanable);
|
||||||
|
public Decal WithColor(Color? color) => new(Coordinates, Id, color, Angle, ZIndex, Cleanable);
|
||||||
|
public Decal WithRotation(Angle angle) => new(Coordinates, Id, Color, angle, ZIndex, Cleanable);
|
||||||
|
public Decal WithZIndex(int zIndex) => new(Coordinates, Id, Color, Angle, zIndex, Cleanable);
|
||||||
|
public Decal WithCleanable(bool cleanable) => new(Coordinates, Id, Color, Angle, ZIndex, cleanable);
|
||||||
|
}
|
||||||
|
}
|
||||||
15
Content.Shared/Decals/DecalChunkUpdateEvent.cs
Normal file
15
Content.Shared/Decals/DecalChunkUpdateEvent.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Shared.Decals
|
||||||
|
{
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public class DecalChunkUpdateEvent : EntityEventArgs
|
||||||
|
{
|
||||||
|
public Dictionary<GridId, Dictionary<Vector2i, Dictionary<uint, Decal>>> Data = new();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
using Robust.Shared.Serialization.Manager;
|
||||||
|
using Robust.Shared.Serialization.Manager.Attributes;
|
||||||
|
using Robust.Shared.Serialization.Manager.Result;
|
||||||
|
using Robust.Shared.Serialization.Markdown;
|
||||||
|
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||||
|
using Robust.Shared.Serialization.Markdown.Validation;
|
||||||
|
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
|
||||||
|
|
||||||
|
namespace Content.Shared.Decals
|
||||||
|
{
|
||||||
|
[TypeSerializer]
|
||||||
|
public class DecalGridChunkCollectionTypeSerializer : ITypeSerializer<DecalGridComponent.DecalGridChunkCollection, MappingDataNode>
|
||||||
|
{
|
||||||
|
public ValidationNode Validate(ISerializationManager serializationManager, MappingDataNode node,
|
||||||
|
IDependencyCollection dependencies, ISerializationContext? context = null)
|
||||||
|
{
|
||||||
|
return serializationManager.ValidateNode<Dictionary<Vector2i, Dictionary<uint, Decal>>>(node, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DeserializationResult Read(ISerializationManager serializationManager, MappingDataNode node,
|
||||||
|
IDependencyCollection dependencies, bool skipHook, ISerializationContext? context = null)
|
||||||
|
{
|
||||||
|
//todo this read method does not support pushing inheritance
|
||||||
|
var dictionary =
|
||||||
|
serializationManager.ReadValueOrThrow<Dictionary<Vector2i, Dictionary<uint, Decal>>>(node, context, skipHook);
|
||||||
|
|
||||||
|
var uids = new SortedSet<uint>();
|
||||||
|
var uidChunkMap = new Dictionary<uint, Vector2i>();
|
||||||
|
foreach (var (indices, decals) in dictionary)
|
||||||
|
{
|
||||||
|
foreach (var (uid, _) in decals)
|
||||||
|
{
|
||||||
|
uids.Add(uid);
|
||||||
|
uidChunkMap[uid] = indices;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var uidMap = new Dictionary<uint, uint>();
|
||||||
|
uint nextIndex = 0;
|
||||||
|
foreach (var uid in uids)
|
||||||
|
{
|
||||||
|
uidMap[uid] = nextIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
var newDict = new Dictionary<Vector2i, Dictionary<uint, Decal>>();
|
||||||
|
foreach (var (oldUid, newUid) in uidMap)
|
||||||
|
{
|
||||||
|
var indices = uidChunkMap[oldUid];
|
||||||
|
if(!newDict.ContainsKey(indices))
|
||||||
|
newDict[indices] = new();
|
||||||
|
newDict[indices][newUid] = dictionary[indices][oldUid];
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DeserializedValue<DecalGridComponent.DecalGridChunkCollection>(
|
||||||
|
new DecalGridComponent.DecalGridChunkCollection(newDict){NextUid = nextIndex});
|
||||||
|
}
|
||||||
|
|
||||||
|
public DataNode Write(ISerializationManager serializationManager, DecalGridComponent.DecalGridChunkCollection value, bool alwaysWrite = false,
|
||||||
|
ISerializationContext? context = null)
|
||||||
|
{
|
||||||
|
return serializationManager.WriteValue(value.ChunkCollection, alwaysWrite, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DecalGridComponent.DecalGridChunkCollection Copy(ISerializationManager serializationManager, DecalGridComponent.DecalGridChunkCollection source,
|
||||||
|
DecalGridComponent.DecalGridChunkCollection target, bool skipHook, ISerializationContext? context = null)
|
||||||
|
{
|
||||||
|
var dict = serializationManager.Copy(source.ChunkCollection, target.ChunkCollection, context, skipHook)!;
|
||||||
|
return new DecalGridComponent.DecalGridChunkCollection(dict) {NextUid = source.NextUid};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
23
Content.Shared/Decals/DecalGridComponent.cs
Normal file
23
Content.Shared/Decals/DecalGridComponent.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Robust.Shared.Analyzers;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
using Robust.Shared.Serialization.Manager.Attributes;
|
||||||
|
|
||||||
|
namespace Content.Shared.Decals
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
[Friend(typeof(SharedDecalSystem))]
|
||||||
|
public class DecalGridComponent : Component
|
||||||
|
{
|
||||||
|
public override string Name => "DecalGrid";
|
||||||
|
|
||||||
|
[DataField("chunkCollection", serverOnly: true)]
|
||||||
|
public DecalGridChunkCollection ChunkCollection = new(new ());
|
||||||
|
|
||||||
|
public record DecalGridChunkCollection(Dictionary<Vector2i, Dictionary<uint, Decal>> ChunkCollection)
|
||||||
|
{
|
||||||
|
public uint NextUid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
Content.Shared/Decals/DecalPrototype.cs
Normal file
15
Content.Shared/Decals/DecalPrototype.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Serialization.Manager.Attributes;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
|
namespace Content.Shared.Decals
|
||||||
|
{
|
||||||
|
[Prototype("decal")]
|
||||||
|
public class DecalPrototype : IPrototype
|
||||||
|
{
|
||||||
|
[DataField("id")] public string ID { get; } = null!;
|
||||||
|
[DataField("sprite")] public SpriteSpecifier Sprite { get; } = SpriteSpecifier.Invalid;
|
||||||
|
[DataField("tags")] public List<string> Tags = new();
|
||||||
|
}
|
||||||
|
}
|
||||||
153
Content.Shared/Decals/SharedDecalSystem.cs
Normal file
153
Content.Shared/Decals/SharedDecalSystem.cs
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using Robust.Shared;
|
||||||
|
using Robust.Shared.Configuration;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Shared.Decals
|
||||||
|
{
|
||||||
|
public abstract class SharedDecalSystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] protected readonly IPrototypeManager PrototypeManager = default!;
|
||||||
|
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||||
|
[Dependency] protected readonly IMapManager MapManager = default!;
|
||||||
|
|
||||||
|
protected readonly Dictionary<GridId, Dictionary<uint, Vector2i>> ChunkIndex = new();
|
||||||
|
|
||||||
|
public const int ChunkSize = 32;
|
||||||
|
public static Vector2i GetChunkIndices(Vector2 coordinates) => new ((int) Math.Floor(coordinates.X / ChunkSize), (int) Math.Floor(coordinates.Y / ChunkSize));
|
||||||
|
|
||||||
|
private float _viewSize;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
SubscribeLocalEvent<GridInitializeEvent>(OnGridInitialize);
|
||||||
|
_configurationManager.OnValueChanged(CVars.NetMaxUpdateRange, OnPvsRangeChanged, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Shutdown()
|
||||||
|
{
|
||||||
|
base.Shutdown();
|
||||||
|
_configurationManager.UnsubValueChanged(CVars.NetMaxUpdateRange, OnPvsRangeChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPvsRangeChanged(float obj)
|
||||||
|
{
|
||||||
|
_viewSize = obj * 2f;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGridInitialize(GridInitializeEvent msg)
|
||||||
|
{
|
||||||
|
var comp = EntityManager.EnsureComponent<DecalGridComponent>(MapManager.GetGrid(msg.GridId).GridEntityId);
|
||||||
|
ChunkIndex[msg.GridId] = new();
|
||||||
|
foreach (var (indices, decals) in comp.ChunkCollection.ChunkCollection)
|
||||||
|
{
|
||||||
|
foreach (var uid in decals.Keys)
|
||||||
|
{
|
||||||
|
ChunkIndex[msg.GridId][uid] = indices;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected DecalGridComponent.DecalGridChunkCollection DecalGridChunkCollection(GridId gridId) => EntityManager
|
||||||
|
.GetComponent<DecalGridComponent>(MapManager.GetGrid(gridId).GridEntityId).ChunkCollection;
|
||||||
|
protected Dictionary<Vector2i, Dictionary<uint, Decal>> ChunkCollection(GridId gridId) => DecalGridChunkCollection(gridId).ChunkCollection;
|
||||||
|
|
||||||
|
protected virtual void DirtyChunk(GridId id, Vector2i chunkIndices) {}
|
||||||
|
|
||||||
|
protected bool RemoveDecalInternal(GridId gridId, uint uid)
|
||||||
|
{
|
||||||
|
if (!RemoveDecalHook(gridId, uid)) return false;
|
||||||
|
|
||||||
|
if (!ChunkIndex.TryGetValue(gridId, out var values) || !values.TryGetValue(uid, out var indices))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var chunkCollection = ChunkCollection(gridId);
|
||||||
|
if (!chunkCollection.TryGetValue(indices, out var chunk) || !chunk.Remove(uid))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chunkCollection[indices].Count == 0)
|
||||||
|
chunkCollection.Remove(indices);
|
||||||
|
|
||||||
|
ChunkIndex[gridId]?.Remove(uid);
|
||||||
|
DirtyChunk(gridId, indices);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual bool RemoveDecalHook(GridId gridId, uint uid) => true;
|
||||||
|
|
||||||
|
private (Box2 view, MapId mapId) CalcViewBounds(in EntityUid euid)
|
||||||
|
{
|
||||||
|
var xform = EntityManager.GetComponent<TransformComponent>(euid);
|
||||||
|
|
||||||
|
var view = Box2.UnitCentered.Scale(_viewSize).Translated(xform.WorldPosition);
|
||||||
|
var map = xform.MapID;
|
||||||
|
|
||||||
|
return (view, map);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Dictionary<GridId, HashSet<Vector2i>> GetChunksForViewers(HashSet<EntityUid> viewers)
|
||||||
|
{
|
||||||
|
var chunks = new Dictionary<GridId, HashSet<Vector2i>>();
|
||||||
|
foreach (var viewerUid in viewers)
|
||||||
|
{
|
||||||
|
var (bounds, mapId) = CalcViewBounds(viewerUid);
|
||||||
|
MapManager.FindGridsIntersectingEnumerator(mapId, bounds, out var gridsEnumerator, true);
|
||||||
|
while(gridsEnumerator.MoveNext(out var grid))
|
||||||
|
{
|
||||||
|
if(!chunks.ContainsKey(grid.Index))
|
||||||
|
chunks[grid.Index] = new();
|
||||||
|
var enumerator = new ChunkIndicesEnumerator(grid.InvWorldMatrix.TransformBox(bounds), ChunkSize);
|
||||||
|
while (enumerator.MoveNext(out var indices))
|
||||||
|
{
|
||||||
|
chunks[grid.Index].Add(indices.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return chunks;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal struct ChunkIndicesEnumerator
|
||||||
|
{
|
||||||
|
private Vector2i _chunkLB;
|
||||||
|
private Vector2i _chunkRT;
|
||||||
|
|
||||||
|
private int _xIndex;
|
||||||
|
private int _yIndex;
|
||||||
|
|
||||||
|
internal ChunkIndicesEnumerator(Box2 localAABB, int chunkSize)
|
||||||
|
{
|
||||||
|
_chunkLB = new Vector2i((int)Math.Floor(localAABB.Left / chunkSize), (int)Math.Floor(localAABB.Bottom / chunkSize));
|
||||||
|
_chunkRT = new Vector2i((int)Math.Floor(localAABB.Right / chunkSize), (int)Math.Floor(localAABB.Top / chunkSize));
|
||||||
|
|
||||||
|
_xIndex = _chunkLB.X;
|
||||||
|
_yIndex = _chunkLB.Y;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool MoveNext([NotNullWhen(true)] out Vector2i? indices)
|
||||||
|
{
|
||||||
|
if (_yIndex > _chunkRT.Y)
|
||||||
|
{
|
||||||
|
_yIndex = _chunkLB.Y;
|
||||||
|
_xIndex += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
indices = new Vector2i(_xIndex, _yIndex);
|
||||||
|
_yIndex += 1;
|
||||||
|
|
||||||
|
return _xIndex <= _chunkRT.X;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -53,12 +53,13 @@ namespace Content.Shared.Jittering
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
/// <param name="uid">Entity in question.</param>
|
/// <param name="uid">Entity in question.</param>
|
||||||
/// <param name="time">For how much time to apply the effect.</param>
|
/// <param name="time">For how much time to apply the effect.</param>
|
||||||
|
/// <param name="refresh">The status effect cooldown should be refreshed (true) or accumulated (false).</param>
|
||||||
/// <param name="amplitude">Jitteriness of the animation. See <see cref="MaxAmplitude"/> and <see cref="MinAmplitude"/>.</param>
|
/// <param name="amplitude">Jitteriness of the animation. See <see cref="MaxAmplitude"/> and <see cref="MinAmplitude"/>.</param>
|
||||||
/// <param name="frequency">Frequency for jittering. See <see cref="MaxFrequency"/> and <see cref="MinFrequency"/>.</param>
|
/// <param name="frequency">Frequency for jittering. See <see cref="MaxFrequency"/> and <see cref="MinFrequency"/>.</param>
|
||||||
/// <param name="forceValueChange">Whether to change any existing jitter value even if they're greater than the ones we're setting.</param>
|
/// <param name="forceValueChange">Whether to change any existing jitter value even if they're greater than the ones we're setting.</param>
|
||||||
/// <param name="status">The status effects component to modify.</param>
|
/// <param name="status">The status effects component to modify.</param>
|
||||||
/// <param name="alerts">The alerts component.</param>
|
/// <param name="alerts">The alerts component.</param>
|
||||||
public void DoJitter(EntityUid uid, TimeSpan time, float amplitude = 10f, float frequency = 4f, bool forceValueChange = false,
|
public void DoJitter(EntityUid uid, TimeSpan time, bool refresh, float amplitude = 10f, float frequency = 4f, bool forceValueChange = false,
|
||||||
StatusEffectsComponent? status = null,
|
StatusEffectsComponent? status = null,
|
||||||
SharedAlertsComponent? alerts = null)
|
SharedAlertsComponent? alerts = null)
|
||||||
{
|
{
|
||||||
@@ -68,7 +69,7 @@ namespace Content.Shared.Jittering
|
|||||||
amplitude = Math.Clamp(amplitude, MinAmplitude, MaxAmplitude);
|
amplitude = Math.Clamp(amplitude, MinAmplitude, MaxAmplitude);
|
||||||
frequency = Math.Clamp(frequency, MinFrequency, MaxFrequency);
|
frequency = Math.Clamp(frequency, MinFrequency, MaxFrequency);
|
||||||
|
|
||||||
if (StatusEffects.TryAddStatusEffect<JitteringComponent>(uid, "Jitter", time, status, alerts))
|
if (StatusEffects.TryAddStatusEffect<JitteringComponent>(uid, "Jitter", time, refresh, status, alerts))
|
||||||
{
|
{
|
||||||
var jittering = EntityManager.GetComponent<JitteringComponent>(uid);
|
var jittering = EntityManager.GetComponent<JitteringComponent>(uid);
|
||||||
|
|
||||||
|
|||||||
@@ -289,9 +289,11 @@ namespace Content.Shared.MobState.Components
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void SetMobState(IMobState? old, (IMobState state, FixedPoint2 threshold)? current)
|
private void SetMobState(IMobState? old, (IMobState state, FixedPoint2 threshold)? current)
|
||||||
{
|
{
|
||||||
|
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||||
|
|
||||||
if (!current.HasValue)
|
if (!current.HasValue)
|
||||||
{
|
{
|
||||||
old?.ExitState(Owner, IoCManager.Resolve<IEntityManager>());
|
old?.ExitState(Owner, entMan);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -301,22 +303,19 @@ namespace Content.Shared.MobState.Components
|
|||||||
|
|
||||||
if (state == old)
|
if (state == old)
|
||||||
{
|
{
|
||||||
state.UpdateState(Owner, threshold, IoCManager.Resolve<IEntityManager>());
|
state.UpdateState(Owner, threshold, entMan);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
old?.ExitState(Owner, IoCManager.Resolve<IEntityManager>());
|
old?.ExitState(Owner, entMan);
|
||||||
|
|
||||||
CurrentState = state;
|
CurrentState = state;
|
||||||
|
|
||||||
state.EnterState(Owner, IoCManager.Resolve<IEntityManager>());
|
state.EnterState(Owner, entMan);
|
||||||
state.UpdateState(Owner, threshold, IoCManager.Resolve<IEntityManager>());
|
state.UpdateState(Owner, threshold, entMan);
|
||||||
|
|
||||||
var message = new MobStateChangedMessage(this, old, state);
|
var message = new MobStateChangedEvent(this, old, state);
|
||||||
#pragma warning disable 618
|
entMan.EventBus.RaiseLocalEvent(Owner, message);
|
||||||
SendMessage(message);
|
|
||||||
#pragma warning restore 618
|
|
||||||
IoCManager.Resolve<IEntityManager>().EventBus.RaiseEvent(EventSource.Local, message);
|
|
||||||
|
|
||||||
Dirty();
|
Dirty();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,9 @@ using Robust.Shared.GameObjects;
|
|||||||
|
|
||||||
namespace Content.Shared.MobState
|
namespace Content.Shared.MobState
|
||||||
{
|
{
|
||||||
#pragma warning disable 618
|
public class MobStateChangedEvent : EntityEventArgs
|
||||||
public class MobStateChangedMessage : ComponentMessage
|
|
||||||
#pragma warning restore 618
|
|
||||||
{
|
{
|
||||||
public MobStateChangedMessage(
|
public MobStateChangedEvent(
|
||||||
MobStateComponent component,
|
MobStateComponent component,
|
||||||
IMobState? oldMobState,
|
IMobState? oldMobState,
|
||||||
IMobState currentMobState)
|
IMobState currentMobState)
|
||||||
@@ -68,7 +68,7 @@ namespace Content.Shared.Nutrition.EntitySystems
|
|||||||
|
|
||||||
CreamedEntity(uid, creamPied, args);
|
CreamedEntity(uid, creamPied, args);
|
||||||
|
|
||||||
_stunSystem.TryParalyze(uid, TimeSpan.FromSeconds(creamPie.ParalyzeTime));
|
_stunSystem.TryParalyze(uid, TimeSpan.FromSeconds(creamPie.ParalyzeTime), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void CreamedEntity(EntityUid uid, CreamPiedComponent creamPied, ThrowHitByEvent args) {}
|
protected virtual void CreamedEntity(EntityUid uid, CreamPiedComponent creamPied, ThrowHitByEvent args) {}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user