Merge remote-tracking branch 'upstream/master' into ed-15-07-2024-upstream

This commit is contained in:
Ed
2024-07-15 12:58:23 +03:00
231 changed files with 2239 additions and 1162 deletions

View File

@@ -41,31 +41,22 @@ jobs:
- name: Package client
run: dotnet run --project Content.Packaging client --no-wipe-release
- name: Update Build Info
run: Tools/gen_build_info.py
- name: Shuffle files around
run: |
mkdir "release/${{ github.sha }}"
mv release/*.zip "release/${{ github.sha }}"
- name: Upload files to centcomm
uses: appleboy/scp-action@master
- name: Upload build artifact
id: artifact-upload-step
uses: actions/upload-artifact@v4
with:
host: centcomm.spacestation14.io
username: wizards-build-push
key: ${{ secrets.CENTCOMM_WIZARDS_BUILDS_PUSH_KEY }}
source: "release/${{ github.sha }}"
target: "/home/wizards-build-push/builds_dir/builds/"
strip_components: 1
name: build
path: release/*.zip
compression-level: 0
retention-days: 0
- name: Update manifest JSON
uses: appleboy/ssh-action@master
with:
host: centcomm.spacestation14.io
username: wizards-build-push
key: ${{ secrets.CENTCOMM_WIZARDS_BUILDS_PUSH_KEY }}
script: /home/wizards-build-push/push.ps1 ${{ github.sha }}
- name: Publish version
run: Tools/publish_github_artifact.py
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }}
ARTIFACT_ID: ${{ steps.artifact-upload-step.outputs.artifact-id }}
GITHUB_REPOSITORY: ${{ vars.GITHUB_REPOSITORY }}
- name: Publish changelog (Discord)
run: Tools/actions_changelogs_since_last_run.py
@@ -77,3 +68,8 @@ jobs:
run: Tools/actions_changelog_rss.py
env:
CHANGELOG_RSS_KEY: ${{ secrets.CHANGELOG_RSS_KEY }}
- uses: geekyeggo/delete-artifact@v5
if: always()
with:
name: build

View File

@@ -64,11 +64,3 @@ jobs:
- name: Package client
run: dotnet run --project Content.Packaging client --no-wipe-release
- name: Update Build Info
run: Tools/gen_build_info.py
- name: Shuffle files around
run: |
mkdir "release/${{ github.sha }}"
mv release/*.zip "release/${{ github.sha }}"

View File

@@ -12,11 +12,18 @@ namespace Content.Client.Access.UI;
[GenerateTypedNameReferences]
public sealed partial class AccessLevelControl : GridContainer
{
[Dependency] private readonly ILogManager _logManager = default!;
private ISawmill _sawmill = default!;
public readonly Dictionary<ProtoId<AccessLevelPrototype>, Button> ButtonsList = new();
public AccessLevelControl()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_sawmill = _logManager.GetSawmill("accesslevelcontrol");
}
public void Populate(List<ProtoId<AccessLevelPrototype>> accessLevels, IPrototypeManager prototypeManager)
@@ -25,7 +32,7 @@ public sealed partial class AccessLevelControl : GridContainer
{
if (!prototypeManager.TryIndex(access, out var accessLevel))
{
Logger.Error($"Unable to find accesslevel for {access}");
_sawmill.Error($"Unable to find accesslevel for {access}");
continue;
}

View File

@@ -1,4 +1,4 @@
using Content.Client.Stylesheets;
using Content.Client.Stylesheets;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -77,9 +77,12 @@ namespace Content.Client.Actions.UI
MaxWidth = TooltipTextMaxWidth,
StyleClasses = {StyleNano.StyleClassTooltipActionRequirements}
};
requiresLabel.SetMessage(FormattedMessage.FromMarkup("[color=#635c5c]" +
requires +
"[/color]"));
if (!FormattedMessage.TryFromMarkup("[color=#635c5c]" + requires + "[/color]", out var markup))
return;
requiresLabel.SetMessage(markup);
vbox.AddChild(requiresLabel);
}
}
@@ -97,8 +100,11 @@ namespace Content.Client.Actions.UI
if (timeLeft > TimeSpan.Zero)
{
var duration = Cooldown.Value.End - Cooldown.Value.Start;
_cooldownLabel.SetMessage(FormattedMessage.FromMarkup(
$"[color=#a10505]{(int) duration.TotalSeconds} sec cooldown ({(int) timeLeft.TotalSeconds + 1} sec remaining)[/color]"));
if (!FormattedMessage.TryFromMarkup($"[color=#a10505]{(int) duration.TotalSeconds} sec cooldown ({(int) timeLeft.TotalSeconds + 1} sec remaining)[/color]", out var markup))
return;
_cooldownLabel.SetMessage(markup);
_cooldownLabel.Visible = true;
}
else

View File

@@ -4,6 +4,7 @@ using Content.Shared.Audio;
using Content.Shared.CCVar;
using Content.Shared.GameTicking;
using Content.Shared.Random;
using Content.Shared.Random.Rules;
using Robust.Client.GameObjects;
using Robust.Client.Player;
using Robust.Client.ResourceManagement;

View File

@@ -0,0 +1,26 @@
using Content.Shared.Clock;
using Robust.Client.GameObjects;
namespace Content.Client.Clock;
public sealed class ClockSystem : SharedClockSystem
{
public override void Update(float frameTime)
{
base.Update(frameTime);
var query = EntityQueryEnumerator<ClockComponent, SpriteComponent>();
while (query.MoveNext(out var uid, out var comp, out var sprite))
{
if (!sprite.LayerMapTryGet(ClockVisualLayers.HourHand, out var hourLayer) ||
!sprite.LayerMapTryGet(ClockVisualLayers.MinuteHand, out var minuteLayer))
continue;
var time = GetClockTime((uid, comp));
var hourState = $"{comp.HoursBase}{time.Hours % 12}";
var minuteState = $"{comp.MinutesBase}{time.Minutes / 5}";
sprite.LayerSetState(hourLayer, hourState);
sprite.LayerSetState(minuteLayer, minuteState);
}
}
}

View File

@@ -48,11 +48,11 @@ namespace Content.Client.Construction
CommandBinds.Builder
.Bind(ContentKeyFunctions.OpenCraftingMenu,
new PointerInputCmdHandler(HandleOpenCraftingMenu, outsidePrediction:true))
new PointerInputCmdHandler(HandleOpenCraftingMenu, outsidePrediction: true))
.Bind(EngineKeyFunctions.Use,
new PointerInputCmdHandler(HandleUse, outsidePrediction: true))
.Bind(ContentKeyFunctions.EditorFlipObject,
new PointerInputCmdHandler(HandleFlip, outsidePrediction:true))
new PointerInputCmdHandler(HandleFlip, outsidePrediction: true))
.Register<ConstructionSystem>();
SubscribeLocalEvent<ConstructionGhostComponent, ExaminedEvent>(HandleConstructionGhostExamined);
@@ -196,7 +196,7 @@ namespace Content.Client.Construction
if (GhostPresent(loc))
return false;
var predicate = GetPredicate(prototype.CanBuildInImpassable, loc.ToMap(EntityManager, _transformSystem));
var predicate = GetPredicate(prototype.CanBuildInImpassable, _transformSystem.ToMapCoordinates(loc));
if (!_examineSystem.InRangeUnOccluded(user, loc, 20f, predicate: predicate))
return false;

View File

@@ -170,7 +170,7 @@ namespace Content.Client.ContextMenu.UI
if (_combatMode.IsInCombatMode(args.Session?.AttachedEntity))
return false;
var coords = args.Coordinates.ToMap(_entityManager, _xform);
var coords = _xform.ToMapCoordinates(args.Coordinates);
if (_verbSystem.TryGetEntityMenuEntities(coords, out var entities))
OpenRootMenu(entities);

View File

@@ -1,7 +0,0 @@
using Content.Shared.Extinguisher;
using Robust.Shared.GameStates;
namespace Content.Client.Extinguisher;
[RegisterComponent]
public sealed partial class FireExtinguisherComponent : SharedFireExtinguisherComponent;

View File

@@ -1,27 +1,22 @@
using Content.Shared.Flash;
using Content.Shared.Flash.Components;
using Content.Shared.StatusEffect;
using Content.Client.Viewport;
using Robust.Client.Graphics;
using Robust.Client.State;
using Robust.Client.Player;
using Robust.Shared.Enums;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using SixLabors.ImageSharp.PixelFormats;
namespace Content.Client.Flash
{
public sealed class FlashOverlay : Overlay
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IClyde _displayManager = default!;
[Dependency] private readonly IStateManager _stateManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IGameTiming _timing = default!;
private readonly StatusEffectsSystem _statusSys;
private readonly StatusEffectsSystem _statusSys;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
private readonly ShaderInstance _shader;
@@ -56,20 +51,6 @@ namespace Content.Client.Flash
PercentComplete = timeDone / lastsFor;
}
public void ReceiveFlash()
{
if (_stateManager.CurrentState is IMainViewportState state)
{
// take a screenshot
// note that the callback takes a while and ScreenshotTexture will be null the first few Draws
state.Viewport.Viewport.Screenshot(image =>
{
var rgba32Image = image.CloneAs<Rgba32>(SixLabors.ImageSharp.Configuration.Default);
ScreenshotTexture = _displayManager.LoadTextureFromImage(rgba32Image);
});
}
}
protected override bool BeforeDraw(in OverlayDrawArgs args)
{
if (!_entityManager.TryGetComponent(_playerManager.LocalEntity, out EyeComponent? eyeComp))
@@ -82,6 +63,11 @@ namespace Content.Client.Flash
protected override void Draw(in OverlayDrawArgs args)
{
if (RequestScreenTexture && ScreenTexture != null)
{
ScreenshotTexture = ScreenTexture;
RequestScreenTexture = false; // we only need the first frame, so we can stop the request now for performance reasons
}
if (ScreenshotTexture == null)
return;
@@ -96,7 +82,6 @@ namespace Content.Client.Flash
{
base.DisposeBehavior();
ScreenshotTexture = null;
PercentComplete = 1.0f;
}
}
}

View File

@@ -22,7 +22,6 @@ public sealed class FlashSystem : SharedFlashSystem
SubscribeLocalEvent<FlashedComponent, ComponentShutdown>(OnShutdown);
SubscribeLocalEvent<FlashedComponent, LocalPlayerAttachedEvent>(OnPlayerAttached);
SubscribeLocalEvent<FlashedComponent, LocalPlayerDetachedEvent>(OnPlayerDetached);
SubscribeLocalEvent<FlashedComponent, StatusEffectAddedEvent>(OnStatusAdded);
_overlay = new();
}
@@ -34,8 +33,8 @@ public sealed class FlashSystem : SharedFlashSystem
private void OnPlayerDetached(EntityUid uid, FlashedComponent component, LocalPlayerDetachedEvent args)
{
_overlay.PercentComplete = 1.0f;
_overlay.ScreenshotTexture = null;
_overlay.RequestScreenTexture = false;
_overlayMan.RemoveOverlay(_overlay);
}
@@ -43,6 +42,7 @@ public sealed class FlashSystem : SharedFlashSystem
{
if (_player.LocalEntity == uid)
{
_overlay.RequestScreenTexture = true;
_overlayMan.AddOverlay(_overlay);
}
}
@@ -51,17 +51,9 @@ public sealed class FlashSystem : SharedFlashSystem
{
if (_player.LocalEntity == uid)
{
_overlay.PercentComplete = 1.0f;
_overlay.ScreenshotTexture = null;
_overlay.RequestScreenTexture = false;
_overlayMan.RemoveOverlay(_overlay);
}
}
private void OnStatusAdded(EntityUid uid, FlashedComponent component, StatusEffectAddedEvent args)
{
if (_player.LocalEntity == uid && args.Key == FlashedKey)
{
_overlay.ReceiveFlash();
}
}
}

View File

@@ -104,7 +104,8 @@ namespace Content.Client.Gameplay
public IEnumerable<EntityUid> GetClickableEntities(EntityCoordinates coordinates)
{
return GetClickableEntities(coordinates.ToMap(_entityManager, _entitySystemManager.GetEntitySystem<SharedTransformSystem>()));
var transformSystem = _entitySystemManager.GetEntitySystem<SharedTransformSystem>();
return GetClickableEntities(transformSystem.ToMapCoordinates(coordinates));
}
public IEnumerable<EntityUid> GetClickableEntities(MapCoordinates coordinates)

View File

@@ -25,6 +25,9 @@ namespace Content.Client.MainMenu
[Dependency] private readonly IGameController _controllerProxy = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!;
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
[Dependency] private readonly ILogManager _logManager = default!;
private ISawmill _sawmill = default!;
private MainMenuControl _mainMenuControl = default!;
private bool _isConnecting;
@@ -35,6 +38,8 @@ namespace Content.Client.MainMenu
/// <inheritdoc />
protected override void Startup()
{
_sawmill = _logManager.GetSawmill("mainmenu");
_mainMenuControl = new MainMenuControl(_resourceCache, _configurationManager);
_userInterfaceManager.StateRoot.AddChild(_mainMenuControl);
@@ -116,7 +121,7 @@ namespace Content.Client.MainMenu
catch (ArgumentException e)
{
_userInterfaceManager.Popup($"Unable to connect: {e.Message}", "Connection error.");
Logger.Warning(e.ToString());
_sawmill.Warning(e.ToString());
_netManager.ConnectFailed -= _onConnectFailed;
_setConnectingState(false);
}

View File

@@ -1,4 +1,4 @@
using Content.Shared.Materials;
using Content.Shared.Materials;
using Robust.Client.GameObjects;
namespace Content.Client.Materials;
@@ -49,7 +49,7 @@ public sealed class MaterialStorageSystem : SharedMaterialStorageSystem
{
if (!base.TryInsertMaterialEntity(user, toInsert, receiver, storage, material, composition))
return false;
_transform.DetachParentToNull(toInsert, Transform(toInsert));
_transform.DetachEntity(toInsert, Transform(toInsert));
return true;
}
}

View File

@@ -15,6 +15,7 @@ public sealed class JetpackSystem : SharedJetpackSystem
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly ClothingSystem _clothing = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
public override void Initialize()
{
@@ -73,11 +74,11 @@ public sealed class JetpackSystem : SharedJetpackSystem
var uidXform = Transform(uid);
var coordinates = uidXform.Coordinates;
var gridUid = coordinates.GetGridUid(EntityManager);
var gridUid = _transform.GetGrid(coordinates);
if (TryComp<MapGridComponent>(gridUid, out var grid))
{
coordinates = new EntityCoordinates(gridUid.Value, grid.WorldToLocal(coordinates.ToMapPos(EntityManager, _transform)));
coordinates = new EntityCoordinates(gridUid.Value, _mapSystem.WorldToLocal(gridUid.Value, grid, _transform.ToMapCoordinates(coordinates).Position));
}
else if (uidXform.MapUid != null)
{

View File

@@ -203,7 +203,7 @@ namespace Content.Client.NPC
if (found || !_system.Breadcrumbs.TryGetValue(netGrid, out var crumbs) || !xformQuery.TryGetComponent(grid, out var gridXform))
continue;
var (_, _, worldMatrix, invWorldMatrix) = gridXform.GetWorldPositionRotationMatrixWithInv();
var (_, _, worldMatrix, invWorldMatrix) = _transformSystem.GetWorldPositionRotationMatrixWithInv(gridXform);
var localAABB = invWorldMatrix.TransformBox(aabb.Enlarged(float.Epsilon - SharedPathfindingSystem.ChunkSize));
foreach (var chunk in crumbs)
@@ -287,7 +287,7 @@ namespace Content.Client.NPC
return;
}
var invGridMatrix = gridXform.InvWorldMatrix;
var invGridMatrix = _transformSystem.GetInvWorldMatrix(gridXform);
DebugPathPoly? nearest = null;
foreach (var poly in tile)
@@ -359,7 +359,7 @@ namespace Content.Client.NPC
continue;
}
var (_, _, worldMatrix, invWorldMatrix) = gridXform.GetWorldPositionRotationMatrixWithInv();
var (_, _, worldMatrix, invWorldMatrix) = _transformSystem.GetWorldPositionRotationMatrixWithInv(gridXform);
worldHandle.SetTransform(worldMatrix);
var localAABB = invWorldMatrix.TransformBox(aabb);
@@ -419,7 +419,7 @@ namespace Content.Client.NPC
!xformQuery.TryGetComponent(grid, out var gridXform))
continue;
var (_, _, worldMatrix, invWorldMatrix) = gridXform.GetWorldPositionRotationMatrixWithInv();
var (_, _, worldMatrix, invWorldMatrix) = _transformSystem.GetWorldPositionRotationMatrixWithInv(gridXform);
worldHandle.SetTransform(worldMatrix);
var localAABB = invWorldMatrix.TransformBox(aabb);
@@ -458,7 +458,7 @@ namespace Content.Client.NPC
!xformQuery.TryGetComponent(grid, out var gridXform))
continue;
var (_, _, worldMatrix, invMatrix) = gridXform.GetWorldPositionRotationMatrixWithInv();
var (_, _, worldMatrix, invMatrix) = _transformSystem.GetWorldPositionRotationMatrixWithInv(gridXform);
worldHandle.SetTransform(worldMatrix);
var localAABB = invMatrix.TransformBox(aabb);
@@ -483,7 +483,7 @@ namespace Content.Client.NPC
if (neighborPoly.NetEntity != poly.GraphUid)
{
color = Color.Green;
var neighborMap = _entManager.GetCoordinates(neighborPoly).ToMap(_entManager, _transformSystem);
var neighborMap = _transformSystem.ToMapCoordinates(_entManager.GetCoordinates(neighborPoly));
if (neighborMap.MapId != args.MapId)
continue;
@@ -517,7 +517,7 @@ namespace Content.Client.NPC
!xformQuery.TryGetComponent(grid, out var gridXform))
continue;
var (_, _, worldMatrix, invWorldMatrix) = gridXform.GetWorldPositionRotationMatrixWithInv();
var (_, _, worldMatrix, invWorldMatrix) = _transformSystem.GetWorldPositionRotationMatrixWithInv(gridXform);
worldHandle.SetTransform(worldMatrix);
var localAABB = invWorldMatrix.TransformBox(args.WorldBounds);
@@ -544,7 +544,7 @@ namespace Content.Client.NPC
if (!_entManager.TryGetComponent<TransformComponent>(_entManager.GetEntity(node.GraphUid), out var graphXform))
continue;
worldHandle.SetTransform(graphXform.WorldMatrix);
worldHandle.SetTransform(_transformSystem.GetWorldMatrix(graphXform));
worldHandle.DrawRect(node.Box, Color.Orange.WithAlpha(0.10f));
}
}
@@ -568,7 +568,7 @@ namespace Content.Client.NPC
continue;
matrix = graph;
worldHandle.SetTransform(graphXform.WorldMatrix);
worldHandle.SetTransform(_transformSystem.GetWorldMatrix(graphXform));
}
worldHandle.DrawRect(node.Box, new Color(0f, cost / highestGScore, 1f - (cost / highestGScore), 0.10f));

View File

@@ -58,8 +58,8 @@ public sealed class JointVisualsOverlay : Overlay
coordsA = coordsA.Offset(rotA.RotateVec(visuals.OffsetA));
coordsB = coordsB.Offset(rotB.RotateVec(visuals.OffsetB));
var posA = coordsA.ToMapPos(_entManager, xformSystem);
var posB = coordsB.ToMapPos(_entManager, xformSystem);
var posA = xformSystem.ToMapCoordinates(coordsA).Position;
var posB = xformSystem.ToMapCoordinates(coordsB).Position;
var diff = (posB - posA);
var length = diff.Length();

View File

@@ -228,8 +228,8 @@ public partial class NavMapControl : MapGridControl
{
if (!blip.Selectable)
continue;
var currentDistance = (blip.Coordinates.ToMapPos(EntManager, _transformSystem) - worldPosition).Length();
var currentDistance = (_transformSystem.ToMapCoordinates(blip.Coordinates).Position - worldPosition).Length();
if (closestDistance < currentDistance || currentDistance * MinimapScale > MaxSelectableDistance)
continue;
@@ -397,7 +397,7 @@ public partial class NavMapControl : MapGridControl
{
if (lit && value.Visible)
{
var mapPos = coord.ToMap(EntManager, _transformSystem);
var mapPos = _transformSystem.ToMapCoordinates(coord);
if (mapPos.MapId != MapId.Nullspace)
{
@@ -418,7 +418,7 @@ public partial class NavMapControl : MapGridControl
if (blip.Texture == null)
continue;
var mapPos = blip.Coordinates.ToMap(EntManager, _transformSystem);
var mapPos = _transformSystem.ToMapCoordinates(blip.Coordinates);
if (mapPos.MapId != MapId.Nullspace)
{
@@ -535,7 +535,7 @@ public partial class NavMapControl : MapGridControl
// East edge
neighborData = 0;
if (relativeTile.X != SharedNavMapSystem.ChunkSize - 1)
neighborData = chunk.TileData[i+SharedNavMapSystem.ChunkSize];
neighborData = chunk.TileData[i + SharedNavMapSystem.ChunkSize];
else if (_navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Right, out neighborChunk))
neighborData = neighborChunk.TileData[i + SharedNavMapSystem.ChunkSize - SharedNavMapSystem.ArraySize];
@@ -548,7 +548,7 @@ public partial class NavMapControl : MapGridControl
// South edge
neighborData = 0;
if (relativeTile.Y != 0)
neighborData = chunk.TileData[i-1];
neighborData = chunk.TileData[i - 1];
else if (_navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Down, out neighborChunk))
neighborData = neighborChunk.TileData[i - 1 + SharedNavMapSystem.ChunkSize];
@@ -561,7 +561,7 @@ public partial class NavMapControl : MapGridControl
// West edge
neighborData = 0;
if (relativeTile.X != 0)
neighborData = chunk.TileData[i-SharedNavMapSystem.ChunkSize];
neighborData = chunk.TileData[i - SharedNavMapSystem.ChunkSize];
else if (_navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Left, out neighborChunk))
neighborData = neighborChunk.TileData[i - SharedNavMapSystem.ChunkSize + SharedNavMapSystem.ArraySize];

View File

@@ -45,7 +45,7 @@ public sealed class AlignRCDConstruction : PlacementMode
_unalignedMouseCoords = ScreenToCursorGrid(mouseScreen);
MouseCoords = _unalignedMouseCoords.AlignWithClosestGridTile(SearchBoxSize, _entityManager, _mapManager);
var gridId = MouseCoords.GetGridUid(_entityManager);
var gridId = _transformSystem.GetGrid(MouseCoords);
if (!_entityManager.TryGetComponent<MapGridComponent>(gridId, out var mapGrid))
return;
@@ -75,7 +75,7 @@ public sealed class AlignRCDConstruction : PlacementMode
if (!_entityManager.TryGetComponent<TransformComponent>(player, out var xform))
return false;
if (!xform.Coordinates.InRange(_entityManager, _transformSystem, position, SharedInteractionSystem.InteractionRange))
if (!_transformSystem.InRange(xform.Coordinates, position, SharedInteractionSystem.InteractionRange))
{
InvalidPlaceColor = InvalidPlaceColor.WithAlpha(0);
return false;
@@ -105,8 +105,8 @@ public sealed class AlignRCDConstruction : PlacementMode
if (currentState is not GameplayStateBase screen)
return false;
var target = screen.GetClickedEntity(_unalignedMouseCoords.ToMap(_entityManager, _transformSystem));
var target = screen.GetClickedEntity(_transformSystem.ToMapCoordinates(_unalignedMouseCoords));
// Determine if the RCD operation is valid or not
if (!_rcdSystem.IsRCDOperationStillValid(heldEntity.Value, rcd, mapGridData.Value, target, player.Value, false))

View File

@@ -116,7 +116,9 @@ namespace Content.Client.Radiation.Overlays
var shaderInstance = _pulses[pulseEntity];
shaderInstance.instance.CurrentMapCoords = _transform.GetMapCoordinates(pulseEntity);
shaderInstance.instance.Range = pulse.VisualRange;
} else {
}
else
{
_pulses[pulseEntity].shd.Dispose();
_pulses.Remove(pulseEntity);
}
@@ -129,7 +131,7 @@ namespace Content.Client.Radiation.Overlays
var transformComponent = _entityManager.GetComponent<TransformComponent>(pulseEntity);
var transformSystem = _entityManager.System<SharedTransformSystem>();
return transformComponent.MapID == currentEyeLoc.MapId
&& transformComponent.Coordinates.InRange(_entityManager, transformSystem, EntityCoordinates.FromMap(transformComponent.ParentUid, currentEyeLoc, transformSystem, _entityManager), MaxDist);
&& transformSystem.InRange(transformComponent.Coordinates, transformSystem.ToCoordinates(transformComponent.ParentUid, currentEyeLoc), MaxDist);
}
private sealed record RadiationShaderInstance(MapCoordinates CurrentMapCoords, float Range, TimeSpan Start, float Duration)

View File

@@ -17,6 +17,7 @@ namespace Content.Client.Sandbox
[Dependency] private readonly IPlacementManager _placement = default!;
[Dependency] private readonly ContentEyeSystem _contentEye = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
private bool _sandboxEnabled;
public bool SandboxAllowed { get; private set; }
@@ -92,7 +93,7 @@ namespace Content.Client.Sandbox
&& EntityManager.TryGetComponent(uid, out MetaDataComponent? comp)
&& !comp.EntityDeleted)
{
if (comp.EntityPrototype == null || comp.EntityPrototype.NoSpawn || comp.EntityPrototype.Abstract)
if (comp.EntityPrototype == null || comp.EntityPrototype.HideSpawnMenu || comp.EntityPrototype.Abstract)
return false;
if (_placement.Eraser)
@@ -109,7 +110,8 @@ namespace Content.Client.Sandbox
}
// Try copy tile.
if (!_map.TryFindGridAt(coords.ToMap(EntityManager, _transform), out _, out var grid) || !grid.TryGetTileRef(coords, out var tileRef))
if (!_map.TryFindGridAt(_transform.ToMapCoordinates(coords), out var gridUid, out var grid) || !_mapSystem.TryGetTileRef(gridUid, grid, coords, out var tileRef))
return false;
if (_placement.Eraser)

View File

@@ -35,9 +35,9 @@ public sealed partial class ShuttleSystem
switch (mapObj)
{
case ShuttleBeaconObject beacon:
return GetCoordinates(beacon.Coordinates).ToMap(EntityManager, XformSystem);
return XformSystem.ToMapCoordinates(GetCoordinates(beacon.Coordinates));
case ShuttleExclusionObject exclusion:
return GetCoordinates(exclusion.Coordinates).ToMap(EntityManager, XformSystem);
return XformSystem.ToMapCoordinates(GetCoordinates(exclusion.Coordinates));
case GridMapObject grid:
var gridXform = Transform(grid.Entity);

View File

@@ -107,7 +107,7 @@ public sealed partial class ShuttleDockControl : BaseShuttleControl
DrawCircles(handle);
var gridNent = EntManager.GetNetEntity(GridEntity);
var mapPos = _xformSystem.ToMapCoordinates(_coordinates.Value);
var ourGridMatrix = _xformSystem.GetWorldMatrix(gridXform.Owner);
var ourGridMatrix = _xformSystem.GetWorldMatrix(GridEntity.Value);
var dockMatrix = Matrix3Helpers.CreateTransform(_coordinates.Value.Position, Angle.Zero);
var worldFromDock = Matrix3x2.Multiply(dockMatrix, ourGridMatrix);

View File

@@ -519,7 +519,7 @@ public sealed partial class ShuttleMapControl : BaseShuttleControl
if (mapO is not ShuttleBeaconObject beacon)
continue;
var beaconCoords = EntManager.GetCoordinates(beacon.Coordinates).ToMap(EntManager, _xformSystem);
var beaconCoords = _xformSystem.ToMapCoordinates(EntManager.GetCoordinates(beacon.Coordinates));
var position = Vector2.Transform(beaconCoords.Position, mapTransform);
var localPos = ScalePosition(position with {Y = -position.Y});

View File

@@ -44,7 +44,7 @@ namespace Content.Client.Stack
// TODO PREDICT ENTITY DELETION: This should really just be a normal entity deletion call.
if (component.Count <= 0 && !component.Lingering)
{
Xform.DetachParentToNull(uid, Transform(uid));
Xform.DetachEntity(uid, Transform(uid));
return;
}

View File

@@ -1,4 +1,4 @@
using System.Linq;
using System.Linq;
using System.Numerics;
using Content.Client.Animations;
using Content.Shared.Hands;
@@ -142,14 +142,14 @@ public sealed class StorageSystem : SharedStorageSystem
{
if (!_timing.IsFirstTimePredicted)
return;
if (finalCoords.InRange(EntityManager, TransformSystem, initialCoords, 0.1f) ||
if (TransformSystem.InRange(finalCoords, initialCoords, 0.1f) ||
!Exists(initialCoords.EntityId) || !Exists(finalCoords.EntityId))
{
return;
}
var finalMapPos = finalCoords.ToMapPos(EntityManager, TransformSystem);
var finalMapPos = TransformSystem.ToMapCoordinates(finalCoords).Position;
var finalPos = Vector2.Transform(finalMapPos, TransformSystem.GetInvWorldMatrix(initialCoords.EntityId));
_entityPickupAnimation.AnimateEntityPickup(item, initialCoords, finalPos, initialAngle);

View File

@@ -220,7 +220,7 @@ public sealed partial class MeleeWeaponSystem : SharedMeleeWeaponSystem
return;
}
var targetMap = coordinates.ToMap(EntityManager, TransformSystem);
var targetMap = TransformSystem.ToMapCoordinates(coordinates);
if (targetMap.MapId != userXform.MapID)
return;

View File

@@ -176,7 +176,7 @@ public sealed partial class GunSystem : SharedGunSystem
}
// Define target coordinates relative to gun entity, so that network latency on moving grids doesn't fuck up the target location.
var coordinates = EntityCoordinates.FromMap(entity, mousePos, TransformSystem, EntityManager);
var coordinates = TransformSystem.ToCoordinates(entity, mousePos);
NetEntity? target = null;
if (_state.CurrentState is GameplayStateBase screen)
@@ -200,7 +200,7 @@ public sealed partial class GunSystem : SharedGunSystem
// Rather than splitting client / server for every ammo provider it's easier
// to just delete the spawned entities. This is for programmer sanity despite the wasted perf.
// This also means any ammo specific stuff can be grabbed as necessary.
var direction = fromCoordinates.ToMapPos(EntityManager, TransformSystem) - toCoordinates.ToMapPos(EntityManager, TransformSystem);
var direction = TransformSystem.ToMapCoordinates(fromCoordinates).Position - TransformSystem.ToMapCoordinates(toCoordinates).Position;
var worldAngle = direction.ToAngle().Opposite();
foreach (var (ent, shootable) in ammo)
@@ -276,6 +276,14 @@ public sealed partial class GunSystem : SharedGunSystem
if (!Timing.IsFirstTimePredicted)
return;
// EntityUid check added to stop throwing exceptions due to https://github.com/space-wizards/space-station-14/issues/28252
// TODO: Check to see why invalid entities are firing effects.
if (gunUid == EntityUid.Invalid)
{
Log.Debug($"Invalid Entity sent MuzzleFlashEvent (proto: {message.Prototype}, user: {user})");
return;
}
var gunXform = Transform(gunUid);
var gridUid = gunXform.GridUid;
EntityCoordinates coordinates;
@@ -375,6 +383,6 @@ public sealed partial class GunSystem : SharedGunSystem
var uidPlayer = EnsureComp<AnimationPlayerComponent>(gunUid);
_animPlayer.Stop(gunUid, uidPlayer, "muzzle-flash-light");
_animPlayer.Play((gunUid, uidPlayer), animTwo,"muzzle-flash-light");
_animPlayer.Play((gunUid, uidPlayer), animTwo, "muzzle-flash-light");
}
}

View File

@@ -0,0 +1,133 @@
using System.Linq;
using Content.Server.Antag.Components;
using Content.Server.GameTicking;
using Content.Server.GameTicking.Rules;
using Content.Server.GameTicking.Rules.Components;
using Content.Server.Mind;
using Content.Server.Roles;
using Content.Shared.GameTicking;
using Content.Shared.GameTicking.Components;
using Content.Shared.Mind;
using Content.Shared.NPC.Systems;
using Content.Shared.Objectives.Components;
using Robust.Shared.GameObjects;
using Robust.Shared.Prototypes;
namespace Content.IntegrationTests.Tests.GameRules;
[TestFixture]
public sealed class TraitorRuleTest
{
private const string TraitorGameRuleProtoId = "Traitor";
private const string TraitorAntagRoleName = "Traitor";
[Test]
public async Task TestTraitorObjectives()
{
await using var pair = await PoolManager.GetServerClient(new PoolSettings()
{
Dirty = true,
DummyTicker = false,
Connected = true,
InLobby = true,
});
var server = pair.Server;
var client = pair.Client;
var entMan = server.EntMan;
var protoMan = server.ProtoMan;
var compFact = server.ResolveDependency<IComponentFactory>();
var ticker = server.System<GameTicker>();
var mindSys = server.System<MindSystem>();
var roleSys = server.System<RoleSystem>();
var factionSys = server.System<NpcFactionSystem>();
var traitorRuleSys = server.System<TraitorRuleSystem>();
// Look up the minimum player count and max total objective difficulty for the game rule
var minPlayers = 1;
var maxDifficulty = 0f;
await server.WaitAssertion(() =>
{
Assert.That(protoMan.TryIndex<EntityPrototype>(TraitorGameRuleProtoId, out var gameRuleEnt),
$"Failed to lookup traitor game rule entity prototype with ID \"{TraitorGameRuleProtoId}\"!");
Assert.That(gameRuleEnt.TryGetComponent<GameRuleComponent>(out var gameRule, compFact),
$"Game rule entity {TraitorGameRuleProtoId} does not have a GameRuleComponent!");
Assert.That(gameRuleEnt.TryGetComponent<AntagRandomObjectivesComponent>(out var randomObjectives, compFact),
$"Game rule entity {TraitorGameRuleProtoId} does not have an AntagRandomObjectivesComponent!");
minPlayers = gameRule.MinPlayers;
maxDifficulty = randomObjectives.MaxDifficulty;
});
// Initially in the lobby
Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.PreRoundLobby));
Assert.That(client.AttachedEntity, Is.Null);
Assert.That(ticker.PlayerGameStatuses[client.User!.Value], Is.EqualTo(PlayerGameStatus.NotReadyToPlay));
// Add enough dummy players for the game rule
var dummies = await pair.Server.AddDummySessions(minPlayers);
await pair.RunTicksSync(5);
// Initially, the players have no attached entities
Assert.That(pair.Player?.AttachedEntity, Is.Null);
Assert.That(dummies.All(x => x.AttachedEntity == null));
// Opt-in the player for the traitor role
await pair.SetAntagPreference(TraitorAntagRoleName, true);
// Add the game rule
var gameRuleEnt = ticker.AddGameRule(TraitorGameRuleProtoId);
Assert.That(entMan.TryGetComponent<TraitorRuleComponent>(gameRuleEnt, out var traitorRule));
// Ready up
ticker.ToggleReadyAll(true);
Assert.That(ticker.PlayerGameStatuses.Values.All(x => x == PlayerGameStatus.ReadyToPlay));
// Start the round
await server.WaitPost(() =>
{
ticker.StartRound();
// Force traitor mode to start (skip the delay)
ticker.StartGameRule(gameRuleEnt);
});
await pair.RunTicksSync(10);
// Game should have started
Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.InRound));
Assert.That(ticker.PlayerGameStatuses.Values.All(x => x == PlayerGameStatus.JoinedGame));
Assert.That(client.EntMan.EntityExists(client.AttachedEntity));
// Check the player and dummies are spawned
var dummyEnts = dummies.Select(x => x.AttachedEntity ?? default).ToArray();
var player = pair.Player!.AttachedEntity!.Value;
Assert.That(entMan.EntityExists(player));
Assert.That(dummyEnts.All(entMan.EntityExists));
// Make sure the player is a traitor.
var mind = mindSys.GetMind(player)!.Value;
Assert.That(roleSys.MindIsAntagonist(mind));
Assert.That(factionSys.IsMember(player, "Syndicate"), Is.True);
Assert.That(factionSys.IsMember(player, "NanoTrasen"), Is.False);
Assert.That(traitorRule.TotalTraitors, Is.EqualTo(1));
Assert.That(traitorRule.TraitorMinds[0], Is.EqualTo(mind));
// Check total objective difficulty
Assert.That(entMan.TryGetComponent<MindComponent>(mind, out var mindComp));
var totalDifficulty = mindComp.Objectives.Sum(o => entMan.GetComponent<ObjectiveComponent>(o).Difficulty);
Assert.That(totalDifficulty, Is.AtMost(maxDifficulty),
$"MaxDifficulty exceeded! Objectives: {string.Join(", ", mindComp.Objectives.Select(o => FormatObjective(o, entMan)))}");
Assert.That(mindComp.Objectives, Is.Not.Empty,
$"No objectives assigned!");
await pair.CleanReturnAsync();
}
private static string FormatObjective(Entity<ObjectiveComponent> entity, IEntityManager entMan)
{
var meta = entMan.GetComponent<MetaDataComponent>(entity);
var objective = entMan.GetComponent<ObjectiveComponent>(entity);
return $"{meta.EntityName} ({objective.Difficulty})";
}
}

View File

@@ -114,7 +114,7 @@ public abstract partial class InteractionTest
return await SpawnEntity((stack.StackTypeId, spec.Quantity), coords);
Assert.That(spec.Quantity, Is.EqualTo(1), "SpawnEntity only supports returning a singular entity");
await Server.WaitPost(() => uid = SEntMan.SpawnEntity(spec.Prototype, coords));
await Server.WaitPost(() => uid = SEntMan.SpawnAtPosition(spec.Prototype, coords));
return uid;
}

View File

@@ -91,7 +91,7 @@ public abstract partial class InteractionTest
Target = NetEntity.Invalid;
await Server.WaitPost(() =>
{
Target = SEntMan.GetNetEntity(SEntMan.SpawnEntity(prototype, SEntMan.GetCoordinates(TargetCoords)));
Target = SEntMan.GetNetEntity(SEntMan.SpawnAtPosition(prototype, SEntMan.GetCoordinates(TargetCoords)));
});
await RunTicks(5);

View File

@@ -12,13 +12,12 @@ using Robust.Shared.Console;
namespace Content.Server.Administration.Commands;
[AdminCommand(AdminFlags.Admin)]
public sealed class AGhost : LocalizedCommands
public sealed class AGhostCommand : LocalizedCommands
{
[Dependency] private readonly IEntityManager _entities = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
public override string Command => "aghost";
public override string Description => LocalizationManager.GetString("aghost-description");
public override string Help => "aghost";
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)

View File

@@ -39,7 +39,8 @@ public sealed class AntagRandomObjectivesSystem : EntitySystem
for (var pick = 0; pick < set.MaxPicks && ent.Comp.MaxDifficulty > difficulty; pick++)
{
if (_objectives.GetRandomObjective(mindId, mind, set.Groups) is not {} objective)
var remainingDifficulty = ent.Comp.MaxDifficulty - difficulty;
if (_objectives.GetRandomObjective(mindId, mind, set.Groups, remainingDifficulty) is not { } objective)
continue;
_mind.AddObjective(mindId, mind, objective);

View File

@@ -162,7 +162,7 @@ namespace Content.Server.Atmos.EntitySystems
if (component.LastPosition.HasValue)
{
// Check if position is out of range => don't update and disable
if (!component.LastPosition.Value.InRange(EntityManager, _transform, userPos, SharedInteractionSystem.InteractionRange))
if (!_transform.InRange(component.LastPosition.Value, userPos, SharedInteractionSystem.InteractionRange))
{
if (component.User is { } userId && component.Enabled)
_popup.PopupEntity(Loc.GetString("gas-analyzer-shutoff"), userId, userId);

View File

@@ -81,7 +81,7 @@ namespace Content.Server.Atmos.EntitySystems
TankPressure = component.Air?.Pressure ?? 0,
OutputPressure = initialUpdate ? component.OutputPressure : null,
InternalsConnected = component.IsConnected,
CanConnectInternals = CanConnectToInternals(component)
CanConnectInternals = CanConnectToInternals(ent)
});
}
@@ -217,24 +217,24 @@ namespace Content.Server.Atmos.EntitySystems
return air;
}
public bool CanConnectToInternals(GasTankComponent component)
public bool CanConnectToInternals(Entity<GasTankComponent> ent)
{
var internals = GetInternalsComponent(component, component.User);
return internals != null && internals.BreathTools.Count != 0 && !component.IsValveOpen;
TryGetInternalsComp(ent, out _, out var internalsComp, ent.Comp.User);
return internalsComp != null && internalsComp.BreathTools.Count != 0 && !ent.Comp.IsValveOpen;
}
public void ConnectToInternals(Entity<GasTankComponent> ent)
{
var (owner, component) = ent;
if (component.IsConnected || !CanConnectToInternals(component))
if (component.IsConnected || !CanConnectToInternals(ent))
return;
var internals = GetInternalsComponent(component);
if (internals == null)
TryGetInternalsComp(ent, out var internalsUid, out var internalsComp, ent.Comp.User);
if (internalsUid == null || internalsComp == null)
return;
if (_internals.TryConnectTank((internals.Owner, internals), owner))
component.User = internals.Owner;
if (_internals.TryConnectTank((internalsUid.Value, internalsComp), owner))
component.User = internalsUid.Value;
_actions.SetToggled(component.ToggleActionEntity, component.IsConnected);
@@ -243,7 +243,7 @@ namespace Content.Server.Atmos.EntitySystems
return;
component.ConnectStream = _audioSys.Stop(component.ConnectStream);
component.ConnectStream = _audioSys.PlayPvs(component.ConnectSound, component.Owner)?.Entity;
component.ConnectStream = _audioSys.PlayPvs(component.ConnectSound, owner)?.Entity;
UpdateUserInterface(ent);
}
@@ -251,29 +251,59 @@ namespace Content.Server.Atmos.EntitySystems
public void DisconnectFromInternals(Entity<GasTankComponent> ent)
{
var (owner, component) = ent;
if (component.User == null)
return;
var internals = GetInternalsComponent(component);
TryGetInternalsComp(ent, out var internalsUid, out var internalsComp, component.User);
component.User = null;
_actions.SetToggled(component.ToggleActionEntity, false);
_internals.DisconnectTank(internals);
if (internalsUid != null && internalsComp != null)
_internals.DisconnectTank((internalsUid.Value, internalsComp));
component.DisconnectStream = _audioSys.Stop(component.DisconnectStream);
component.DisconnectStream = _audioSys.PlayPvs(component.DisconnectSound, component.Owner)?.Entity;
component.DisconnectStream = _audioSys.PlayPvs(component.DisconnectSound, owner)?.Entity;
UpdateUserInterface(ent);
}
private InternalsComponent? GetInternalsComponent(GasTankComponent component, EntityUid? owner = null)
/// <summary>
/// Tries to retrieve the internals component of either the gas tank's user,
/// or the gas tank's... containing container
/// </summary>
/// <param name="user">The user of the gas tank</param>
/// <returns>True if internals comp isn't null, false if it is null</returns>
private bool TryGetInternalsComp(Entity<GasTankComponent> ent, out EntityUid? internalsUid, out InternalsComponent? internalsComp, EntityUid? user = null)
{
owner ??= component.User;
if (Deleted(component.Owner))return null;
if (owner != null) return CompOrNull<InternalsComponent>(owner.Value);
return _containers.TryGetContainingContainer(component.Owner, out var container)
? CompOrNull<InternalsComponent>(container.Owner)
: null;
internalsUid = default;
internalsComp = default;
// If the gas tank doesn't exist for whatever reason, don't even bother
if (TerminatingOrDeleted(ent.Owner))
return false;
user ??= ent.Comp.User;
// Check if the gas tank's user actually has the component that allows them to use a gas tank and mask
if (TryComp<InternalsComponent>(user, out var userInternalsComp) && userInternalsComp != null)
{
internalsUid = user;
internalsComp = userInternalsComp;
return true;
}
// Yeah I have no clue what this actually does, I appreciate the lack of comments on the original function
if (_containers.TryGetContainingContainer((ent.Owner, Transform(ent.Owner)), out var container) && container != null)
{
if (TryComp<InternalsComponent>(container.Owner, out var containerInternalsComp) && containerInternalsComp != null)
{
internalsUid = container.Owner;
internalsComp = containerInternalsComp;
return true;
}
}
return false;
}
public void AssumeAir(Entity<GasTankComponent> ent, GasMixture giver)
@@ -321,7 +351,7 @@ namespace Content.Server.Atmos.EntitySystems
if(environment != null)
_atmosphereSystem.Merge(environment, component.Air);
_audioSys.PlayPvs(component.RuptureSound, Transform(component.Owner).Coordinates, AudioParams.Default.WithVariation(0.125f));
_audioSys.PlayPvs(component.RuptureSound, Transform(owner).Coordinates, AudioParams.Default.WithVariation(0.125f));
QueueDel(owner);
return;

View File

@@ -102,7 +102,7 @@ public sealed class InternalsSystem : EntitySystem
{
if (force)
{
DisconnectTank(internals);
DisconnectTank((uid, internals));
return;
}
@@ -199,16 +199,13 @@ public sealed class InternalsSystem : EntitySystem
_alerts.ShowAlert(ent, ent.Comp.InternalsAlert, GetSeverity(ent));
}
public void DisconnectTank(InternalsComponent? component)
public void DisconnectTank(Entity<InternalsComponent> ent)
{
if (component is null)
return;
if (TryComp(ent.Comp.GasTankEntity, out GasTankComponent? tank))
_gasTank.DisconnectFromInternals((ent.Comp.GasTankEntity.Value, tank));
if (TryComp(component.GasTankEntity, out GasTankComponent? tank))
_gasTank.DisconnectFromInternals((component.GasTankEntity.Value, tank));
component.GasTankEntity = null;
_alerts.ShowAlert(component.Owner, component.InternalsAlert, GetSeverity(component));
ent.Comp.GasTankEntity = null;
_alerts.ShowAlert(ent.Owner, ent.Comp.InternalsAlert, GetSeverity(ent.Comp));
}
public bool TryConnectTank(Entity<InternalsComponent> ent, EntityUid tankEntity)
@@ -267,21 +264,21 @@ public sealed class InternalsSystem : EntitySystem
if (_inventory.TryGetSlotEntity(user, "back", out var backEntity, user.Comp2, user.Comp3) &&
TryComp<GasTankComponent>(backEntity, out var backGasTank) &&
_gasTank.CanConnectToInternals(backGasTank))
_gasTank.CanConnectToInternals((backEntity.Value, backGasTank)))
{
return (backEntity.Value, backGasTank);
}
if (_inventory.TryGetSlotEntity(user, "suitstorage", out var entity, user.Comp2, user.Comp3) &&
TryComp<GasTankComponent>(entity, out var gasTank) &&
_gasTank.CanConnectToInternals(gasTank))
_gasTank.CanConnectToInternals((entity.Value, gasTank)))
{
return (entity.Value, gasTank);
}
foreach (var item in _inventory.GetHandOrInventoryEntities((user.Owner, user.Comp1, user.Comp2)))
{
if (TryComp(item, out gasTank) && _gasTank.CanConnectToInternals(gasTank))
if (TryComp(item, out gasTank) && _gasTank.CanConnectToInternals((item, gasTank)))
return (item, gasTank);
}

View File

@@ -40,6 +40,7 @@ namespace Content.Server.Cargo.Systems
SubscribeLocalEvent<CargoOrderConsoleComponent, BoundUIOpenedEvent>(OnOrderUIOpened);
SubscribeLocalEvent<CargoOrderConsoleComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<CargoOrderConsoleComponent, InteractUsingEvent>(OnInteractUsing);
SubscribeLocalEvent<CargoOrderConsoleComponent, BankBalanceUpdatedEvent>(OnOrderBalanceUpdated);
Reset();
}
@@ -315,6 +316,15 @@ namespace Content.Server.Cargo.Systems
#endregion
private void OnOrderBalanceUpdated(Entity<CargoOrderConsoleComponent> ent, ref BankBalanceUpdatedEvent args)
{
if (!_uiSystem.IsUiOpen(ent.Owner, CargoConsoleUiKey.Orders))
return;
UpdateOrderState(ent, args.Station);
}
private void UpdateOrderState(EntityUid consoleUid, EntityUid? station)
{
if (station == null ||

View File

@@ -81,18 +81,18 @@ public sealed partial class CargoSystem : SharedCargoSystem
public void UpdateBankAccount(EntityUid uid, StationBankAccountComponent component, int balanceAdded)
{
component.Balance += balanceAdded;
var query = EntityQueryEnumerator<CargoOrderConsoleComponent>();
var query = EntityQueryEnumerator<BankClientComponent, TransformComponent>();
while (query.MoveNext(out var oUid, out var _))
var ev = new BankBalanceUpdatedEvent(uid, component.Balance);
while (query.MoveNext(out var client, out var comp, out var xform))
{
if (!_uiSystem.IsUiOpen(oUid, CargoConsoleUiKey.Orders))
continue;
var station = _station.GetOwningStation(oUid);
var station = _station.GetOwningStation(client, xform);
if (station != uid)
continue;
UpdateOrderState(oUid, station);
comp.Balance = component.Balance;
Dirty(client, comp);
RaiseLocalEvent(client, ref ev);
}
}
}

View File

@@ -0,0 +1,42 @@
using Content.Server.GameTicking.Events;
using Content.Shared.Clock;
using Content.Shared.Destructible;
using Robust.Server.GameStates;
using Robust.Shared.Random;
namespace Content.Server.Clock;
public sealed class ClockSystem : SharedClockSystem
{
[Dependency] private readonly PvsOverrideSystem _pvsOverride = default!;
[Dependency] private readonly IRobustRandom _robustRandom = default!;
/// <inheritdoc/>
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<RoundStartingEvent>(OnRoundStart);
SubscribeLocalEvent<GlobalTimeManagerComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<ClockComponent, BreakageEventArgs>(OnBreak);
}
private void OnRoundStart(RoundStartingEvent ev)
{
var manager = Spawn();
AddComp<GlobalTimeManagerComponent>(manager);
}
private void OnMapInit(Entity<GlobalTimeManagerComponent> ent, ref MapInitEvent args)
{
ent.Comp.TimeOffset = TimeSpan.FromHours(_robustRandom.NextFloat(0, 24));
_pvsOverride.AddGlobalOverride(ent);
Dirty(ent);
}
private void OnBreak(Entity<ClockComponent> ent, ref BreakageEventArgs args)
{
ent.Comp.StuckTime = GetClockTime(ent);
Dirty(ent, ent.Comp);
}
}

View File

@@ -146,7 +146,7 @@ public sealed partial class DragonSystem : EntitySystem
// cant stack rifts near eachother
foreach (var (_, riftXform) in EntityQuery<DragonRiftComponent, TransformComponent>(true))
{
if (riftXform.Coordinates.InRange(EntityManager, _transform, xform.Coordinates, RiftRange))
if (_transform.InRange(riftXform.Coordinates, xform.Coordinates, RiftRange))
{
_popup.PopupEntity(Loc.GetString("carp-rift-proximity", ("proximity", RiftRange)), uid, uid);
return;

View File

@@ -65,8 +65,8 @@ namespace Content.Server.Engineering.EntitySystems
EntityManager.SpawnEntity(component.Prototype, args.ClickLocation.SnapToGrid(grid));
if (component.RemoveOnInteract && stackComp == null && !((!EntityManager.EntityExists(uid) ? EntityLifeStage.Deleted : EntityManager.GetComponent<MetaDataComponent>(component.Owner).EntityLifeStage) >= EntityLifeStage.Deleted))
EntityManager.DeleteEntity(uid);
if (component.RemoveOnInteract && stackComp == null)
TryQueueDel(uid);
}
}
}

View File

@@ -1,4 +1,4 @@
using System.Linq;
using System.Linq;
using Content.Server.Explosion.Components;
using Content.Server.Explosion.EntitySystems;
using Robust.Shared.Physics.Dynamics;
@@ -42,14 +42,15 @@ public sealed partial class TriggerSystem
private void UpdateTimedCollide(float frameTime)
{
foreach (var (activeTrigger, triggerOnTimedCollide) in EntityQuery<ActiveTriggerOnTimedCollideComponent, TriggerOnTimedCollideComponent>())
var query = EntityQueryEnumerator<ActiveTriggerOnTimedCollideComponent, TriggerOnTimedCollideComponent>();
while (query.MoveNext(out var uid, out _, out var triggerOnTimedCollide))
{
foreach (var (collidingEntity, collidingTimer) in triggerOnTimedCollide.Colliding)
{
triggerOnTimedCollide.Colliding[collidingEntity] += frameTime;
if (collidingTimer > triggerOnTimedCollide.Threshold)
{
RaiseLocalEvent(activeTrigger.Owner, new TriggerEvent(activeTrigger.Owner, collidingEntity), true);
RaiseLocalEvent(uid, new TriggerEvent(uid, collidingEntity), true);
triggerOnTimedCollide.Colliding[collidingEntity] -= triggerOnTimedCollide.Threshold;
}
}

View File

@@ -1,10 +0,0 @@
using Content.Shared.Extinguisher;
using Robust.Shared.GameStates;
namespace Content.Server.Extinguisher;
[RegisterComponent]
[Access(typeof(FireExtinguisherSystem))]
public sealed partial class FireExtinguisherComponent : SharedFireExtinguisherComponent
{
}

View File

@@ -1,139 +0,0 @@
using Content.Server.Chemistry.Containers.EntitySystems;
using Content.Server.Fluids.EntitySystems;
using Content.Server.Popups;
using Content.Shared.Chemistry.Components;
using Content.Shared.Extinguisher;
using Content.Shared.FixedPoint;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
using Content.Shared.Verbs;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
namespace Content.Server.Extinguisher;
public sealed class FireExtinguisherSystem : EntitySystem
{
[Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<FireExtinguisherComponent, ComponentInit>(OnFireExtinguisherInit);
SubscribeLocalEvent<FireExtinguisherComponent, UseInHandEvent>(OnUseInHand);
SubscribeLocalEvent<FireExtinguisherComponent, AfterInteractEvent>(OnAfterInteract);
SubscribeLocalEvent<FireExtinguisherComponent, GetVerbsEvent<InteractionVerb>>(OnGetInteractionVerbs);
SubscribeLocalEvent<FireExtinguisherComponent, SprayAttemptEvent>(OnSprayAttempt);
}
private void OnFireExtinguisherInit(Entity<FireExtinguisherComponent> entity, ref ComponentInit args)
{
if (entity.Comp.HasSafety)
{
UpdateAppearance((entity.Owner, entity.Comp));
}
}
private void OnUseInHand(Entity<FireExtinguisherComponent> entity, ref UseInHandEvent args)
{
if (args.Handled)
return;
ToggleSafety((entity.Owner, entity.Comp), args.User);
args.Handled = true;
}
private void OnAfterInteract(Entity<FireExtinguisherComponent> entity, ref AfterInteractEvent args)
{
if (args.Target == null || !args.CanReach)
{
return;
}
if (args.Handled)
return;
if (entity.Comp.HasSafety && entity.Comp.Safety)
{
_popupSystem.PopupEntity(Loc.GetString("fire-extinguisher-component-safety-on-message"), entity.Owner, args.User);
return;
}
if (args.Target is not { Valid: true } target ||
!_solutionContainerSystem.TryGetDrainableSolution(target, out var targetSoln, out var targetSolution) ||
!_solutionContainerSystem.TryGetRefillableSolution(entity.Owner, out var containerSoln, out var containerSolution))
{
return;
}
args.Handled = true;
// TODO: why is this copy paste shit here just have fire extinguisher cancel transfer when safety is on
var transfer = containerSolution.AvailableVolume;
if (TryComp<SolutionTransferComponent>(entity.Owner, out var solTrans))
{
transfer = solTrans.TransferAmount;
}
transfer = FixedPoint2.Min(transfer, targetSolution.Volume);
if (transfer > 0)
{
var drained = _solutionContainerSystem.Drain(target, targetSoln.Value, transfer);
_solutionContainerSystem.TryAddSolution(containerSoln.Value, drained);
_audio.PlayPvs(entity.Comp.RefillSound, entity.Owner);
_popupSystem.PopupEntity(Loc.GetString("fire-extinguisher-component-after-interact-refilled-message", ("owner", entity.Owner)),
entity.Owner, args.Target.Value);
}
}
private void OnGetInteractionVerbs(Entity<FireExtinguisherComponent> entity, ref GetVerbsEvent<InteractionVerb> args)
{
if (!args.CanAccess || !args.CanInteract)
return;
var user = args.User;
var verb = new InteractionVerb
{
Act = () => ToggleSafety((entity.Owner, entity.Comp), user),
Text = Loc.GetString("fire-extinguisher-component-verb-text"),
};
args.Verbs.Add(verb);
}
private void OnSprayAttempt(Entity<FireExtinguisherComponent> entity, ref SprayAttemptEvent args)
{
if (entity.Comp.HasSafety && entity.Comp.Safety)
{
_popupSystem.PopupEntity(Loc.GetString("fire-extinguisher-component-safety-on-message"), entity, args.User);
args.Cancel();
}
}
private void UpdateAppearance(Entity<FireExtinguisherComponent, AppearanceComponent?> entity)
{
if (!Resolve(entity, ref entity.Comp2, false))
return;
if (entity.Comp1.HasSafety)
{
_appearance.SetData(entity, FireExtinguisherVisuals.Safety, entity.Comp1.Safety, entity.Comp2);
}
}
public void ToggleSafety(Entity<FireExtinguisherComponent?> extinguisher, EntityUid user)
{
if (!Resolve(extinguisher, ref extinguisher.Comp))
return;
extinguisher.Comp.Safety = !extinguisher.Comp.Safety;
_audio.PlayPvs(extinguisher.Comp.SafetySound, extinguisher, AudioParams.Default.WithVariation(0.125f).WithVolume(-4f));
UpdateAppearance((extinguisher.Owner, extinguisher.Comp));
}
}

View File

@@ -1,11 +1,11 @@
using Content.Server.Chemistry.Components;
using Content.Server.Chemistry.Containers.EntitySystems;
using Content.Server.Chemistry.EntitySystems;
using Content.Server.Extinguisher;
using Content.Server.Fluids.Components;
using Content.Server.Gravity;
using Content.Server.Popups;
using Content.Shared.FixedPoint;
using Content.Shared.Fluids;
using Content.Shared.Interaction;
using Content.Shared.Timing;
using Content.Shared.Vapor;
@@ -34,7 +34,7 @@ public sealed class SpraySystem : EntitySystem
{
base.Initialize();
SubscribeLocalEvent<SprayComponent, AfterInteractEvent>(OnAfterInteract, after: new[] { typeof(FireExtinguisherSystem) });
SubscribeLocalEvent<SprayComponent, AfterInteractEvent>(OnAfterInteract);
}
private void OnAfterInteract(Entity<SprayComponent> entity, ref AfterInteractEvent args)
@@ -48,7 +48,7 @@ public sealed class SpraySystem : EntitySystem
return;
var ev = new SprayAttemptEvent(args.User);
RaiseLocalEvent(entity, ev);
RaiseLocalEvent(entity, ref ev);
if (ev.Cancelled)
return;
@@ -148,13 +148,3 @@ public sealed class SpraySystem : EntitySystem
_useDelay.TryResetDelay((entity, useDelay));
}
}
public sealed class SprayAttemptEvent : CancellableEntityEventArgs
{
public EntityUid User;
public SprayAttemptEvent(EntityUid user)
{
User = user;
}
}

View File

@@ -325,7 +325,7 @@ namespace Content.Server.Guardian
if (!guardianComponent.GuardianLoose)
return;
if (!guardianXform.Coordinates.InRange(EntityManager, _transform, hostXform.Coordinates, guardianComponent.DistanceAllowed))
if (!_transform.InRange(guardianXform.Coordinates, hostXform.Coordinates, guardianComponent.DistanceAllowed))
RetractGuardian(hostUid, hostComponent, guardianUid, guardianComponent);
}

View File

@@ -402,7 +402,8 @@ public sealed partial class InstrumentSystem : SharedInstrumentSystem
var trans = transformQuery.GetComponent(uid);
var masterTrans = transformQuery.GetComponent(master);
if (!masterTrans.Coordinates.InRange(EntityManager, _transform, trans.Coordinates, 10f))
if (!_transform.InRange(masterTrans.Coordinates, trans.Coordinates, 10f)
)
{
Clean(uid, instrument);
}

View File

@@ -65,7 +65,7 @@ public sealed class HealthAnalyzerSystem : EntitySystem
//Get distance between health analyzer and the scanned entity
var patientCoordinates = Transform(patient).Coordinates;
if (!patientCoordinates.InRange(EntityManager, _transformSystem, transform.Coordinates, component.MaxScanRange))
if (!_transformSystem.InRange(patientCoordinates, transform.Coordinates, component.MaxScanRange))
{
//Range too far, disable updates
StopAnalyzingEntity((uid, component), patient);

View File

@@ -58,6 +58,7 @@ public sealed class PullController : VirtualController
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly SharedGravitySystem _gravity = default!;
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
/// <summary>
/// If distance between puller and pulled entity lower that this threshold,
@@ -133,8 +134,8 @@ public sealed class PullController : VirtualController
var range = 2f;
var fromUserCoords = coords.WithEntityId(player, EntityManager);
var userCoords = new EntityCoordinates(player, Vector2.Zero);
if (!coords.InRange(EntityManager, TransformSystem, userCoords, range))
if (!_transformSystem.InRange(coords, userCoords, range))
{
var direction = fromUserCoords.Position - userCoords.Position;

View File

@@ -160,9 +160,9 @@ public sealed class HTNPlanJob : Job<HTNPlan>
{
var compound = _protoManager.Index<HTNCompoundPrototype>(compoundId.Task);
for (var i = mtrIndex; i < compound.Branches.Count; i++)
for (; mtrIndex < compound.Branches.Count; mtrIndex++)
{
var branch = compound.Branches[i];
var branch = compound.Branches[mtrIndex];
var isValid = true;
foreach (var con in branch.Preconditions)

View File

@@ -8,12 +8,19 @@ namespace Content.Server.NPC.HTN.Preconditions;
public sealed partial class CoordinatesInRangePrecondition : HTNPrecondition
{
[Dependency] private readonly IEntityManager _entManager = default!;
private SharedTransformSystem _transformSystem = default!;
[DataField("targetKey", required: true)] public string TargetKey = default!;
[DataField("rangeKey", required: true)]
public string RangeKey = default!;
public override void Initialize(IEntitySystemManager sysManager)
{
base.Initialize(sysManager);
_transformSystem = sysManager.GetEntitySystem<SharedTransformSystem>();
}
public override bool IsMet(NPCBlackboard blackboard)
{
if (!blackboard.TryGetValue<EntityCoordinates>(NPCBlackboard.OwnerCoordinates, out var coordinates, _entManager))
@@ -22,6 +29,6 @@ public sealed partial class CoordinatesInRangePrecondition : HTNPrecondition
if (!blackboard.TryGetValue<EntityCoordinates>(TargetKey, out var target, _entManager))
return false;
return coordinates.InRange(_entManager, _entManager.System<SharedTransformSystem>(), target, blackboard.GetValueOrDefault<float>(RangeKey, _entManager));
return _transformSystem.InRange(coordinates, target, blackboard.GetValueOrDefault<float>(RangeKey, _entManager));
}
}

View File

@@ -8,12 +8,19 @@ namespace Content.Server.NPC.HTN.Preconditions;
public sealed partial class CoordinatesNotInRangePrecondition : HTNPrecondition
{
[Dependency] private readonly IEntityManager _entManager = default!;
private SharedTransformSystem _transformSystem = default!;
[DataField("targetKey", required: true)] public string TargetKey = default!;
[DataField("rangeKey", required: true)]
public string RangeKey = default!;
public override void Initialize(IEntitySystemManager sysManager)
{
base.Initialize(sysManager);
_transformSystem = sysManager.GetEntitySystem<SharedTransformSystem>();
}
public override bool IsMet(NPCBlackboard blackboard)
{
if (!blackboard.TryGetValue<EntityCoordinates>(NPCBlackboard.OwnerCoordinates, out var coordinates, _entManager))
@@ -22,7 +29,7 @@ public sealed partial class CoordinatesNotInRangePrecondition : HTNPrecondition
if (!blackboard.TryGetValue<EntityCoordinates>(TargetKey, out var target, _entManager))
return false;
return !coordinates.InRange(_entManager, _entManager.System<SharedTransformSystem>(), target, blackboard.GetValueOrDefault<float>(RangeKey, _entManager));
return !_transformSystem.InRange(coordinates, target, blackboard.GetValueOrDefault<float>(RangeKey, _entManager));
}
}

View File

@@ -8,11 +8,17 @@ namespace Content.Server.NPC.HTN.Preconditions;
public sealed partial class TargetInRangePrecondition : HTNPrecondition
{
[Dependency] private readonly IEntityManager _entManager = default!;
private SharedTransformSystem _transformSystem = default!;
[DataField("targetKey", required: true)] public string TargetKey = default!;
[DataField("rangeKey", required: true)]
public string RangeKey = default!;
public override void Initialize(IEntitySystemManager sysManager)
{
base.Initialize(sysManager);
_transformSystem = sysManager.GetEntitySystem<SharedTransformSystem>();
}
public override bool IsMet(NPCBlackboard blackboard)
{
@@ -23,6 +29,7 @@ public sealed partial class TargetInRangePrecondition : HTNPrecondition
!_entManager.TryGetComponent<TransformComponent>(target, out var targetXform))
return false;
return coordinates.InRange(_entManager, _entManager.System<SharedTransformSystem>(), targetXform.Coordinates, blackboard.GetValueOrDefault<float>(RangeKey, _entManager));
var transformSystem = _entManager.System<SharedTransformSystem>;
return _transformSystem.InRange(coordinates, targetXform.Coordinates, blackboard.GetValueOrDefault<float>(RangeKey, _entManager));
}
}

View File

@@ -89,7 +89,8 @@ public sealed partial class MeleeOperator : HTNOperator, IHtnConditionalShutdown
HTNOperatorStatus status;
if (_entManager.TryGetComponent<NPCMeleeCombatComponent>(owner, out var combat) &&
blackboard.TryGetValue<EntityUid>(TargetKey, out var target, _entManager))
blackboard.TryGetValue<EntityUid>(TargetKey, out var target, _entManager) &&
target != EntityUid.Invalid)
{
combat.Target = target;

View File

@@ -1,6 +1,16 @@
using Content.Shared.Mobs;
namespace Content.Server.NPC.Queries.Considerations;
/// <summary>
/// Goes linearly from 1f to 0f, with 0 damage returning 1f and <see cref=TargetState> damage returning 0f
/// </summary>
public sealed partial class TargetHealthCon : UtilityConsideration
{
/// <summary>
/// Which MobState the consideration returns 0f at, defaults to choosing earliest incapacitating MobState
/// </summary>
[DataField("targetState")]
public MobState TargetState = MobState.Invalid;
}

View File

@@ -143,6 +143,9 @@ public sealed class NPCJukeSystem : EntitySystem
if (!_melee.TryGetWeapon(uid, out var weaponUid, out var weapon))
return;
if (!HasComp<TransformComponent>(melee.Target))
return;
var cdRemaining = weapon.NextAttack - _timing.CurTime;
var attackCooldown = TimeSpan.FromSeconds(1f / _melee.GetAttackRate(weaponUid, uid, weapon));

View File

@@ -7,10 +7,13 @@ using Content.Server.NPC.Queries.Queries;
using Content.Server.Nutrition.Components;
using Content.Server.Nutrition.EntitySystems;
using Content.Server.Storage.Components;
using Content.Shared.Damage;
using Content.Shared.Examine;
using Content.Shared.Fluids.Components;
using Content.Shared.Hands.Components;
using Content.Shared.Inventory;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Content.Shared.NPC.Systems;
using Content.Shared.Nutrition.Components;
@@ -48,6 +51,7 @@ public sealed class NPCUtilitySystem : EntitySystem
[Dependency] private readonly WeldableSystem _weldable = default!;
[Dependency] private readonly ExamineSystemShared _examine = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
[Dependency] private readonly MobThresholdSystem _thresholdSystem = default!;
private EntityQuery<PuddleComponent> _puddleQuery;
private EntityQuery<TransformComponent> _xformQuery;
@@ -293,8 +297,14 @@ public sealed class NPCUtilitySystem : EntitySystem
return (float) ev.Count / ev.Capacity;
}
case TargetHealthCon:
case TargetHealthCon con:
{
if (!TryComp(targetUid, out DamageableComponent? damage))
return 0f;
if (con.TargetState != MobState.Invalid && _thresholdSystem.TryGetPercentageForState(targetUid, con.TargetState, damage.TotalDamage, out var percentage))
return Math.Clamp((float)(1 - percentage), 0f, 1f);
if (_thresholdSystem.TryGetIncapPercentage(targetUid, damage.TotalDamage, out var incapPercentage))
return Math.Clamp((float)(1 - incapPercentage), 0f, 1f);
return 0f;
}
case TargetInLOSCon:

View File

@@ -42,7 +42,7 @@ namespace Content.Server.Nutrition.EntitySystems
if (!isHotEvent.IsHot)
return;
if (TryTransferReagents(entity.Comp, smokable))
if (TryTransferReagents(entity, (entity.Owner, smokable)))
SetSmokableState(entity, SmokableState.Lit, smokable);
args.Handled = true;
}
@@ -62,7 +62,7 @@ namespace Content.Server.Nutrition.EntitySystems
if (!isHotEvent.IsHot)
return;
if (TryTransferReagents(entity.Comp, smokable))
if (TryTransferReagents(entity, (entity.Owner, smokable)))
SetSmokableState(entity, SmokableState.Lit, smokable);
args.Handled = true;
}
@@ -74,15 +74,15 @@ namespace Content.Server.Nutrition.EntitySystems
}
// Convert smokable item into reagents to be smoked
private bool TryTransferReagents(SmokingPipeComponent component, SmokableComponent smokable)
private bool TryTransferReagents(Entity<SmokingPipeComponent> entity, Entity<SmokableComponent> smokable)
{
if (component.BowlSlot.Item == null)
if (entity.Comp.BowlSlot.Item == null)
return false;
EntityUid contents = component.BowlSlot.Item.Value;
EntityUid contents = entity.Comp.BowlSlot.Item.Value;
if (!TryComp<SolutionContainerManagerComponent>(contents, out var reagents) ||
!_solutionContainerSystem.TryGetSolution(smokable.Owner, smokable.Solution, out var pipeSolution, out _))
!_solutionContainerSystem.TryGetSolution(smokable.Owner, smokable.Comp.Solution, out var pipeSolution, out _))
return false;
foreach (var (_, soln) in _solutionContainerSystem.EnumerateSolutions((contents, reagents)))
@@ -93,7 +93,7 @@ namespace Content.Server.Nutrition.EntitySystems
EntityManager.DeleteEntity(contents);
_itemSlotsSystem.SetLock(component.Owner, component.BowlSlot, true); //no inserting more until current runs out
_itemSlotsSystem.SetLock(entity.Owner, entity.Comp.BowlSlot, true); //no inserting more until current runs out
return true;
}

View File

@@ -31,24 +31,24 @@ namespace Content.Server.Nutrition.EntitySystems
/// <summary>
/// Clicked with utensil
/// </summary>
private void OnAfterInteract(EntityUid uid, UtensilComponent component, AfterInteractEvent ev)
private void OnAfterInteract(Entity<UtensilComponent> entity, ref AfterInteractEvent ev)
{
if (ev.Handled || ev.Target == null || !ev.CanReach)
return;
var result = TryUseUtensil(ev.User, ev.Target.Value, component);
var result = TryUseUtensil(ev.User, ev.Target.Value, entity);
ev.Handled = result.Handled;
}
public (bool Success, bool Handled) TryUseUtensil(EntityUid user, EntityUid target, UtensilComponent component)
public (bool Success, bool Handled) TryUseUtensil(EntityUid user, EntityUid target, Entity<UtensilComponent> utensil)
{
if (!EntityManager.TryGetComponent(target, out FoodComponent? food))
return (false, true);
//Prevents food usage with a wrong utensil
if ((food.Utensil & component.Types) == 0)
if ((food.Utensil & utensil.Comp.Types) == 0)
{
_popupSystem.PopupEntity(Loc.GetString("food-system-wrong-utensil", ("food", target), ("utensil", component.Owner)), user, user);
_popupSystem.PopupEntity(Loc.GetString("food-system-wrong-utensil", ("food", target), ("utensil", utensil.Owner)), user, user);
return (false, true);
}

View File

@@ -12,6 +12,7 @@ using Robust.Shared.Random;
using System.Linq;
using System.Text;
using Robust.Server.Player;
using Robust.Shared.Utility;
namespace Content.Server.Objectives;
@@ -180,33 +181,32 @@ public sealed class ObjectivesSystem : SharedObjectivesSystem
}
}
public EntityUid? GetRandomObjective(EntityUid mindId, MindComponent mind, string objectiveGroupProto)
public EntityUid? GetRandomObjective(EntityUid mindId, MindComponent mind, ProtoId<WeightedRandomPrototype> objectiveGroupProto, float maxDifficulty)
{
if (!_prototypeManager.TryIndex<WeightedRandomPrototype>(objectiveGroupProto, out var groups))
if (!_prototypeManager.TryIndex(objectiveGroupProto, out var groupsProto))
{
Log.Error($"Tried to get a random objective, but can't index WeightedRandomPrototype {objectiveGroupProto}");
return null;
}
// TODO replace whatever the fuck this is with a proper objective selection system
// yeah the old 'preventing infinite loops' thing wasn't super elegant either and it mislead people on what exactly it did
var tries = 0;
while (tries < 20)
{
var groupName = groups.Pick(_random);
// Make a copy of the weights so we don't trash the prototype by removing entries
var groups = groupsProto.Weights.ShallowClone();
while (_random.TryPickAndTake(groups, out var groupName))
{
if (!_prototypeManager.TryIndex<WeightedRandomPrototype>(groupName, out var group))
{
Log.Error($"Couldn't index objective group prototype {groupName}");
return null;
}
var proto = group.Pick(_random);
var objective = TryCreateObjective(mindId, mind, proto);
if (objective != null)
return objective;
tries++;
var objectives = group.Weights.ShallowClone();
while (_random.TryPickAndTake(objectives, out var objectiveProto))
{
if (TryCreateObjective((mindId, mind), objectiveProto, out var objective)
&& Comp<ObjectiveComponent>(objective.Value).Difficulty <= maxDifficulty)
return objective;
}
}
return null;

View File

@@ -101,7 +101,7 @@ namespace Content.Server.Pointing.EntitySystems
{
if (HasComp<GhostComponent>(pointer))
{
return Transform(pointer).Coordinates.InRange(EntityManager, _transform, coordinates, 15);
return _transform.InRange(Transform(pointer).Coordinates, coordinates, 15);
}
else
{
@@ -145,8 +145,7 @@ namespace Content.Server.Pointing.EntitySystems
_popup.PopupEntity(Loc.GetString("pointing-system-try-point-cannot-reach"), player, player);
return false;
}
var mapCoordsPointed = coordsPointed.ToMap(EntityManager, _transform);
var mapCoordsPointed = _transform.ToMapCoordinates(coordsPointed);
_rotateToFaceSystem.TryFaceCoordinates(player, mapCoordsPointed.Position);
var arrow = EntityManager.SpawnEntity("PointingArrow", coordsPointed);
@@ -154,7 +153,7 @@ namespace Content.Server.Pointing.EntitySystems
if (TryComp<PointingArrowComponent>(arrow, out var pointing))
{
if (TryComp(player, out TransformComponent? xformPlayer))
pointing.StartPosition = EntityCoordinates.FromMap(arrow, xformPlayer.Coordinates.ToMap(EntityManager, _transform), _transform).Position;
pointing.StartPosition = _transform.ToCoordinates((player, xformPlayer), _transform.ToMapCoordinates(xformPlayer.Coordinates)).Position;
pointing.EndTime = _gameTiming.CurTime + PointDuration;

View File

@@ -8,6 +8,7 @@ using Content.Shared.Radio;
using Content.Shared.Salvage.Magnet;
using Robust.Server.Maps;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
namespace Content.Server.Salvage;
@@ -253,7 +254,8 @@ public sealed partial class SalvageSystem
var seed = data.Comp.Offered[index];
var offering = GetSalvageOffering(seed);
var salvMap = _mapManager.CreateMap();
var salvMap = _mapSystem.CreateMap();
var salvMapXform = Transform(salvMap);
// Set values while awaiting asteroid dungeon if relevant so we can't double-take offers.
data.Comp.ActiveSeed = seed;
@@ -264,8 +266,8 @@ public sealed partial class SalvageSystem
switch (offering)
{
case AsteroidOffering asteroid:
var grid = _mapManager.CreateGrid(salvMap);
await _dungeon.GenerateDungeonAsync(asteroid.DungeonConfig, grid.Owner, grid, Vector2i.Zero, seed);
var grid = _mapManager.CreateGridEntity(salvMap);
await _dungeon.GenerateDungeonAsync(asteroid.DungeonConfig, grid.Owner, grid.Comp, Vector2i.Zero, seed);
break;
case SalvageOffering wreck:
var salvageProto = wreck.SalvageMap;
@@ -275,10 +277,10 @@ public sealed partial class SalvageSystem
Offset = new Vector2(0, 0)
};
if (!_map.TryLoad(salvMap, salvageProto.MapPath.ToString(), out var roots, opts))
if (!_map.TryLoad(salvMapXform.MapID, salvageProto.MapPath.ToString(), out _, opts))
{
Report(magnet, MagnetChannel, "salvage-system-announcement-spawn-debris-disintegrated");
_mapManager.DeleteMap(salvMap);
_mapManager.DeleteMap(salvMapXform.MapID);
return;
}
@@ -288,15 +290,14 @@ public sealed partial class SalvageSystem
}
Box2? bounds = null;
var mapXform = _xformQuery.GetComponent(_mapManager.GetMapEntityId(salvMap));
if (mapXform.ChildCount == 0)
if (salvMapXform.ChildCount == 0)
{
Report(magnet.Owner, MagnetChannel, "salvage-system-announcement-spawn-no-debris-available");
return;
}
var mapChildren = mapXform.ChildEnumerator;
var mapChildren = salvMapXform.ChildEnumerator;
while (mapChildren.MoveNext(out var mapChild))
{
@@ -340,19 +341,25 @@ public sealed partial class SalvageSystem
if (!TryGetSalvagePlacementLocation(mapId, attachedBounds, bounds!.Value, worldAngle, out var spawnLocation, out var spawnAngle))
{
Report(magnet.Owner, MagnetChannel, "salvage-system-announcement-spawn-no-debris-available");
_mapManager.DeleteMap(salvMap);
_mapManager.DeleteMap(salvMapXform.MapID);
return;
}
// I have no idea if we want to return on failure or not
// but I assume trying to set the parent with a null value wouldn't have worked out anyways
if (!_mapSystem.TryGetMap(spawnLocation.MapId, out var spawnUid))
return;
data.Comp.ActiveEntities = null;
mapChildren = mapXform.ChildEnumerator;
mapChildren = salvMapXform.ChildEnumerator;
// It worked, move it into position and cleanup values.
while (mapChildren.MoveNext(out var mapChild))
{
var salvXForm = _xformQuery.GetComponent(mapChild);
var localPos = salvXForm.LocalPosition;
_transform.SetParent(mapChild, salvXForm, _mapManager.GetMapEntityId(spawnLocation.MapId));
_transform.SetParent(mapChild, salvXForm, spawnUid.Value);
_transform.SetWorldPositionRotation(mapChild, spawnLocation.Position + localPos, spawnAngle, salvXForm);
data.Comp.ActiveEntities ??= new List<EntityUid>();
@@ -371,7 +378,7 @@ public sealed partial class SalvageSystem
}
Report(magnet.Owner, MagnetChannel, "salvage-system-announcement-arrived", ("timeLeft", data.Comp.ActiveTime.TotalSeconds));
_mapManager.DeleteMap(salvMap);
_mapManager.DeleteMap(salvMapXform.MapID);
data.Comp.Announced = false;

View File

@@ -27,7 +27,8 @@ public sealed class IdExaminableSystem : EntitySystem
{
Act = () =>
{
var markup = FormattedMessage.FromMarkup(info);
var markup = FormattedMessage.FromMarkupOrThrow(info);
_examineSystem.SendExamineTooltip(args.User, uid, markup, false, false);
},
Text = Loc.GetString("id-examinable-component-verb-text"),

View File

@@ -261,7 +261,7 @@ public sealed class ActionContainerSystem : EntitySystem
if (action.Container == null)
return;
_transform.DetachParentToNull(actionId, Transform(actionId));
_transform.DetachEntity(actionId, Transform(actionId));
// Container removal events should have removed the action from the action container.
// However, just in case the container was already deleted we will still manually clear the container field

View File

@@ -4,6 +4,9 @@ using Robust.Shared.Serialization;
namespace Content.Shared.Actions;
/// <summary>
/// Used on action entities to define an action that triggers when targeting an entity.
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed partial class EntityTargetActionComponent : BaseTargetActionComponent
{
@@ -16,8 +19,15 @@ public sealed partial class EntityTargetActionComponent : BaseTargetActionCompon
[NonSerialized]
public EntityTargetActionEvent? Event;
/// <summary>
/// Determines which entities are valid targets for this action.
/// </summary>
/// <remarks>No whitelist check when null.</remarks>
[DataField("whitelist")] public EntityWhitelist? Whitelist;
/// <summary>
/// Whether this action considers the user as a valid target entity when using this action.
/// </summary>
[DataField("canTargetSelf")] public bool CanTargetSelf = true;
}

View File

@@ -427,7 +427,7 @@ public abstract class SharedActionsSystem : EntitySystem
}
var entityCoordinatesTarget = GetCoordinates(netCoordinatesTarget);
_rotateToFaceSystem.TryFaceCoordinates(user, entityCoordinatesTarget.ToMapPos(EntityManager, _transformSystem));
_rotateToFaceSystem.TryFaceCoordinates(user, _transformSystem.ToMapCoordinates(entityCoordinatesTarget).Position);
if (!ValidateWorldTarget(user, entityCoordinatesTarget, (actionEnt, worldAction)))
return;
@@ -533,7 +533,7 @@ public abstract class SharedActionsSystem : EntitySystem
if (action.Range <= 0)
return true;
return coords.InRange(EntityManager, _transformSystem, Transform(user).Coordinates, action.Range);
return _transformSystem.InRange(coords, Transform(user).Coordinates, action.Range);
}
return _interactionSystem.InRangeUnobstructed(user, coords, range: action.Range);

View File

@@ -3,6 +3,9 @@ using Robust.Shared.Serialization;
namespace Content.Shared.Actions;
/// <summary>
/// Used on action entities to define an action that triggers when targeting an entity coordinate.
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed partial class WorldTargetActionComponent : BaseTargetActionComponent
{

View File

@@ -1,4 +1,5 @@
using Content.Shared.Random;
using Content.Shared.Random.Rules;
using Robust.Shared.Audio;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;

View File

@@ -0,0 +1,26 @@
using Content.Shared.Cargo;
using Robust.Shared.GameStates;
namespace Content.Shared.Cargo.Components;
/// <summary>
/// Makes an entity a client of the station's bank account.
/// When its balance changes it will have <see cref="BankBalanceUpdatedEvent"/> raised on it.
/// Other systems can then use this for logic or to update ui states.
/// </summary>
[RegisterComponent, NetworkedComponent, Access(typeof(SharedCargoSystem))]
[AutoGenerateComponentState]
public sealed partial class BankClientComponent : Component
{
/// <summary>
/// The balance updated for the last station this entity was a part of.
/// </summary>
[DataField, AutoNetworkedField]
public int Balance;
}
/// <summary>
/// Raised on an entity with <see cref="BankClientComponent"/> when the bank's balance is updated.
/// </summary>
[ByRefEvent]
public record struct BankBalanceUpdatedEvent(EntityUid Station, int Balance);

View File

@@ -117,6 +117,7 @@ public sealed class SolutionTransferSystem : EntitySystem
transferAmount = FixedPoint2.Min(transferAmount, maxRefill);
var transferred = Transfer(args.User, target, targetSoln.Value, uid, ownerSoln.Value, transferAmount);
args.Handled = true;
if (transferred > 0)
{
var toTheBrim = ownerRefill.AvailableVolume == 0;
@@ -125,8 +126,6 @@ public sealed class SolutionTransferSystem : EntitySystem
: "comp-solution-transfer-fill-normal";
_popup.PopupClient(Loc.GetString(msg, ("owner", args.Target), ("amount", transferred), ("target", uid)), uid, args.User);
args.Handled = true;
return;
}
}
@@ -143,13 +142,11 @@ public sealed class SolutionTransferSystem : EntitySystem
transferAmount = FixedPoint2.Min(transferAmount, maxRefill);
var transferred = Transfer(args.User, uid, ownerSoln.Value, target, targetSoln.Value, transferAmount);
args.Handled = true;
if (transferred > 0)
{
var message = Loc.GetString("comp-solution-transfer-transfer-solution", ("amount", transferred), ("target", target));
_popup.PopupClient(message, uid, args.User);
args.Handled = true;
}
}
}
@@ -202,6 +199,9 @@ public sealed class SolutionTransferSystem : EntitySystem
var solution = _solution.SplitSolution(source, actualAmount);
_solution.AddSolution(target, solution);
var ev = new SolutionTransferredEvent(sourceEntity, targetEntity, user, actualAmount);
RaiseLocalEvent(targetEntity, ref ev);
_adminLogger.Add(LogType.Action, LogImpact.Medium,
$"{ToPrettyString(user):player} transferred {SharedSolutionContainerSystem.ToPrettyString(solution)} to {ToPrettyString(targetEntity):target}, which now contains {SharedSolutionContainerSystem.ToPrettyString(targetSolution)}");
@@ -225,3 +225,9 @@ public record struct SolutionTransferAttemptEvent(EntityUid From, EntityUid To,
CancelReason = reason;
}
}
/// <summary>
/// Raised on the target entity when a non-zero amount of solution gets transferred.
/// </summary>
[ByRefEvent]
public record struct SolutionTransferredEvent(EntityUid From, EntityUid To, EntityUid User, FixedPoint2 Amount);

View File

@@ -0,0 +1,42 @@
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
namespace Content.Shared.Clock;
[RegisterComponent, NetworkedComponent]
[Access(typeof(SharedClockSystem))]
[AutoGenerateComponentState]
public sealed partial class ClockComponent : Component
{
/// <summary>
/// If not null, this time will be permanently shown.
/// </summary>
[DataField, AutoNetworkedField]
public TimeSpan? StuckTime;
/// <summary>
/// The format in which time is displayed.
/// </summary>
[DataField, AutoNetworkedField]
public ClockType ClockType = ClockType.TwelveHour;
[DataField]
public string HoursBase = "hours_";
[DataField]
public string MinutesBase = "minutes_";
}
[Serializable, NetSerializable]
public enum ClockType : byte
{
TwelveHour,
TwentyFourHour
}
[Serializable, NetSerializable]
public enum ClockVisualLayers : byte
{
HourHand,
MinuteHand
}

View File

@@ -0,0 +1,16 @@
using Robust.Shared.GameStates;
namespace Content.Shared.Clock;
/// <summary>
/// This is used for globally managing the time on-station
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause, Access(typeof(SharedClockSystem))]
public sealed partial class GlobalTimeManagerComponent : Component
{
/// <summary>
/// A fixed random offset, used to fuzz the time between shifts.
/// </summary>
[DataField, AutoPausedField, AutoNetworkedField]
public TimeSpan TimeOffset;
}

View File

@@ -0,0 +1,66 @@
using System.Linq;
using Content.Shared.Examine;
using Content.Shared.GameTicking;
namespace Content.Shared.Clock;
public abstract class SharedClockSystem : EntitySystem
{
[Dependency] private readonly SharedGameTicker _ticker = default!;
/// <inheritdoc/>
public override void Initialize()
{
SubscribeLocalEvent<ClockComponent, ExaminedEvent>(OnExamined);
}
private void OnExamined(Entity<ClockComponent> ent, ref ExaminedEvent args)
{
if (!args.IsInDetailsRange)
return;
args.PushMarkup(Loc.GetString("clock-examine", ("time", GetClockTimeText(ent))));
}
public string GetClockTimeText(Entity<ClockComponent> ent)
{
var time = GetClockTime(ent);
switch (ent.Comp.ClockType)
{
case ClockType.TwelveHour:
return time.ToString(@"h\:mm");
case ClockType.TwentyFourHour:
return time.ToString(@"hh\:mm");
default:
throw new ArgumentOutOfRangeException();
}
}
private TimeSpan GetGlobalTime()
{
return (EntityQuery<GlobalTimeManagerComponent>().FirstOrDefault()?.TimeOffset ?? TimeSpan.Zero) + _ticker.RoundDuration();
}
public TimeSpan GetClockTime(Entity<ClockComponent> ent)
{
var comp = ent.Comp;
if (comp.StuckTime != null)
return comp.StuckTime.Value;
var time = GetGlobalTime();
switch (comp.ClockType)
{
case ClockType.TwelveHour:
var adjustedHours = time.Hours % 12;
if (adjustedHours == 0)
adjustedHours = 12;
return new TimeSpan(adjustedHours, time.Minutes, time.Seconds);
case ClockType.TwentyFourHour:
return time;
default:
throw new ArgumentOutOfRangeException();
}
}
}

View File

@@ -1,3 +1,4 @@
using Content.Shared.Humanoid;
using Content.Shared.Inventory;
using Robust.Shared.GameStates;
@@ -30,4 +31,16 @@ public sealed partial class FoldableClothingComponent : Component
/// </summary>
[DataField]
public string? FoldedHeldPrefix;
/// <summary>
/// Which layers does this hide when Unfolded? See <see cref="HumanoidVisualLayers"/> and <see cref="HideLayerClothingComponent"/>
/// </summary>
[DataField]
public HashSet<HumanoidVisualLayers> UnfoldedHideLayers = new();
/// <summary>
/// Which layers does this hide when folded? See <see cref="HumanoidVisualLayers"/> and <see cref="HideLayerClothingComponent"/>
/// </summary>
[DataField]
public HashSet<HumanoidVisualLayers> FoldedHideLayers = new();
}

View File

@@ -47,6 +47,10 @@ public sealed class FoldableClothingSystem : EntitySystem
if (ent.Comp.FoldedHeldPrefix != null)
_itemSystem.SetHeldPrefix(ent.Owner, ent.Comp.FoldedHeldPrefix, false, itemComp);
if (TryComp<HideLayerClothingComponent>(ent.Owner, out var hideLayerComp))
hideLayerComp.Slots = ent.Comp.FoldedHideLayers;
}
else
{
@@ -59,6 +63,9 @@ public sealed class FoldableClothingSystem : EntitySystem
if (ent.Comp.FoldedHeldPrefix != null)
_itemSystem.SetHeldPrefix(ent.Owner, null, false, itemComp);
if (TryComp<HideLayerClothingComponent>(ent.Owner, out var hideLayerComp))
hideLayerComp.Slots = ent.Comp.UnfoldedHideLayers;
}
}
}

View File

@@ -170,7 +170,7 @@ public abstract partial class SharedDoAfterSystem : EntitySystem
if (args.BreakOnMove && !(!args.BreakOnWeightlessMove && _gravity.IsWeightless(args.User, xform: userXform)))
{
// Whether the user has moved too much from their original position.
if (!userXform.Coordinates.InRange(EntityManager, _transform, doAfter.UserPosition, args.MovementThreshold))
if (!_transform.InRange(userXform.Coordinates, doAfter.UserPosition, args.MovementThreshold))
return true;
// Whether the distance between the user and target(if any) has changed too much.

View File

@@ -1,25 +0,0 @@
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
namespace Content.Shared.Extinguisher;
[NetworkedComponent]
public abstract partial class SharedFireExtinguisherComponent : Component
{
[DataField("refillSound")] public SoundSpecifier RefillSound = new SoundPathSpecifier("/Audio/Effects/refill.ogg");
[DataField("hasSafety")] public bool HasSafety = true;
[DataField("safety")] public bool Safety = true;
[DataField("safetySound")]
public SoundSpecifier SafetySound { get; private set; } = new SoundPathSpecifier("/Audio/Machines/button.ogg");
}
[Serializable, NetSerializable]
public enum FireExtinguisherVisuals : byte
{
Safety
}

View File

@@ -0,0 +1,24 @@
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
namespace Content.Shared.Fluids.Components;
/// <summary>
/// Uses <c>ItemToggle</c> to control safety for a spray item.
/// You can't spray or refill it while safety is on.
/// </summary>
[RegisterComponent, NetworkedComponent, Access(typeof(SpraySafetySystem))]
public sealed partial class SpraySafetyComponent : Component
{
/// <summary>
/// Popup shown when trying to spray or refill with safety on.
/// </summary>
[DataField]
public LocId Popup = "fire-extinguisher-component-safety-on-message";
/// <summary>
/// Sound to play after refilling.
/// </summary>
[DataField]
public SoundSpecifier RefillSound = new SoundPathSpecifier("/Audio/Effects/refill.ogg");
}

View File

@@ -34,3 +34,15 @@ public sealed partial class AbsorbantDoAfterEvent : DoAfterEvent
public override DoAfterEvent Clone() => this;
}
/// <summary>
/// Raised when trying to spray something, for example a fire extinguisher.
/// </summary>
[ByRefEvent]
public record struct SprayAttemptEvent(EntityUid User, bool Cancelled = false)
{
public void Cancel()
{
Cancelled = true;
}
}

View File

@@ -0,0 +1,44 @@
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Fluids.Components;
using Content.Shared.Item.ItemToggle;
using Content.Shared.Popups;
using Robust.Shared.Audio.Systems;
namespace Content.Shared.Fluids;
public sealed class SpraySafetySystem : EntitySystem
{
[Dependency] private readonly ItemToggleSystem _toggle = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SpraySafetyComponent, SolutionTransferAttemptEvent>(OnTransferAttempt);
SubscribeLocalEvent<SpraySafetyComponent, SolutionTransferredEvent>(OnTransferred);
SubscribeLocalEvent<SpraySafetyComponent, SprayAttemptEvent>(OnSprayAttempt);
}
private void OnTransferAttempt(Entity<SpraySafetyComponent> ent, ref SolutionTransferAttemptEvent args)
{
var (uid, comp) = ent;
if (uid == args.To && !_toggle.IsActivated(uid))
args.Cancel(Loc.GetString(comp.Popup));
}
private void OnTransferred(Entity<SpraySafetyComponent> ent, ref SolutionTransferredEvent args)
{
_audio.PlayPredicted(ent.Comp.RefillSound, ent, args.User);
}
private void OnSprayAttempt(Entity<SpraySafetyComponent> ent, ref SprayAttemptEvent args)
{
if (!_toggle.IsActivated(ent.Owner))
{
_popup.PopupEntity(Loc.GetString(ent.Comp.Popup), ent, args.User);
args.Cancel();
}
}
}

View File

@@ -227,7 +227,7 @@ public sealed class FollowerSystem : EntitySystem
if (_netMan.IsClient)
{
_transform.DetachParentToNull(uid, xform);
_transform.DetachEntity(uid, xform);
return;
}

View File

@@ -43,7 +43,7 @@ public sealed partial class HandsComponent : Component
/// </summary>
[DataField]
[ViewVariables(VVAccess.ReadWrite)]
public float BaseThrowspeed { get; set; } = 10f;
public float BaseThrowspeed { get; set; } = 11f;
/// <summary>
/// Distance after which longer throw targets stop increasing throw impulse.

View File

@@ -62,21 +62,22 @@ public sealed class SmartEquipSystem : EntitySystem
if (playerSession.AttachedEntity is not { Valid: true } uid || !Exists(uid))
return;
if (!_actionBlocker.CanInteract(uid, null))
return;
// early out if we don't have any hands or a valid inventory slot
if (!TryComp<HandsComponent>(uid, out var hands) || hands.ActiveHand == null)
return;
var handItem = hands.ActiveHand.HeldEntity;
// can the user interact, and is the item interactable? e.g. virtual items
if (!_actionBlocker.CanInteract(uid, handItem))
return;
if (!TryComp<InventoryComponent>(uid, out var inventory) || !_inventory.HasSlot(uid, equipmentSlot, inventory))
{
_popup.PopupClient(Loc.GetString("smart-equip-missing-equipment-slot", ("slotName", equipmentSlot)), uid, uid);
return;
}
var handItem = hands.ActiveHand.HeldEntity;
// early out if we have an item and cant drop it at all
if (handItem != null && !_hands.CanDropHeld(uid, hands.ActiveHand))
{

View File

@@ -2,6 +2,7 @@ using System.Diagnostics.CodeAnalysis;
using Content.Shared.Hands;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
using Content.Shared.Inventory.Events;
using Content.Shared.Item;
using Content.Shared.Popups;
@@ -43,6 +44,7 @@ public abstract class SharedVirtualItemSystem : EntitySystem
SubscribeLocalEvent<VirtualItemComponent, BeingUnequippedAttemptEvent>(OnBeingUnequippedAttempt);
SubscribeLocalEvent<VirtualItemComponent, BeforeRangedInteractEvent>(OnBeforeRangedInteract);
SubscribeLocalEvent<VirtualItemComponent, GettingInteractedWithAttemptEvent>(OnGettingInteractedWithAttemptEvent);
}
/// <summary>
@@ -72,6 +74,12 @@ public abstract class SharedVirtualItemSystem : EntitySystem
args.Handled = true;
}
private void OnGettingInteractedWithAttemptEvent(Entity<VirtualItemComponent> ent, ref GettingInteractedWithAttemptEvent args)
{
// No interactions with a virtual item, please.
args.Cancelled = true;
}
#region Hands
/// <summary>
@@ -244,7 +252,7 @@ public abstract class SharedVirtualItemSystem : EntitySystem
if (TerminatingOrDeleted(item))
return;
_transformSystem.DetachParentToNull(item, Transform(item));
_transformSystem.DetachEntity(item, Transform(item));
if (_netManager.IsServer)
QueueDel(item);
}

View File

@@ -1,5 +1,5 @@
using System.Diagnostics.CodeAnalysis;
using Content.Shared.Mind;
using Content.Shared.Objectives;
using Content.Shared.Objectives.Components;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
@@ -40,7 +40,7 @@ public abstract class SharedObjectivesSystem : EntitySystem
if (comp.Unique)
{
var proto = _metaQuery.GetComponent(uid).EntityPrototype?.ID;
foreach (var objective in mind.AllObjectives)
foreach (var objective in mind.Objectives)
{
if (_metaQuery.GetComponent(objective).EntityPrototype?.ID == proto)
return false;
@@ -92,7 +92,18 @@ public abstract class SharedObjectivesSystem : EntitySystem
}
/// <summary>
/// Get the title, description, icon and progress of an objective using <see cref="ObjectiveGetProgressEvent"/>.
/// Spawns and assigns an objective for a mind.
/// The objective is not added to the mind's objectives, mind system does that in TryAddObjective.
/// If the objective could not be assigned the objective is deleted and false is returned.
/// </summary>
public bool TryCreateObjective(Entity<MindComponent> mind, EntProtoId proto, [NotNullWhen(true)] out EntityUid? objective)
{
objective = TryCreateObjective(mind.Owner, mind.Comp, proto);
return objective != null;
}
/// <summary>
/// Get the title, description, icon and progress of an objective using <see cref="ObjectiveGetInfoEvent"/>.
/// If any of them are null it is logged and null is returned.
/// </summary>
/// <param name="uid"/>ID of the condition entity</param>

View File

@@ -1,3 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Shared.Dataset;
using Content.Shared.FixedPoint;
@@ -87,6 +88,26 @@ namespace Content.Shared.Random.Helpers
throw new InvalidOperationException("Invalid weighted pick");
}
public static T PickAndTake<T>(this IRobustRandom random, Dictionary<T, float> weights)
where T : notnull
{
var pick = Pick(random, weights);
weights.Remove(pick);
return pick;
}
public static bool TryPickAndTake<T>(this IRobustRandom random, Dictionary<T, float> weights, [NotNullWhen(true)] out T? pick)
where T : notnull
{
if (weights.Count == 0)
{
pick = default;
return false;
}
pick = PickAndTake(random, weights);
return true;
}
public static (string reagent, FixedPoint2 quantity) Pick(this WeightedRandomFillSolutionPrototype prototype, IRobustRandom? random = null)
{
var randomFill = prototype.PickRandomFill(random);

View File

@@ -0,0 +1,12 @@
namespace Content.Shared.Random.Rules;
/// <summary>
/// Always returns true. Used for fallbacks.
/// </summary>
public sealed partial class AlwaysTrueRule : RulesRule
{
public override bool Check(EntityManager entManager, EntityUid uid)
{
return !Inverted;
}
}

View File

@@ -0,0 +1,39 @@
using System.Numerics;
using Robust.Shared.Map;
namespace Content.Shared.Random.Rules;
/// <summary>
/// Returns true if on a grid or in range of one.
/// </summary>
public sealed partial class GridInRangeRule : RulesRule
{
[DataField]
public float Range = 10f;
public override bool Check(EntityManager entManager, EntityUid uid)
{
if (!entManager.TryGetComponent(uid, out TransformComponent? xform))
{
return false;
}
if (xform.GridUid != null)
{
return !Inverted;
}
var transform = entManager.System<SharedTransformSystem>();
var mapManager = IoCManager.Resolve<IMapManager>();
var worldPos = transform.GetWorldPosition(xform);
var gridRange = new Vector2(Range, Range);
foreach (var _ in mapManager.FindGridsIntersecting(xform.MapID, new Box2(worldPos - gridRange, worldPos + gridRange)))
{
return !Inverted;
}
return false;
}
}

View File

@@ -0,0 +1,18 @@
namespace Content.Shared.Random.Rules;
/// <summary>
/// Returns true if the attached entity is in space.
/// </summary>
public sealed partial class InSpaceRule : RulesRule
{
public override bool Check(EntityManager entManager, EntityUid uid)
{
if (!entManager.TryGetComponent(uid, out TransformComponent? xform) ||
xform.GridUid != null)
{
return Inverted;
}
return !Inverted;
}
}

View File

@@ -0,0 +1,77 @@
using Content.Shared.Access;
using Content.Shared.Access.Components;
using Content.Shared.Access.Systems;
using Robust.Shared.Prototypes;
namespace Content.Shared.Random.Rules;
/// <summary>
/// Checks for an entity nearby with the specified access.
/// </summary>
public sealed partial class NearbyAccessRule : RulesRule
{
// This exists because of door electronics contained inside doors.
/// <summary>
/// Does the access entity need to be anchored.
/// </summary>
[DataField]
public bool Anchored = true;
/// <summary>
/// Count of entities that need to be nearby.
/// </summary>
[DataField]
public int Count = 1;
[DataField(required: true)]
public List<ProtoId<AccessLevelPrototype>> Access = new();
[DataField]
public float Range = 10f;
public override bool Check(EntityManager entManager, EntityUid uid)
{
var xformQuery = entManager.GetEntityQuery<TransformComponent>();
if (!xformQuery.TryGetComponent(uid, out var xform) ||
xform.MapUid == null)
{
return false;
}
var transform = entManager.System<SharedTransformSystem>();
var lookup = entManager.System<EntityLookupSystem>();
var reader = entManager.System<AccessReaderSystem>();
var found = false;
var worldPos = transform.GetWorldPosition(xform, xformQuery);
var count = 0;
// TODO: Update this when we get the callback version
var entities = new HashSet<Entity<AccessReaderComponent>>();
lookup.GetEntitiesInRange(xform.MapID, worldPos, Range, entities);
foreach (var comp in entities)
{
if (!reader.AreAccessTagsAllowed(Access, comp) ||
Anchored &&
(!xformQuery.TryGetComponent(comp, out var compXform) ||
!compXform.Anchored))
{
continue;
}
count++;
if (count < Count)
continue;
found = true;
break;
}
if (!found)
return Inverted;
return !Inverted;
}
}

View File

@@ -0,0 +1,71 @@
using Robust.Shared.Prototypes;
namespace Content.Shared.Random.Rules;
public sealed partial class NearbyComponentsRule : RulesRule
{
/// <summary>
/// Does the entity need to be anchored.
/// </summary>
[DataField]
public bool Anchored;
[DataField]
public int Count;
[DataField(required: true)]
public ComponentRegistry Components = default!;
[DataField]
public float Range = 10f;
public override bool Check(EntityManager entManager, EntityUid uid)
{
var inRange = new HashSet<Entity<IComponent>>();
var xformQuery = entManager.GetEntityQuery<TransformComponent>();
if (!xformQuery.TryGetComponent(uid, out var xform) ||
xform.MapUid == null)
{
return false;
}
var transform = entManager.System<SharedTransformSystem>();
var lookup = entManager.System<EntityLookupSystem>();
var found = false;
var worldPos = transform.GetWorldPosition(xform);
var count = 0;
foreach (var compType in Components.Values)
{
inRange.Clear();
lookup.GetEntitiesInRange(compType.Component.GetType(), xform.MapID, worldPos, Range, inRange);
foreach (var comp in inRange)
{
if (Anchored &&
(!xformQuery.TryGetComponent(comp, out var compXform) ||
!compXform.Anchored))
{
continue;
}
count++;
if (count < Count)
continue;
found = true;
break;
}
if (found)
break;
}
if (!found)
return Inverted;
return !Inverted;
}
}

View File

@@ -0,0 +1,58 @@
using Content.Shared.Whitelist;
namespace Content.Shared.Random.Rules;
/// <summary>
/// Checks for entities matching the whitelist in range.
/// This is more expensive than <see cref="NearbyComponentsRule"/> so prefer that!
/// </summary>
public sealed partial class NearbyEntitiesRule : RulesRule
{
/// <summary>
/// How many of the entity need to be nearby.
/// </summary>
[DataField]
public int Count = 1;
[DataField(required: true)]
public EntityWhitelist Whitelist = new();
[DataField]
public float Range = 10f;
public override bool Check(EntityManager entManager, EntityUid uid)
{
if (!entManager.TryGetComponent(uid, out TransformComponent? xform) ||
xform.MapUid == null)
{
return false;
}
var transform = entManager.System<SharedTransformSystem>();
var lookup = entManager.System<EntityLookupSystem>();
var whitelistSystem = entManager.System<EntityWhitelistSystem>();
var found = false;
var worldPos = transform.GetWorldPosition(xform);
var count = 0;
foreach (var ent in lookup.GetEntitiesInRange(xform.MapID, worldPos, Range))
{
if (whitelistSystem.IsWhitelistFail(Whitelist, ent))
continue;
count++;
if (count < Count)
continue;
found = true;
break;
}
if (!found)
return Inverted;
return !Inverted;
}
}

View File

@@ -0,0 +1,79 @@
using Content.Shared.Maps;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics.Components;
using Robust.Shared.Prototypes;
namespace Content.Shared.Random.Rules;
public sealed partial class NearbyTilesPercentRule : RulesRule
{
/// <summary>
/// If there are anchored entities on the tile do we ignore the tile.
/// </summary>
[DataField]
public bool IgnoreAnchored;
[DataField(required: true)]
public float Percent;
[DataField(required: true)]
public List<ProtoId<ContentTileDefinition>> Tiles = new();
[DataField]
public float Range = 10f;
public override bool Check(EntityManager entManager, EntityUid uid)
{
if (!entManager.TryGetComponent(uid, out TransformComponent? xform) ||
!entManager.TryGetComponent<MapGridComponent>(xform.GridUid, out var grid))
{
return false;
}
var transform = entManager.System<SharedTransformSystem>();
var tileDef = IoCManager.Resolve<ITileDefinitionManager>();
var physicsQuery = entManager.GetEntityQuery<PhysicsComponent>();
var tileCount = 0;
var matchingTileCount = 0;
foreach (var tile in grid.GetTilesIntersecting(new Circle(transform.GetWorldPosition(xform),
Range)))
{
// Only consider collidable anchored (for reasons some subfloor stuff has physics but non-collidable)
if (IgnoreAnchored)
{
var gridEnum = grid.GetAnchoredEntitiesEnumerator(tile.GridIndices);
var found = false;
while (gridEnum.MoveNext(out var ancUid))
{
if (!physicsQuery.TryGetComponent(ancUid, out var physics) ||
!physics.CanCollide)
{
continue;
}
found = true;
break;
}
if (found)
continue;
}
tileCount++;
if (!Tiles.Contains(tileDef[tile.Tile.TypeId].ID))
continue;
matchingTileCount++;
}
if (tileCount == 0 || matchingTileCount / (float) tileCount < Percent)
return Inverted;
return !Inverted;
}
}

View File

@@ -0,0 +1,19 @@
namespace Content.Shared.Random.Rules;
/// <summary>
/// Returns true if griduid and mapuid match (AKA on 'planet').
/// </summary>
public sealed partial class OnMapGridRule : RulesRule
{
public override bool Check(EntityManager entManager, EntityUid uid)
{
if (!entManager.TryGetComponent(uid, out TransformComponent? xform) ||
xform.GridUid != xform.MapUid ||
xform.MapUid == null)
{
return Inverted;
}
return !Inverted;
}
}

View File

@@ -0,0 +1,39 @@
using Robust.Shared.Prototypes;
namespace Content.Shared.Random.Rules;
/// <summary>
/// Rules-based item selection. Can be used for any sort of conditional selection
/// Every single condition needs to be true for this to be selected.
/// e.g. "choose maintenance audio if 90% of tiles nearby are maintenance tiles"
/// </summary>
[Prototype("rules")]
public sealed partial class RulesPrototype : IPrototype
{
[IdDataField] public string ID { get; } = string.Empty;
[DataField("rules", required: true)]
public List<RulesRule> Rules = new();
}
[ImplicitDataDefinitionForInheritors]
public abstract partial class RulesRule
{
[DataField]
public bool Inverted;
public abstract bool Check(EntityManager entManager, EntityUid uid);
}
public sealed class RulesSystem : EntitySystem
{
public bool IsTrue(EntityUid uid, RulesPrototype rules)
{
foreach (var rule in rules.Rules)
{
if (!rule.Check(EntityManager, uid))
return false;
}
return true;
}
}

View File

@@ -1,141 +0,0 @@
using Content.Shared.Access;
using Content.Shared.Maps;
using Content.Shared.Whitelist;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
namespace Content.Shared.Random;
/// <summary>
/// Rules-based item selection. Can be used for any sort of conditional selection
/// Every single condition needs to be true for this to be selected.
/// e.g. "choose maintenance audio if 90% of tiles nearby are maintenance tiles"
/// </summary>
[Prototype("rules")]
public sealed partial class RulesPrototype : IPrototype
{
[IdDataField] public string ID { get; } = string.Empty;
[DataField("rules", required: true)]
public List<RulesRule> Rules = new();
}
[ImplicitDataDefinitionForInheritors]
public abstract partial class RulesRule
{
}
/// <summary>
/// Returns true if the attached entity is in space.
/// </summary>
public sealed partial class InSpaceRule : RulesRule
{
}
/// <summary>
/// Checks for entities matching the whitelist in range.
/// This is more expensive than <see cref="NearbyComponentsRule"/> so prefer that!
/// </summary>
public sealed partial class NearbyEntitiesRule : RulesRule
{
/// <summary>
/// How many of the entity need to be nearby.
/// </summary>
[DataField("count")]
public int Count = 1;
[DataField("whitelist", required: true)]
public EntityWhitelist Whitelist = new();
[DataField("range")]
public float Range = 10f;
}
public sealed partial class NearbyTilesPercentRule : RulesRule
{
/// <summary>
/// If there are anchored entities on the tile do we ignore the tile.
/// </summary>
[DataField("ignoreAnchored")] public bool IgnoreAnchored;
[DataField("percent", required: true)]
public float Percent;
[DataField("tiles", required: true, customTypeSerializer:typeof(PrototypeIdListSerializer<ContentTileDefinition>))]
public List<string> Tiles = new();
[DataField("range")]
public float Range = 10f;
}
/// <summary>
/// Always returns true. Used for fallbacks.
/// </summary>
public sealed partial class AlwaysTrueRule : RulesRule
{
}
/// <summary>
/// Returns true if on a grid or in range of one.
/// </summary>
public sealed partial class GridInRangeRule : RulesRule
{
[DataField("range")]
public float Range = 10f;
[DataField("inverted")]
public bool Inverted = false;
}
/// <summary>
/// Returns true if griduid and mapuid match (AKA on 'planet').
/// </summary>
public sealed partial class OnMapGridRule : RulesRule
{
}
/// <summary>
/// Checks for an entity nearby with the specified access.
/// </summary>
public sealed partial class NearbyAccessRule : RulesRule
{
// This exists because of doorelectronics contained inside doors.
/// <summary>
/// Does the access entity need to be anchored.
/// </summary>
[DataField("anchored")]
public bool Anchored = true;
/// <summary>
/// Count of entities that need to be nearby.
/// </summary>
[DataField("count")]
public int Count = 1;
[DataField("access", required: true)]
public List<ProtoId<AccessLevelPrototype>> Access = new();
[DataField("range")]
public float Range = 10f;
}
public sealed partial class NearbyComponentsRule : RulesRule
{
/// <summary>
/// Does the entity need to be anchored.
/// </summary>
[DataField("anchored")]
public bool Anchored;
[DataField("count")] public int Count;
[DataField("components", required: true)]
public ComponentRegistry Components = default!;
[DataField("range")]
public float Range = 10f;
}

View File

@@ -1,247 +0,0 @@
using System.Numerics;
using Content.Shared.Access.Components;
using Content.Shared.Access.Systems;
using Content.Shared.Whitelist;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics.Components;
namespace Content.Shared.Random;
public sealed class RulesSystem : EntitySystem
{
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly ITileDefinitionManager _tileDef = default!;
[Dependency] private readonly AccessReaderSystem _reader = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
public bool IsTrue(EntityUid uid, RulesPrototype rules)
{
var inRange = new HashSet<Entity<IComponent>>();
foreach (var rule in rules.Rules)
{
switch (rule)
{
case AlwaysTrueRule:
break;
case GridInRangeRule griddy:
{
if (!TryComp(uid, out TransformComponent? xform))
{
return false;
}
if (xform.GridUid != null)
{
return !griddy.Inverted;
}
var worldPos = _transform.GetWorldPosition(xform);
var gridRange = new Vector2(griddy.Range, griddy.Range);
foreach (var _ in _mapManager.FindGridsIntersecting(
xform.MapID,
new Box2(worldPos - gridRange, worldPos + gridRange)))
{
return !griddy.Inverted;
}
break;
}
case InSpaceRule:
{
if (!TryComp(uid, out TransformComponent? xform) ||
xform.GridUid != null)
{
return false;
}
break;
}
case NearbyAccessRule access:
{
var xformQuery = GetEntityQuery<TransformComponent>();
if (!xformQuery.TryGetComponent(uid, out var xform) ||
xform.MapUid == null)
{
return false;
}
var found = false;
var worldPos = _transform.GetWorldPosition(xform, xformQuery);
var count = 0;
// TODO: Update this when we get the callback version
var entities = new HashSet<Entity<AccessReaderComponent>>();
_lookup.GetEntitiesInRange(xform.MapID, worldPos, access.Range, entities);
foreach (var comp in entities)
{
if (!_reader.AreAccessTagsAllowed(access.Access, comp) ||
access.Anchored &&
(!xformQuery.TryGetComponent(comp, out var compXform) ||
!compXform.Anchored))
{
continue;
}
count++;
if (count < access.Count)
continue;
found = true;
break;
}
if (!found)
return false;
break;
}
case NearbyComponentsRule nearbyComps:
{
var xformQuery = GetEntityQuery<TransformComponent>();
if (!xformQuery.TryGetComponent(uid, out var xform) ||
xform.MapUid == null)
{
return false;
}
var found = false;
var worldPos = _transform.GetWorldPosition(xform);
var count = 0;
foreach (var compType in nearbyComps.Components.Values)
{
inRange.Clear();
_lookup.GetEntitiesInRange(compType.Component.GetType(), xform.MapID, worldPos, nearbyComps.Range, inRange);
foreach (var comp in inRange)
{
if (nearbyComps.Anchored &&
(!xformQuery.TryGetComponent(comp, out var compXform) ||
!compXform.Anchored))
{
continue;
}
count++;
if (count < nearbyComps.Count)
continue;
found = true;
break;
}
if (found)
break;
}
if (!found)
return false;
break;
}
case NearbyEntitiesRule entity:
{
if (!TryComp(uid, out TransformComponent? xform) ||
xform.MapUid == null)
{
return false;
}
var found = false;
var worldPos = _transform.GetWorldPosition(xform);
var count = 0;
foreach (var ent in _lookup.GetEntitiesInRange(xform.MapID, worldPos, entity.Range))
{
if (_whitelistSystem.IsWhitelistFail(entity.Whitelist, ent))
continue;
count++;
if (count < entity.Count)
continue;
found = true;
break;
}
if (!found)
return false;
break;
}
case NearbyTilesPercentRule tiles:
{
if (!TryComp(uid, out TransformComponent? xform) ||
!TryComp<MapGridComponent>(xform.GridUid, out var grid))
{
return false;
}
var physicsQuery = GetEntityQuery<PhysicsComponent>();
var tileCount = 0;
var matchingTileCount = 0;
foreach (var tile in grid.GetTilesIntersecting(new Circle(_transform.GetWorldPosition(xform),
tiles.Range)))
{
// Only consider collidable anchored (for reasons some subfloor stuff has physics but non-collidable)
if (tiles.IgnoreAnchored)
{
var gridEnum = grid.GetAnchoredEntitiesEnumerator(tile.GridIndices);
var found = false;
while (gridEnum.MoveNext(out var ancUid))
{
if (!physicsQuery.TryGetComponent(ancUid, out var physics) ||
!physics.CanCollide)
{
continue;
}
found = true;
break;
}
if (found)
continue;
}
tileCount++;
if (!tiles.Tiles.Contains(_tileDef[tile.Tile.TypeId].ID))
continue;
matchingTileCount++;
}
if (tileCount == 0 || matchingTileCount / (float) tileCount < tiles.Percent)
return false;
break;
}
case OnMapGridRule:
{
if (!TryComp(uid, out TransformComponent? xform) ||
xform.GridUid != xform.MapUid ||
xform.MapUid == null)
{
return false;
}
break;
}
default:
throw new NotImplementedException();
}
}
return true;
}
}

View File

@@ -4,106 +4,109 @@ using Content.Shared.Whitelist;
using JetBrains.Annotations;
using Robust.Shared.Containers;
namespace Content.Shared.Storage.EntitySystems
namespace Content.Shared.Storage.EntitySystems;
/// <summary>
/// <c>ItemMapperSystem</c> is a system that on each initialization, insertion, removal of an entity from
/// given <see cref="ItemMapperComponent"/> (with appropriate storage attached) will check each stored item to see
/// if its tags/component, and overall quantity match <see cref="ItemMapperComponent.MapLayers"/>.
/// </summary>
[UsedImplicitly]
public abstract class SharedItemMapperSystem : EntitySystem
{
/// <summary>
/// <c>ItemMapperSystem</c> is a system that on each initialization, insertion, removal of an entity from
/// given <see cref="ItemMapperComponent"/> (with appropriate storage attached) will check each stored item to see
/// if its tags/component, and overall quantity match <see cref="ItemMapperComponent.MapLayers"/>.
/// </summary>
[UsedImplicitly]
public abstract class SharedItemMapperSystem : EntitySystem
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
/// <inheritdoc />
public override void Initialize()
{
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
base.Initialize();
SubscribeLocalEvent<ItemMapperComponent, ComponentInit>(InitLayers);
SubscribeLocalEvent<ItemMapperComponent, EntInsertedIntoContainerMessage>(MapperEntityInserted);
SubscribeLocalEvent<ItemMapperComponent, EntRemovedFromContainerMessage>(MapperEntityRemoved);
}
/// <inheritdoc />
public override void Initialize()
private void InitLayers(EntityUid uid, ItemMapperComponent component, ComponentInit args)
{
foreach (var (layerName, val) in component.MapLayers)
{
base.Initialize();
SubscribeLocalEvent<ItemMapperComponent, ComponentInit>(InitLayers);
SubscribeLocalEvent<ItemMapperComponent, EntInsertedIntoContainerMessage>(MapperEntityInserted);
SubscribeLocalEvent<ItemMapperComponent, EntRemovedFromContainerMessage>(MapperEntityRemoved);
val.Layer = layerName;
}
private void InitLayers(EntityUid uid, ItemMapperComponent component, ComponentInit args)
if (EntityManager.TryGetComponent(uid, out AppearanceComponent? appearanceComponent))
{
foreach (var (layerName, val) in component.MapLayers)
{
val.Layer = layerName;
}
if (EntityManager.TryGetComponent(uid, out AppearanceComponent? appearanceComponent))
{
var list = new List<string>(component.MapLayers.Keys);
_appearance.SetData(uid, StorageMapVisuals.InitLayers, new ShowLayerData(list), appearanceComponent);
}
// Ensure appearance is correct with current contained entities.
UpdateAppearance(uid, component);
var list = new List<string>(component.MapLayers.Keys);
_appearance.SetData(uid, StorageMapVisuals.InitLayers, new ShowLayerData(list), appearanceComponent);
}
private void MapperEntityRemoved(EntityUid uid, ItemMapperComponent itemMapper,
EntRemovedFromContainerMessage args)
// Ensure appearance is correct with current contained entities.
UpdateAppearance(uid, component);
}
private void MapperEntityRemoved(EntityUid uid, ItemMapperComponent itemMapper, EntRemovedFromContainerMessage args)
{
if (itemMapper.ContainerWhitelist != null && !itemMapper.ContainerWhitelist.Contains(args.Container.ID))
return;
UpdateAppearance(uid, itemMapper);
}
private void MapperEntityInserted(EntityUid uid,
ItemMapperComponent itemMapper,
EntInsertedIntoContainerMessage args)
{
if (itemMapper.ContainerWhitelist != null && !itemMapper.ContainerWhitelist.Contains(args.Container.ID))
return;
UpdateAppearance(uid, itemMapper);
}
private void UpdateAppearance(EntityUid uid, ItemMapperComponent? itemMapper = null)
{
if (!Resolve(uid, ref itemMapper))
return;
if (EntityManager.TryGetComponent(uid, out AppearanceComponent? appearanceComponent)
&& TryGetLayers(uid, itemMapper, out var containedLayers))
{
if (itemMapper.ContainerWhitelist != null && !itemMapper.ContainerWhitelist.Contains(args.Container.ID))
return;
UpdateAppearance(uid, itemMapper);
}
private void MapperEntityInserted(EntityUid uid, ItemMapperComponent itemMapper,
EntInsertedIntoContainerMessage args)
{
if (itemMapper.ContainerWhitelist != null && !itemMapper.ContainerWhitelist.Contains(args.Container.ID))
return;
UpdateAppearance(uid, itemMapper);
}
private void UpdateAppearance(EntityUid uid, ItemMapperComponent? itemMapper = null)
{
if(!Resolve(uid, ref itemMapper))
return;
if (EntityManager.TryGetComponent(uid, out AppearanceComponent? appearanceComponent)
&& TryGetLayers(uid, itemMapper, out var containedLayers))
{
_appearance.SetData(uid, StorageMapVisuals.LayerChanged, new ShowLayerData(containedLayers), appearanceComponent);
}
}
/// <summary>
/// Method that iterates over storage of the entity in <paramref name="uid"/> and sets <paramref name="containedLayers"/> according to
/// <paramref name="itemMapper"/> definition. It will have O(n*m) time behavior (n - number of entities in container, and m - number of
/// definitions in <paramref name="containedLayers"/>.
/// </summary>
/// <param name="uid">EntityUid used to search the storage</param>
/// <param name="itemMapper">component that contains definition used to map <see cref="Content.Shared.Whitelist.EntityWhitelist">whitelist</see> in
/// <c>mapLayers</c> to string.
/// </param>
/// <param name="containedLayers">list of <paramref name="itemMapper"/> layers that should be visible</param>
/// <returns>false if <c>msg.Container.Owner</c> is not a storage, true otherwise.</returns>
private bool TryGetLayers(EntityUid uid,
ItemMapperComponent itemMapper,
out List<string> showLayers)
{
var containedLayers = _container.GetAllContainers(uid)
.Where(c => itemMapper.ContainerWhitelist?.Contains(c.ID) ?? true).SelectMany(cont => cont.ContainedEntities).ToArray();
var list = new List<string>();
foreach (var mapLayerData in itemMapper.MapLayers.Values)
{
var count = containedLayers.Count(ent => _whitelistSystem.IsWhitelistPass(mapLayerData.Whitelist, ent));
if (count >= mapLayerData.MinCount && count <= mapLayerData.MaxCount)
{
list.Add(mapLayerData.Layer);
}
}
showLayers = list;
return true;
_appearance.SetData(uid,
StorageMapVisuals.LayerChanged,
new ShowLayerData(containedLayers),
appearanceComponent);
}
}
/// <summary>
/// Method that iterates over storage of the entity in <paramref name="uid"/> and sets <paramref name="showLayers"/>
/// according to <paramref name="itemMapper"/> definition. It will have O(n*m) time behavior
/// (n - number of entities in container, and m - number of definitions in <paramref name="showLayers"/>).
/// </summary>
/// <param name="uid">EntityUid used to search the storage</param>
/// <param name="itemMapper">component that contains definition used to map
/// <see cref="EntityWhitelist">Whitelist</see> in <see cref="ItemMapperComponent.MapLayers"/> to string.
/// </param>
/// <param name="showLayers">list of <paramref name="itemMapper"/> layers that should be visible</param>
/// <returns>false if <c>msg.Container.Owner</c> is not a storage, true otherwise.</returns>
private bool TryGetLayers(EntityUid uid, ItemMapperComponent itemMapper, out List<string> showLayers)
{
var containedLayers = _container.GetAllContainers(uid)
.Where(c => itemMapper.ContainerWhitelist?.Contains(c.ID) ?? true)
.SelectMany(cont => cont.ContainedEntities)
.ToArray();
var list = new List<string>();
foreach (var mapLayerData in itemMapper.MapLayers.Values)
{
var count = containedLayers.Count(ent => _whitelistSystem.IsWhitelistPassOrNull(mapLayerData.Whitelist,
ent));
if (count >= mapLayerData.MinCount && count <= mapLayerData.MaxCount)
{
list.Add(mapLayerData.Layer);
}
}
showLayers = list;
return true;
}
}

View File

@@ -26,11 +26,6 @@ public sealed class ThrowingSystem : EntitySystem
public const float PushbackDefault = 2f;
/// <summary>
/// The minimum amount of time an entity needs to be thrown before the timer can be run.
/// Anything below this threshold never enters the air.
/// </summary>
public const float MinFlyTime = 0.15f;
public const float FlyTimePercentage = 0.8f;
private float _frictionModifier;
@@ -168,9 +163,6 @@ public sealed class ThrowingSystem : EntitySystem
var flyTime = direction.Length() / baseThrowSpeed;
if (compensateFriction)
flyTime *= FlyTimePercentage;
if (flyTime < MinFlyTime)
flyTime = 0f;
comp.ThrownTime = _gameTiming.CurTime;
comp.LandTime = comp.ThrownTime + TimeSpan.FromSeconds(flyTime);
comp.PlayLandSound = playSound;

View File

@@ -108,7 +108,7 @@ public abstract partial class SharedGunSystem
else
{
// Similar to below just due to prediction.
TransformSystem.DetachParentToNull(chamberEnt.Value, Transform(chamberEnt.Value));
TransformSystem.DetachEntity(chamberEnt.Value, Transform(chamberEnt.Value));
}
}

Some files were not shown because too many files have changed in this diff Show More