Merge remote-tracking branch 'upstream/master' into ed-15-07-2024-upstream
This commit is contained in:
42
.github/workflows/publish.yml
vendored
42
.github/workflows/publish.yml
vendored
@@ -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
|
||||
|
||||
8
.github/workflows/test-packaging.yml
vendored
8
.github/workflows/test-packaging.yml
vendored
@@ -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 }}"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
26
Content.Client/Clock/ClockSystem.cs
Normal file
26
Content.Client/Clock/ClockSystem.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
using Content.Shared.Extinguisher;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Client.Extinguisher;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class FireExtinguisherComponent : SharedFireExtinguisherComponent;
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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];
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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});
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
133
Content.IntegrationTests/Tests/GameRules/TraitorRuleTest.cs
Normal file
133
Content.IntegrationTests/Tests/GameRules/TraitorRuleTest.cs
Normal 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})";
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 ||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
42
Content.Server/Clock/ClockSystem.cs
Normal file
42
Content.Server/Clock/ClockSystem.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
26
Content.Shared/Cargo/Components/BankClientComponent.cs
Normal file
26
Content.Shared/Cargo/Components/BankClientComponent.cs
Normal 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);
|
||||
@@ -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);
|
||||
|
||||
42
Content.Shared/Clock/ClockComponent.cs
Normal file
42
Content.Shared/Clock/ClockComponent.cs
Normal 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
|
||||
}
|
||||
16
Content.Shared/Clock/GlobalTimeManagerComponent.cs
Normal file
16
Content.Shared/Clock/GlobalTimeManagerComponent.cs
Normal 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;
|
||||
}
|
||||
66
Content.Shared/Clock/SharedClockSystem.cs
Normal file
66
Content.Shared/Clock/SharedClockSystem.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
24
Content.Shared/Fluids/Components/SpraySafetyComponent.cs
Normal file
24
Content.Shared/Fluids/Components/SpraySafetyComponent.cs
Normal 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");
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
44
Content.Shared/Fluids/SpraySafetySystem.cs
Normal file
44
Content.Shared/Fluids/SpraySafetySystem.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -227,7 +227,7 @@ public sealed class FollowerSystem : EntitySystem
|
||||
|
||||
if (_netMan.IsClient)
|
||||
{
|
||||
_transform.DetachParentToNull(uid, xform);
|
||||
_transform.DetachEntity(uid, xform);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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))
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
12
Content.Shared/Random/Rules/AlwaysTrue.cs
Normal file
12
Content.Shared/Random/Rules/AlwaysTrue.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
39
Content.Shared/Random/Rules/GridInRange.cs
Normal file
39
Content.Shared/Random/Rules/GridInRange.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
18
Content.Shared/Random/Rules/InSpace.cs
Normal file
18
Content.Shared/Random/Rules/InSpace.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
77
Content.Shared/Random/Rules/NearbyAccess.cs
Normal file
77
Content.Shared/Random/Rules/NearbyAccess.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
71
Content.Shared/Random/Rules/NearbyComponents.cs
Normal file
71
Content.Shared/Random/Rules/NearbyComponents.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
58
Content.Shared/Random/Rules/NearbyEntities.cs
Normal file
58
Content.Shared/Random/Rules/NearbyEntities.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
79
Content.Shared/Random/Rules/NearbyTilesPercent.cs
Normal file
79
Content.Shared/Random/Rules/NearbyTilesPercent.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
19
Content.Shared/Random/Rules/OnMapGrid.cs
Normal file
19
Content.Shared/Random/Rules/OnMapGrid.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
39
Content.Shared/Random/Rules/RulesSystem.cs
Normal file
39
Content.Shared/Random/Rules/RulesSystem.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user