Station AI (#30944)

* Station AI overlay

* implement

* Bunch of ports

* Fix a heap of bugs and basic scouting

* helldivers

* Shuffle interactions a bit

* navmap stuff

* Revert "navmap stuff"

This reverts commit d1f89dd4be83233e22cf5dd062b2581f3c6da062.

* AI wires implemented

* Fix examines

* Optimise the overlay significantly

* Back to old static

* BUI radial working

* lots of work

* Saving work

* thanks fork

* alright

* pc

* AI upload console

* AI upload

* stuff

* Fix copy-paste shitcode

* AI actions

* navmap work

* Fixes

* first impressions

* a

* reh

* Revert "navmap work"

This reverts commit 6f63fea6e9245e189f368f97be3e32e9b210580e.

# Conflicts:
#	Content.Client/Silicons/StationAi/StationAiOverlay.cs

* OD

* radar

* weh

* Fix examines

* scoop mine eyes

* fixes

* reh

* Optimise

* Final round of optimisations

* Fixes

* fixes
This commit is contained in:
metalgearsloth
2024-08-28 10:57:12 +10:00
committed by GitHub
parent 3a4affd438
commit afd0618a60
153 changed files with 2384 additions and 344 deletions

View File

@@ -19,9 +19,6 @@ public sealed partial class EmotesMenu : RadialMenu
[Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly ISharedPlayerManager _playerManager = default!; [Dependency] private readonly ISharedPlayerManager _playerManager = default!;
private readonly SpriteSystem _spriteSystem;
private readonly EntityWhitelistSystem _whitelistSystem;
public event Action<ProtoId<EmotePrototype>>? OnPlayEmote; public event Action<ProtoId<EmotePrototype>>? OnPlayEmote;
public EmotesMenu() public EmotesMenu()
@@ -29,8 +26,8 @@ public sealed partial class EmotesMenu : RadialMenu
IoCManager.InjectDependencies(this); IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this); RobustXamlLoader.Load(this);
_spriteSystem = _entManager.System<SpriteSystem>(); var spriteSystem = _entManager.System<SpriteSystem>();
_whitelistSystem = _entManager.System<EntityWhitelistSystem>(); var whitelistSystem = _entManager.System<EntityWhitelistSystem>();
var main = FindControl<RadialContainer>("Main"); var main = FindControl<RadialContainer>("Main");
@@ -40,8 +37,8 @@ public sealed partial class EmotesMenu : RadialMenu
var player = _playerManager.LocalSession?.AttachedEntity; var player = _playerManager.LocalSession?.AttachedEntity;
if (emote.Category == EmoteCategory.Invalid || if (emote.Category == EmoteCategory.Invalid ||
emote.ChatTriggers.Count == 0 || emote.ChatTriggers.Count == 0 ||
!(player.HasValue && _whitelistSystem.IsWhitelistPassOrNull(emote.Whitelist, player.Value)) || !(player.HasValue && whitelistSystem.IsWhitelistPassOrNull(emote.Whitelist, player.Value)) ||
_whitelistSystem.IsBlacklistPass(emote.Blacklist, player.Value)) whitelistSystem.IsBlacklistPass(emote.Blacklist, player.Value))
continue; continue;
if (!emote.Available && if (!emote.Available &&
@@ -63,7 +60,7 @@ public sealed partial class EmotesMenu : RadialMenu
{ {
VerticalAlignment = VAlignment.Center, VerticalAlignment = VAlignment.Center,
HorizontalAlignment = HAlignment.Center, HorizontalAlignment = HAlignment.Center,
Texture = _spriteSystem.Frame0(emote.Icon), Texture = spriteSystem.Frame0(emote.Icon),
TextureScale = new Vector2(2f, 2f), TextureScale = new Vector2(2f, 2f),
}; };

View File

@@ -1,10 +1,24 @@
<controls:FancyWindow xmlns="https://spacestation14.io" <controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls" xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
SetSize="800 800" SetSize="800 800"
MinSize="800 64"> MinSize="800 128">
<BoxContainer Orientation="Vertical" VerticalExpand="True">
<!--
<BoxContainer Name="RoleNameBox" Orientation="Vertical" Margin="10">
<Label Name="LoadoutNameLabel" Text="{Loc 'loadout-name-edit-label'}"/>
<PanelContainer HorizontalExpand="True" SetHeight="24">
<PanelContainer.PanelOverride>
<graphics:StyleBoxFlat BackgroundColor="#1B1B1E" />
</PanelContainer.PanelOverride>
<LineEdit Name="RoleNameEdit" ToolTip="{Loc 'loadout-name-edit-tooltip'}" VerticalExpand="True" HorizontalExpand="True"/>
</PanelContainer>
</BoxContainer>
-->
<VerticalTabContainer Name="LoadoutGroupsContainer" <VerticalTabContainer Name="LoadoutGroupsContainer"
VerticalExpand="True" VerticalExpand="True"
HorizontalExpand="True"> HorizontalExpand="True">
</VerticalTabContainer> </VerticalTabContainer>
</BoxContainer>
</controls:FancyWindow> </controls:FancyWindow>

View File

@@ -1,3 +1,4 @@
using System.Numerics;
using Content.Client.UserInterface.Controls; using Content.Client.UserInterface.Controls;
using Content.Shared.Preferences; using Content.Shared.Preferences;
using Content.Shared.Preferences.Loadouts; using Content.Shared.Preferences.Loadouts;
@@ -5,6 +6,7 @@ using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
using Robust.Shared.Player; using Robust.Shared.Player;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Client.Lobby.UI.Loadouts; namespace Content.Client.Lobby.UI.Loadouts;
@@ -24,27 +26,36 @@ public sealed partial class LoadoutWindow : FancyWindow
Profile = profile; Profile = profile;
var protoManager = collection.Resolve<IPrototypeManager>(); var protoManager = collection.Resolve<IPrototypeManager>();
foreach (var group in proto.Groups) // Hide if no groups
if (proto.Groups.Count == 0)
{ {
if (!protoManager.TryIndex(group, out var groupProto)) LoadoutGroupsContainer.Visible = false;
continue; SetSize = Vector2.Zero;
}
if (groupProto.Hidden) else
continue; {
foreach (var group in proto.Groups)
var container = new LoadoutGroupContainer(profile, loadout, protoManager.Index(group), session, collection);
LoadoutGroupsContainer.AddTab(container, Loc.GetString(groupProto.Name));
_groups.Add(container);
container.OnLoadoutPressed += args =>
{ {
OnLoadoutPressed?.Invoke(group, args); if (!protoManager.TryIndex(group, out var groupProto))
}; continue;
container.OnLoadoutUnpressed += args => if (groupProto.Hidden)
{ continue;
OnLoadoutUnpressed?.Invoke(group, args);
}; var container = new LoadoutGroupContainer(profile, loadout, protoManager.Index(group), session, collection);
LoadoutGroupsContainer.AddTab(container, Loc.GetString(groupProto.Name));
_groups.Add(container);
container.OnLoadoutPressed += args =>
{
OnLoadoutPressed?.Invoke(group, args);
};
container.OnLoadoutUnpressed += args =>
{
OnLoadoutUnpressed?.Invoke(group, args);
};
}
} }
} }

View File

@@ -6,7 +6,7 @@ namespace Content.Client.Silicons.Laws.SiliconLawEditUi;
public sealed class SiliconLawEui : BaseEui public sealed class SiliconLawEui : BaseEui
{ {
public readonly EntityManager _entityManager = default!; private readonly EntityManager _entityManager;
private SiliconLawUi _siliconLawUi; private SiliconLawUi _siliconLawUi;
private EntityUid _target; private EntityUid _target;

View File

@@ -0,0 +1,28 @@
using Content.Shared.Silicons.StationAi;
using Robust.Client.UserInterface;
namespace Content.Client.Silicons.StationAi;
public sealed class StationAiBoundUserInterface : BoundUserInterface
{
private StationAiMenu? _menu;
public StationAiBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
}
protected override void Open()
{
base.Open();
_menu = this.CreateWindow<StationAiMenu>();
_menu.Track(Owner);
_menu.OnAiRadial += args =>
{
SendPredictedMessage(new StationAiRadialMessage()
{
Event = args,
});
};
}
}

View File

@@ -0,0 +1,13 @@
<ui:RadialMenu xmlns="https://spacestation14.io"
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
BackButtonStyleClass="RadialMenuBackButton"
CloseButtonStyleClass="RadialMenuCloseButton"
VerticalExpand="True"
HorizontalExpand="True"
MinSize="450 450">
<!-- Main -->
<ui:RadialContainer Name="Main" VerticalExpand="True" HorizontalExpand="True" Radius="64" ReserveSpaceForHiddenChildren="False">
</ui:RadialContainer>
</ui:RadialMenu>

View File

@@ -0,0 +1,128 @@
using System.Numerics;
using Content.Client.UserInterface.Controls;
using Content.Shared.Silicons.StationAi;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Timing;
namespace Content.Client.Silicons.StationAi;
[GenerateTypedNameReferences]
public sealed partial class StationAiMenu : RadialMenu
{
[Dependency] private readonly IClyde _clyde = default!;
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;
public event Action<BaseStationAiAction>? OnAiRadial;
private EntityUid _tracked;
public StationAiMenu()
{
IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this);
}
public void Track(EntityUid owner)
{
_tracked = owner;
if (!_entManager.EntityExists(_tracked))
{
Close();
return;
}
BuildButtons();
UpdatePosition();
}
private void BuildButtons()
{
var ev = new GetStationAiRadialEvent();
_entManager.EventBus.RaiseLocalEvent(_tracked, ref ev);
var main = FindControl<RadialContainer>("Main");
main.DisposeAllChildren();
var sprites = _entManager.System<SpriteSystem>();
foreach (var action in ev.Actions)
{
// TODO: This radial boilerplate is quite annoying
var button = new StationAiMenuButton(action.Event)
{
StyleClasses = { "RadialMenuButton" },
SetSize = new Vector2(64f, 64f),
ToolTip = action.Tooltip != null ? Loc.GetString(action.Tooltip) : null,
};
if (action.Sprite != null)
{
var texture = sprites.Frame0(action.Sprite);
var scale = Vector2.One;
if (texture.Width <= 32)
{
scale *= 2;
}
var tex = new TextureRect
{
VerticalAlignment = VAlignment.Center,
HorizontalAlignment = HAlignment.Center,
Texture = texture,
TextureScale = scale,
};
button.AddChild(tex);
}
button.OnPressed += args =>
{
OnAiRadial?.Invoke(action.Event);
Close();
};
main.AddChild(button);
}
}
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
UpdatePosition();
}
private void UpdatePosition()
{
if (!_entManager.TryGetComponent(_tracked, out TransformComponent? xform))
{
Close();
return;
}
if (!xform.Coordinates.IsValid(_entManager))
{
Close();
return;
}
var coords = _entManager.System<SpriteSystem>().GetSpriteScreenCoordinates((_tracked, null, xform));
if (!coords.IsValid)
{
Close();
return;
}
OpenScreenAt(coords.Position, _clyde);
}
}
public sealed class StationAiMenuButton(BaseStationAiAction action) : RadialMenuTextureButton
{
public BaseStationAiAction Action = action;
}

View File

@@ -4,7 +4,9 @@ using Robust.Client.Graphics;
using Robust.Client.Player; using Robust.Client.Player;
using Robust.Shared.Enums; using Robust.Shared.Enums;
using Robust.Shared.Map.Components; using Robust.Shared.Map.Components;
using Robust.Shared.Physics;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
namespace Content.Client.Silicons.StationAi; namespace Content.Client.Silicons.StationAi;
@@ -12,6 +14,7 @@ public sealed class StationAiOverlay : Overlay
{ {
[Dependency] private readonly IClyde _clyde = default!; [Dependency] private readonly IClyde _clyde = default!;
[Dependency] private readonly IEntityManager _entManager = default!; [Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IPlayerManager _player = default!; [Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IPrototypeManager _proto = default!; [Dependency] private readonly IPrototypeManager _proto = default!;
@@ -22,6 +25,9 @@ public sealed class StationAiOverlay : Overlay
private IRenderTexture? _staticTexture; private IRenderTexture? _staticTexture;
private IRenderTexture? _stencilTexture; private IRenderTexture? _stencilTexture;
private float _updateRate = 1f / 30f;
private float _accumulator;
public StationAiOverlay() public StationAiOverlay()
{ {
IoCManager.InjectDependencies(this); IoCManager.InjectDependencies(this);
@@ -47,19 +53,22 @@ public sealed class StationAiOverlay : Overlay
_entManager.TryGetComponent(playerEnt, out TransformComponent? playerXform); _entManager.TryGetComponent(playerEnt, out TransformComponent? playerXform);
var gridUid = playerXform?.GridUid ?? EntityUid.Invalid; var gridUid = playerXform?.GridUid ?? EntityUid.Invalid;
_entManager.TryGetComponent(gridUid, out MapGridComponent? grid); _entManager.TryGetComponent(gridUid, out MapGridComponent? grid);
_entManager.TryGetComponent(gridUid, out BroadphaseComponent? broadphase);
var invMatrix = args.Viewport.GetWorldToLocalMatrix(); var invMatrix = args.Viewport.GetWorldToLocalMatrix();
_accumulator -= (float) _timing.FrameTime.TotalSeconds;
if (grid != null) if (grid != null && broadphase != null)
{ {
// TODO: Pass in attached entity's grid.
// TODO: Credit OD on the moved to code
// TODO: Call the moved-to code here.
_visibleTiles.Clear();
var lookups = _entManager.System<EntityLookupSystem>(); var lookups = _entManager.System<EntityLookupSystem>();
var xforms = _entManager.System<SharedTransformSystem>(); var xforms = _entManager.System<SharedTransformSystem>();
_entManager.System<StationAiVisionSystem>().GetView((gridUid, grid), worldBounds, _visibleTiles);
if (_accumulator <= 0f)
{
_accumulator = MathF.Max(0f, _accumulator + _updateRate);
_visibleTiles.Clear();
_entManager.System<StationAiVisionSystem>().GetView((gridUid, broadphase, grid), worldBounds, _visibleTiles);
}
var gridMatrix = xforms.GetWorldMatrix(gridUid); var gridMatrix = xforms.GetWorldMatrix(gridUid);
var matty = Matrix3x2.Multiply(gridMatrix, invMatrix); var matty = Matrix3x2.Multiply(gridMatrix, invMatrix);

View File

@@ -0,0 +1,30 @@
using Content.Shared.Doors.Components;
using Content.Shared.Silicons.StationAi;
using Robust.Shared.Utility;
namespace Content.Client.Silicons.StationAi;
public sealed partial class StationAiSystem
{
private void InitializeAirlock()
{
SubscribeLocalEvent<DoorBoltComponent, GetStationAiRadialEvent>(OnDoorBoltGetRadial);
}
private void OnDoorBoltGetRadial(Entity<DoorBoltComponent> ent, ref GetStationAiRadialEvent args)
{
args.Actions.Add(new StationAiRadial()
{
Sprite = ent.Comp.BoltsDown ?
new SpriteSpecifier.Rsi(
new ResPath("/Textures/Structures/Doors/Airlocks/Standard/basic.rsi"), "open") :
new SpriteSpecifier.Rsi(
new ResPath("/Textures/Structures/Doors/Airlocks/Standard/basic.rsi"), "closed"),
Tooltip = ent.Comp.BoltsDown ? Loc.GetString("bolt-open") : Loc.GetString("bolt-close"),
Event = new StationAiBoltEvent()
{
Bolted = !ent.Comp.BoltsDown,
}
});
}
}

View File

@@ -0,0 +1,32 @@
using Content.Shared.Item.ItemToggle.Components;
using Content.Shared.Light.Components;
using Content.Shared.Silicons.StationAi;
using Robust.Shared.Utility;
namespace Content.Client.Silicons.StationAi;
public sealed partial class StationAiSystem
{
// Used for surveillance camera lights
private void InitializePowerToggle()
{
SubscribeLocalEvent<ItemTogglePointLightComponent, GetStationAiRadialEvent>(OnLightGetRadial);
}
private void OnLightGetRadial(Entity<ItemTogglePointLightComponent> ent, ref GetStationAiRadialEvent args)
{
if (!TryComp(ent.Owner, out ItemToggleComponent? toggle))
return;
args.Actions.Add(new StationAiRadial()
{
Tooltip = Loc.GetString("toggle-light"),
Sprite = new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/light.svg.192dpi.png")),
Event = new StationAiLightEvent()
{
Enabled = !toggle.Activated
}
});
}
}

View File

@@ -5,7 +5,7 @@ using Robust.Shared.Player;
namespace Content.Client.Silicons.StationAi; namespace Content.Client.Silicons.StationAi;
public sealed partial class StationAiSystem : EntitySystem public sealed partial class StationAiSystem : SharedStationAiSystem
{ {
[Dependency] private readonly IOverlayManager _overlayMgr = default!; [Dependency] private readonly IOverlayManager _overlayMgr = default!;
[Dependency] private readonly IPlayerManager _player = default!; [Dependency] private readonly IPlayerManager _player = default!;
@@ -15,8 +15,8 @@ public sealed partial class StationAiSystem : EntitySystem
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
// InitializeAirlock(); InitializeAirlock();
// InitializePowerToggle(); InitializePowerToggle();
SubscribeLocalEvent<StationAiOverlayComponent, LocalPlayerAttachedEvent>(OnAiAttached); SubscribeLocalEvent<StationAiOverlayComponent, LocalPlayerAttachedEvent>(OnAiAttached);
SubscribeLocalEvent<StationAiOverlayComponent, LocalPlayerDetachedEvent>(OnAiDetached); SubscribeLocalEvent<StationAiOverlayComponent, LocalPlayerDetachedEvent>(OnAiDetached);

View File

@@ -78,6 +78,7 @@ namespace Content.Client.Verbs
// Get entities // Get entities
List<EntityUid> entities; List<EntityUid> entities;
var examineFlags = LookupFlags.All & ~LookupFlags.Sensors;
// Do we have to do FoV checks? // Do we have to do FoV checks?
if ((visibility & MenuVisibility.NoFov) == 0) if ((visibility & MenuVisibility.NoFov) == 0)
@@ -88,7 +89,7 @@ namespace Content.Client.Verbs
TryComp(player.Value, out ExaminerComponent? examiner); TryComp(player.Value, out ExaminerComponent? examiner);
entities = new(); entities = new();
foreach (var ent in _entityLookup.GetEntitiesInRange(targetPos, EntityMenuLookupSize)) foreach (var ent in _entityLookup.GetEntitiesInRange(targetPos, EntityMenuLookupSize, flags: examineFlags))
{ {
if (_examine.CanExamine(player.Value, targetPos, Predicate, ent, examiner)) if (_examine.CanExamine(player.Value, targetPos, Predicate, ent, examiner))
entities.Add(ent); entities.Add(ent);
@@ -96,7 +97,7 @@ namespace Content.Client.Verbs
} }
else else
{ {
entities = _entityLookup.GetEntitiesInRange(targetPos, EntityMenuLookupSize).ToList(); entities = _entityLookup.GetEntitiesInRange(targetPos, EntityMenuLookupSize, flags: examineFlags).ToList();
} }
if (entities.Count == 0) if (entities.Count == 0)

View File

@@ -36,6 +36,7 @@ using Robust.Shared.Utility;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using Content.Server.Silicons.Laws; using Content.Server.Silicons.Laws;
using Content.Shared.Silicons.Laws;
using Content.Shared.Silicons.Laws.Components; using Content.Shared.Silicons.Laws.Components;
using Robust.Server.Player; using Robust.Server.Player;
using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Components;

View File

@@ -183,10 +183,6 @@ namespace Content.Server.Communications
private bool CanUse(EntityUid user, EntityUid console) private bool CanUse(EntityUid user, EntityUid console)
{ {
// This shouldn't technically be possible because of BUI but don't trust client.
if (!_interaction.InRangeUnobstructed(console, user))
return false;
if (TryComp<AccessReaderComponent>(console, out var accessReaderComponent) && !HasComp<EmaggedComponent>(console)) if (TryComp<AccessReaderComponent>(console, out var accessReaderComponent) && !HasComp<EmaggedComponent>(console))
{ {
return _accessReaderSystem.IsAllowed(user, console, accessReaderComponent); return _accessReaderSystem.IsAllowed(user, console, accessReaderComponent);

View File

@@ -220,7 +220,9 @@ namespace Content.Server.Database
foreach (var role in profile.Loadouts) foreach (var role in profile.Loadouts)
{ {
var loadout = new RoleLoadout(role.RoleName); var loadout = new RoleLoadout(role.RoleName)
{
};
foreach (var group in role.Groups) foreach (var group in role.Groups)
{ {

View File

@@ -1,7 +1,6 @@
using Content.Server.DeviceNetwork.Components; using Content.Server.DeviceNetwork.Components;
using Content.Server.Power.Components; using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems; using Content.Server.Power.EntitySystems;
using Content.Shared.Power.EntitySystems;
namespace Content.Server.DeviceNetwork.Systems; namespace Content.Server.DeviceNetwork.Systems;

View File

@@ -2,7 +2,6 @@ using Content.Server.Light.Components;
using Content.Server.Power.Components; using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems; using Content.Server.Power.EntitySystems;
using Content.Shared.Power; using Content.Shared.Power;
using Content.Shared.Power.Components;
namespace Content.Server.Light.EntitySystems namespace Content.Server.Light.EntitySystems
{ {

View File

@@ -26,7 +26,6 @@ using Robust.Shared.Audio.Systems;
using Content.Shared.Damage.Systems; using Content.Shared.Damage.Systems;
using Content.Shared.Damage.Components; using Content.Shared.Damage.Components;
using Content.Shared.Power; using Content.Shared.Power;
using Content.Shared.Power.Components;
namespace Content.Server.Light.EntitySystems namespace Content.Server.Light.EntitySystems
{ {

View File

@@ -341,13 +341,13 @@ public sealed class MindSystem : SharedMindSystem
} }
} }
public void ControlMob(EntityUid user, EntityUid target) public override void ControlMob(EntityUid user, EntityUid target)
{ {
if (TryComp(user, out ActorComponent? actor)) if (TryComp(user, out ActorComponent? actor))
ControlMob(actor.PlayerSession.UserId, target); ControlMob(actor.PlayerSession.UserId, target);
} }
public void ControlMob(NetUserId user, EntityUid target) public override void ControlMob(NetUserId user, EntityUid target)
{ {
var (mindId, mind) = GetOrCreateMind(user); var (mindId, mind) = GetOrCreateMind(user);

View File

@@ -8,7 +8,6 @@ using Content.Shared.Emp;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.Containers; using Robust.Shared.Containers;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using Content.Shared.Power.Components;
using Content.Shared.Storage.Components; using Content.Shared.Storage.Components;
using Robust.Server.Containers; using Robust.Server.Containers;
using Content.Shared.Whitelist; using Content.Shared.Whitelist;
@@ -64,7 +63,7 @@ internal sealed class ChargerSystem : EntitySystem
} }
else else
{ {
// add how much each item is charged it // add how much each item is charged it
foreach (var contained in container.ContainedEntities) foreach (var contained in container.ContainedEntities)
{ {
if (!TryComp<BatteryComponent>(contained, out var battery)) if (!TryComp<BatteryComponent>(contained, out var battery))
@@ -232,7 +231,7 @@ internal sealed class ChargerSystem : EntitySystem
return CellChargerStatus.Charging; return CellChargerStatus.Charging;
} }
private void TransferPower(EntityUid uid, EntityUid targetEntity, ChargerComponent component, float frameTime) private void TransferPower(EntityUid uid, EntityUid targetEntity, ChargerComponent component, float frameTime)
{ {
if (!TryComp(uid, out ApcPowerReceiverComponent? receiverComponent)) if (!TryComp(uid, out ApcPowerReceiverComponent? receiverComponent))

View File

@@ -5,7 +5,6 @@ using Content.Server.Power.NodeGroups;
using Content.Server.Power.Pow3r; using Content.Server.Power.Pow3r;
using Content.Shared.CCVar; using Content.Shared.CCVar;
using Content.Shared.Power; using Content.Shared.Power;
using Content.Shared.Power.Components;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Shared.Configuration; using Robust.Shared.Configuration;

View File

@@ -11,7 +11,6 @@ using Content.Shared.Atmos;
using Content.Shared.DeviceNetwork; using Content.Shared.DeviceNetwork;
using Content.Shared.Examine; using Content.Shared.Examine;
using Content.Shared.Power; using Content.Shared.Power;
using Content.Shared.Power.Components;
using Content.Shared.Power.Generation.Teg; using Content.Shared.Power.Generation.Teg;
using Content.Shared.Rounding; using Content.Shared.Rounding;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;

View File

@@ -6,7 +6,6 @@ using Content.Server.NodeContainer.Nodes;
using Content.Server.Power.Components; using Content.Server.Power.Components;
using Content.Shared.Atmos; using Content.Shared.Atmos;
using Content.Shared.Power; using Content.Shared.Power;
using Content.Shared.Power.Components;
namespace Content.Server.Power.Generator; namespace Content.Server.Power.Generator;

View File

@@ -1,6 +1,7 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Content.Server.NPC.Pathfinding; using Content.Server.NPC.Pathfinding;
using Content.Shared.Maps; using Content.Shared.Maps;
using Content.Shared.NPC;
using Content.Shared.Procedural; using Content.Shared.Procedural;
using Content.Shared.Procedural.DungeonGenerators; using Content.Shared.Procedural.DungeonGenerators;
using Robust.Shared.Collections; using Robust.Shared.Collections;
@@ -29,7 +30,7 @@ public sealed partial class DungeonJob
var pathfinder = _entManager.System<PathfindingSystem>(); var pathfinder = _entManager.System<PathfindingSystem>();
// Gridcast // Gridcast
pathfinder.GridCast(startTile, position, tile => SharedPathfindingSystem.GridCast(startTile, position, tile =>
{ {
if (!_maps.TryGetTileRef(_gridUid, _grid, tile, out var tileRef) || if (!_maps.TryGetTileRef(_gridUid, _grid, tile, out var tileRef) ||
tileRef.Tile.IsSpace(_tileDefManager)) tileRef.Tile.IsSpace(_tileDefManager))

View File

@@ -5,12 +5,10 @@ using Content.Server.GameTicking;
using Content.Server.Radio.Components; using Content.Server.Radio.Components;
using Content.Server.Roles; using Content.Server.Roles;
using Content.Server.Station.Systems; using Content.Server.Station.Systems;
using Content.Shared.Actions;
using Content.Shared.Administration; using Content.Shared.Administration;
using Content.Shared.Chat; using Content.Shared.Chat;
using Content.Shared.Emag.Components; using Content.Shared.Emag.Components;
using Content.Shared.Emag.Systems; using Content.Shared.Emag.Systems;
using Content.Shared.Examine;
using Content.Shared.Mind; using Content.Shared.Mind;
using Content.Shared.Mind.Components; using Content.Shared.Mind.Components;
using Content.Shared.Roles; using Content.Shared.Roles;
@@ -19,10 +17,10 @@ using Content.Shared.Silicons.Laws.Components;
using Content.Shared.Stunnable; using Content.Shared.Stunnable;
using Content.Shared.Wires; using Content.Shared.Wires;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Shared.Containers;
using Robust.Shared.Player; using Robust.Shared.Player;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Toolshed; using Robust.Shared.Toolshed;
using Robust.Shared.Utility;
namespace Content.Server.Silicons.Laws; namespace Content.Server.Silicons.Laws;
@@ -32,11 +30,9 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
[Dependency] private readonly IChatManager _chatManager = default!; [Dependency] private readonly IChatManager _chatManager = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!; [Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly SharedMindSystem _mind = default!; [Dependency] private readonly SharedMindSystem _mind = default!;
[Dependency] private readonly SharedActionsSystem _actions = default!;
[Dependency] private readonly StationSystem _station = default!; [Dependency] private readonly StationSystem _station = default!;
[Dependency] private readonly UserInterfaceSystem _userInterface = default!; [Dependency] private readonly UserInterfaceSystem _userInterface = default!;
[Dependency] private readonly SharedStunSystem _stunSystem = default!; [Dependency] private readonly SharedStunSystem _stunSystem = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly SharedRoleSystem _roles = default!; [Dependency] private readonly SharedRoleSystem _roles = default!;
/// <inheritdoc/> /// <inheritdoc/>
@@ -44,7 +40,6 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
{ {
base.Initialize(); base.Initialize();
SubscribeLocalEvent<SiliconLawBoundComponent, ComponentShutdown>(OnComponentShutdown);
SubscribeLocalEvent<SiliconLawBoundComponent, MapInitEvent>(OnMapInit); SubscribeLocalEvent<SiliconLawBoundComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<SiliconLawBoundComponent, MindAddedMessage>(OnMindAdded); SubscribeLocalEvent<SiliconLawBoundComponent, MindAddedMessage>(OnMindAdded);
SubscribeLocalEvent<SiliconLawBoundComponent, ToggleLawsScreenEvent>(OnToggleLawsScreen); SubscribeLocalEvent<SiliconLawBoundComponent, ToggleLawsScreenEvent>(OnToggleLawsScreen);
@@ -58,15 +53,8 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
SubscribeLocalEvent<EmagSiliconLawComponent, MindRemovedMessage>(OnEmagMindRemoved); SubscribeLocalEvent<EmagSiliconLawComponent, MindRemovedMessage>(OnEmagMindRemoved);
} }
private void OnComponentShutdown(EntityUid uid, SiliconLawBoundComponent component, ComponentShutdown args)
{
if (component.ViewLawsActionEntity != null)
_actions.RemoveAction(uid, component.ViewLawsActionEntity);
}
private void OnMapInit(EntityUid uid, SiliconLawBoundComponent component, MapInitEvent args) private void OnMapInit(EntityUid uid, SiliconLawBoundComponent component, MapInitEvent args)
{ {
_actions.AddAction(uid, ref component.ViewLawsActionEntity, component.ViewLawsAction);
GetLaws(uid, component); GetLaws(uid, component);
} }
@@ -92,7 +80,7 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
private void OnBoundUIOpened(EntityUid uid, SiliconLawBoundComponent component, BoundUIOpenedEvent args) private void OnBoundUIOpened(EntityUid uid, SiliconLawBoundComponent component, BoundUIOpenedEvent args)
{ {
_entityManager.TryGetComponent<IntrinsicRadioTransmitterComponent>(uid, out var intrinsicRadio); TryComp(uid, out IntrinsicRadioTransmitterComponent? intrinsicRadio);
var radioChannels = intrinsicRadio?.Channels; var radioChannels = intrinsicRadio?.Channels;
var state = new SiliconLawBuiState(GetLaws(uid).Laws, radioChannels); var state = new SiliconLawBuiState(GetLaws(uid).Laws, radioChannels);
@@ -264,9 +252,9 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
/// <summary> /// <summary>
/// Extract all the laws from a lawset's prototype ids. /// Extract all the laws from a lawset's prototype ids.
/// </summary> /// </summary>
public SiliconLawset GetLawset(string lawset) public SiliconLawset GetLawset(ProtoId<SiliconLawsetPrototype> lawset)
{ {
var proto = _prototype.Index<SiliconLawsetPrototype>(lawset); var proto = _prototype.Index(lawset);
var laws = new SiliconLawset() var laws = new SiliconLawset()
{ {
Laws = new List<SiliconLaw>(proto.Laws.Count) Laws = new List<SiliconLaw>(proto.Laws.Count)
@@ -294,6 +282,21 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
component.Lawset.Laws = newLaws; component.Lawset.Laws = newLaws;
NotifyLawsChanged(target); NotifyLawsChanged(target);
} }
protected override void OnUpdaterInsert(Entity<SiliconLawUpdaterComponent> ent, ref EntInsertedIntoContainerMessage args)
{
// TODO: Prediction dump this
if (!TryComp(args.Entity, out SiliconLawProviderComponent? provider))
return;
var lawset = GetLawset(provider.Laws).Laws;
var query = EntityManager.CompRegistryQueryEnumerator(ent.Comp.Components);
while (query.MoveNext(out var update))
{
SetLaws(lawset, update);
}
}
} }
[ToolshedCommand, AdminCommand(AdminFlags.Admin)] [ToolshedCommand, AdminCommand(AdminFlags.Admin)]

View File

@@ -0,0 +1,37 @@
using Content.Server.Wires;
using Content.Shared.Doors;
using Content.Shared.Silicons.StationAi;
using Content.Shared.Wires;
namespace Content.Server.Silicons.StationAi;
/// <summary>
/// Controls whether an AI can interact with the target entity.
/// </summary>
public sealed partial class AiInteractWireAction : ComponentWireAction<StationAiWhitelistComponent>
{
public override string Name { get; set; } = "wire-name-ai-act-light";
public override Color Color { get; set; } = Color.DeepSkyBlue;
public override object StatusKey => AirlockWireStatus.AiControlIndicator;
public override StatusLightState? GetLightState(Wire wire, StationAiWhitelistComponent component)
{
return component.Enabled ? StatusLightState.On : StatusLightState.Off;
}
public override bool Cut(EntityUid user, Wire wire, StationAiWhitelistComponent component)
{
return EntityManager.System<SharedStationAiSystem>()
.SetWhitelistEnabled((component.Owner, component), false, announce: true);
}
public override bool Mend(EntityUid user, Wire wire, StationAiWhitelistComponent component)
{
return EntityManager.System<SharedStationAiSystem>()
.SetWhitelistEnabled((component.Owner, component), true);
}
public override void Pulse(EntityUid user, Wire wire, StationAiWhitelistComponent component)
{
}
}

View File

@@ -0,0 +1,40 @@
using Content.Server.Wires;
using Content.Shared.Doors;
using Content.Shared.Silicons.StationAi;
using Content.Shared.StationAi;
using Content.Shared.Wires;
namespace Content.Server.Silicons.StationAi;
/// <summary>
/// Handles StationAiVision functionality for the attached entity.
/// </summary>
public sealed partial class AiVisionWireAction : ComponentWireAction<StationAiVisionComponent>
{
public override string Name { get; set; } = "wire-name-ai-vision-light";
public override Color Color { get; set; } = Color.DeepSkyBlue;
public override object StatusKey => AirlockWireStatus.AiControlIndicator;
public override StatusLightState? GetLightState(Wire wire, StationAiVisionComponent component)
{
return component.Enabled ? StatusLightState.On : StatusLightState.Off;
}
public override bool Cut(EntityUid user, Wire wire, StationAiVisionComponent component)
{
return EntityManager.System<SharedStationAiSystem>()
.SetVisionEnabled((component.Owner, component), false, announce: true);
}
public override bool Mend(EntityUid user, Wire wire, StationAiVisionComponent component)
{
return EntityManager.System<SharedStationAiSystem>()
.SetVisionEnabled((component.Owner, component), true);
}
public override void Pulse(EntityUid user, Wire wire, StationAiVisionComponent component)
{
// TODO: This should turn it off for a bit
// Need timer cleanup first out of scope.
}
}

View File

@@ -0,0 +1,76 @@
using System.Linq;
using Content.Server.Chat.Managers;
using Content.Server.Chat.Systems;
using Content.Shared.Chat;
using Content.Shared.Silicons.StationAi;
using Content.Shared.StationAi;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Map.Components;
using Robust.Shared.Player;
namespace Content.Server.Silicons.StationAi;
public sealed class StationAiSystem : SharedStationAiSystem
{
[Dependency] private readonly IChatManager _chats = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
private readonly HashSet<Entity<StationAiCoreComponent>> _ais = new();
public override bool SetVisionEnabled(Entity<StationAiVisionComponent> entity, bool enabled, bool announce = false)
{
if (!base.SetVisionEnabled(entity, enabled, announce))
return false;
if (announce)
{
AnnounceSnip(entity.Owner);
}
return true;
}
public override bool SetWhitelistEnabled(Entity<StationAiWhitelistComponent> entity, bool enabled, bool announce = false)
{
if (!base.SetWhitelistEnabled(entity, enabled, announce))
return false;
if (announce)
{
AnnounceSnip(entity.Owner);
}
return true;
}
private void AnnounceSnip(EntityUid entity)
{
var xform = Transform(entity);
if (!TryComp(xform.GridUid, out MapGridComponent? grid))
return;
_ais.Clear();
_lookup.GetChildEntities(xform.GridUid.Value, _ais);
var filter = Filter.Empty();
foreach (var ai in _ais)
{
// TODO: Filter API?
if (TryComp(ai.Owner, out ActorComponent? actorComp))
{
filter.AddPlayer(actorComp.PlayerSession);
}
}
// TEST
// filter = Filter.Broadcast();
// No easy way to do chat notif embeds atm.
var tile = Maps.LocalToTile(xform.GridUid.Value, grid, xform.Coordinates);
var msg = Loc.GetString("ai-wire-snipped", ("coords", tile));
_chats.ChatMessageToMany(ChatChannel.Notifications, msg, msg, entity, false, true, filter.Recipients.Select(o => o.Channel));
// Apparently there's no sound for this.
}
}

View File

@@ -1,7 +1,6 @@
using Content.Server.Power.Components; using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems; using Content.Server.Power.EntitySystems;
using Content.Shared.Power; using Content.Shared.Power;
using Content.Shared.Power.Components;
using Content.Shared.Sound; using Content.Shared.Sound;
using Content.Shared.Sound.Components; using Content.Shared.Sound.Components;

View File

@@ -17,6 +17,7 @@ using Content.Shared.Humanoid.Prototypes;
using Content.Shared.PDA; using Content.Shared.PDA;
using Content.Shared.Preferences; using Content.Shared.Preferences;
using Content.Shared.Preferences.Loadouts; using Content.Shared.Preferences.Loadouts;
using Content.Shared.Preferences.Loadouts.Effects;
using Content.Shared.Random; using Content.Shared.Random;
using Content.Shared.Random.Helpers; using Content.Shared.Random.Helpers;
using Content.Shared.Roles; using Content.Shared.Roles;
@@ -150,6 +151,22 @@ public sealed class StationSpawningSystem : SharedStationSpawningSystem
EntityUid? entity = null) EntityUid? entity = null)
{ {
_prototypeManager.TryIndex(job?.Prototype ?? string.Empty, out var prototype); _prototypeManager.TryIndex(job?.Prototype ?? string.Empty, out var prototype);
RoleLoadout? loadout = null;
// Need to get the loadout up-front to handle names if we use an entity spawn override.
var jobLoadout = LoadoutSystem.GetJobPrototype(prototype?.ID);
if (_prototypeManager.TryIndex(jobLoadout, out RoleLoadoutPrototype? roleProto))
{
profile?.Loadouts.TryGetValue(jobLoadout, out loadout);
// Set to default if not present
if (loadout == null)
{
loadout = new RoleLoadout(jobLoadout);
loadout.SetDefault(profile, _actors.GetSession(entity), _prototypeManager);
}
}
// If we're not spawning a humanoid, we're gonna exit early without doing all the humanoid stuff. // If we're not spawning a humanoid, we're gonna exit early without doing all the humanoid stuff.
if (prototype?.JobEntity != null) if (prototype?.JobEntity != null)
@@ -157,6 +174,13 @@ public sealed class StationSpawningSystem : SharedStationSpawningSystem
DebugTools.Assert(entity is null); DebugTools.Assert(entity is null);
var jobEntity = EntityManager.SpawnEntity(prototype.JobEntity, coordinates); var jobEntity = EntityManager.SpawnEntity(prototype.JobEntity, coordinates);
MakeSentientCommand.MakeSentient(jobEntity, EntityManager); MakeSentientCommand.MakeSentient(jobEntity, EntityManager);
// Make sure custom names get handled, what is gameticker control flow whoopy.
if (loadout != null)
{
EquipRoleName(jobEntity, loadout, roleProto!);
}
DoJobSpecials(job, jobEntity); DoJobSpecials(job, jobEntity);
_identity.QueueIdentityUpdate(jobEntity); _identity.QueueIdentityUpdate(jobEntity);
return jobEntity; return jobEntity;
@@ -188,21 +212,9 @@ public sealed class StationSpawningSystem : SharedStationSpawningSystem
profile = HumanoidCharacterProfile.RandomWithSpecies(speciesId); profile = HumanoidCharacterProfile.RandomWithSpecies(speciesId);
} }
var jobLoadout = LoadoutSystem.GetJobPrototype(prototype?.ID); if (loadout != null)
if (_prototypeManager.TryIndex(jobLoadout, out RoleLoadoutPrototype? roleProto))
{ {
RoleLoadout? loadout = null; EquipRoleLoadout(entity.Value, loadout, roleProto!);
profile?.Loadouts.TryGetValue(jobLoadout, out loadout);
// Set to default if not present
if (loadout == null)
{
loadout = new RoleLoadout(jobLoadout);
loadout.SetDefault(profile, _actors.GetSession(entity), _prototypeManager);
}
EquipRoleLoadout(entity.Value, loadout, roleProto);
} }
if (prototype?.StartingGear != null) if (prototype?.StartingGear != null)

View File

@@ -38,7 +38,7 @@ public sealed class EnergySwordSystem : EntitySystem
if (args.Handled) if (args.Handled)
return; return;
if (!_toolSystem.HasQuality(args.Used, "Pulsing")) if (!_toolSystem.HasQuality(args.Used, SharedToolSystem.PulseQuality))
return; return;
args.Handled = true; args.Handled = true;

View File

@@ -44,7 +44,7 @@ public sealed class ArtifactElectricityTriggerSystem : EntitySystem
if (args.Handled) if (args.Handled)
return; return;
if (!_toolSystem.HasQuality(args.Used, "Pulsing")) if (!_toolSystem.HasQuality(args.Used, SharedToolSystem.PulseQuality))
return; return;
args.Handled = _artifactSystem.TryActivateArtifact(uid, args.User); args.Handled = _artifactSystem.TryActivateArtifact(uid, args.User);

View File

@@ -2,6 +2,7 @@ using Content.Shared.Body.Events;
using Content.Shared.Emoting; using Content.Shared.Emoting;
using Content.Shared.Hands; using Content.Shared.Hands;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Interaction.Components;
using Content.Shared.Interaction.Events; using Content.Shared.Interaction.Events;
using Content.Shared.Item; using Content.Shared.Item;
using Content.Shared.Movement.Components; using Content.Shared.Movement.Components;
@@ -22,9 +23,14 @@ namespace Content.Shared.ActionBlocker
{ {
[Dependency] private readonly SharedContainerSystem _container = default!; [Dependency] private readonly SharedContainerSystem _container = default!;
private EntityQuery<ComplexInteractionComponent> _complexInteractionQuery;
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
_complexInteractionQuery = GetEntityQuery<ComplexInteractionComponent>();
SubscribeLocalEvent<InputMoverComponent, ComponentStartup>(OnMoverStartup); SubscribeLocalEvent<InputMoverComponent, ComponentStartup>(OnMoverStartup);
} }
@@ -53,6 +59,15 @@ namespace Content.Shared.ActionBlocker
return !ev.Cancelled; return !ev.Cancelled;
} }
/// <summary>
/// Checks if a given entity is able to do specific complex interactions.
/// This is used to gate manipulation to general humanoids. If a mouse shouldn't be able to do something, then it's complex.
/// </summary>
public bool CanComplexInteract(EntityUid user)
{
return _complexInteractionQuery.HasComp(user);
}
/// <summary> /// <summary>
/// Raises an event directed at both the user and the target entity to check whether a user is capable of /// Raises an event directed at both the user and the target entity to check whether a user is capable of
/// interacting with this entity. /// interacting with this entity.

View File

@@ -358,34 +358,26 @@ public sealed partial class ClimbSystem : VirtualController
return; return;
} }
if (args.OurFixture.Contacts.Count > 1) foreach (var contact in args.OurFixture.Contacts.Values)
{ {
foreach (var contact in args.OurFixture.Contacts.Values) if (!contact.IsTouching)
continue;
var otherEnt = contact.OtherEnt(uid);
var (otherFixtureId, otherFixture) = contact.OtherFixture(uid);
// TODO: Remove this on engine.
if (args.OtherEntity == otherEnt && args.OtherFixtureId == otherFixtureId)
continue;
if (otherFixture is { Hard: true } &&
_climbableQuery.HasComp(otherEnt))
{ {
if (!contact.IsTouching) return;
continue;
var otherEnt = contact.EntityA;
var otherFixture = contact.FixtureA;
var otherFixtureId = contact.FixtureAId;
if (uid == contact.EntityA)
{
otherEnt = contact.EntityB;
otherFixture = contact.FixtureB;
otherFixtureId = contact.FixtureBId;
}
if (args.OtherEntity == otherEnt && args.OtherFixtureId == otherFixtureId)
continue;
if (otherFixture is { Hard: true } &&
_climbableQuery.HasComp(otherEnt))
{
return;
}
} }
} }
// TODO: Is this even needed anymore?
foreach (var otherFixture in args.OurFixture.Contacts.Keys) foreach (var otherFixture in args.OurFixture.Contacts.Keys)
{ {
// If it's the other fixture then ignore em // If it's the other fixture then ignore em

View File

@@ -1,5 +1,6 @@
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Content.Shared.Tools; using Content.Shared.Tools;
using Content.Shared.Tools.Systems;
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
@@ -13,7 +14,7 @@ namespace Content.Shared.Configurable
public Dictionary<string, string?> Config = new(); public Dictionary<string, string?> Config = new();
[DataField("qualityNeeded", customTypeSerializer: typeof(PrototypeIdSerializer<ToolQualityPrototype>))] [DataField("qualityNeeded", customTypeSerializer: typeof(PrototypeIdSerializer<ToolQualityPrototype>))]
public string QualityNeeded = "Pulsing"; public string QualityNeeded = SharedToolSystem.PulseQuality;
[DataField("validation")] [DataField("validation")]
public Regex Validation = new("^[a-zA-Z0-9 ]*$", RegexOptions.Compiled); public Regex Validation = new("^[a-zA-Z0-9 ]*$", RegexOptions.Compiled);

View File

@@ -8,7 +8,7 @@ namespace Content.Shared.Doors
PowerIndicator, PowerIndicator,
BoltIndicator, BoltIndicator,
BoltLightIndicator, BoltLightIndicator,
AIControlIndicator, AiControlIndicator,
TimingIndicator, TimingIndicator,
SafetyIndicator, SafetyIndicator,
} }

View File

@@ -117,12 +117,25 @@ namespace Content.Shared.Examine
if (EntityManager.GetComponent<TransformComponent>(examiner).MapID != target.MapId) if (EntityManager.GetComponent<TransformComponent>(examiner).MapID != target.MapId)
return false; return false;
return InRangeUnOccluded( // Do target InRangeUnoccluded which has different checks.
_transform.GetMapCoordinates(examiner), if (examined != null)
target, {
GetExaminerRange(examiner), return InRangeUnOccluded(
predicate: predicate, examiner,
ignoreInsideBlocker: true); examined.Value,
GetExaminerRange(examiner),
predicate: predicate,
ignoreInsideBlocker: true);
}
else
{
return InRangeUnOccluded(
examiner,
target,
GetExaminerRange(examiner),
predicate: predicate,
ignoreInsideBlocker: true);
}
} }
/// <summary> /// <summary>
@@ -214,6 +227,14 @@ namespace Content.Shared.Examine
public bool InRangeUnOccluded(EntityUid origin, EntityUid other, float range = ExamineRange, Ignored? predicate = null, bool ignoreInsideBlocker = true) public bool InRangeUnOccluded(EntityUid origin, EntityUid other, float range = ExamineRange, Ignored? predicate = null, bool ignoreInsideBlocker = true)
{ {
var ev = new InRangeOverrideEvent(origin, other);
RaiseLocalEvent(origin, ref ev);
if (ev.Handled)
{
return ev.InRange;
}
var originPos = _transform.GetMapCoordinates(origin); var originPos = _transform.GetMapCoordinates(origin);
var otherPos = _transform.GetMapCoordinates(other); var otherPos = _transform.GetMapCoordinates(other);

View File

@@ -161,6 +161,19 @@ public abstract partial class SharedHandsSystem
return item != null; return item != null;
} }
/// <summary>
/// Gets active hand item if relevant otherwise gets the entity itself.
/// </summary>
public EntityUid GetActiveItemOrSelf(Entity<HandsComponent?> entity)
{
if (!TryGetActiveItem(entity, out var item))
{
return entity.Owner;
}
return item.Value;
}
public Hand? GetActiveHand(Entity<HandsComponent?> entity) public Hand? GetActiveHand(Entity<HandsComponent?> entity)
{ {
if (!Resolve(entity, ref entity.Comp)) if (!Resolve(entity, ref entity.Comp))

View File

@@ -17,6 +17,7 @@ using Content.Shared.Movement.Components;
using Content.Shared.Movement.Pulling.Systems; using Content.Shared.Movement.Pulling.Systems;
using Content.Shared.Physics; using Content.Shared.Physics;
using Content.Shared.Popups; using Content.Shared.Popups;
using Content.Shared.Silicons.StationAi;
using Content.Shared.Storage; using Content.Shared.Storage;
using Content.Shared.Tag; using Content.Shared.Tag;
using Content.Shared.Timing; using Content.Shared.Timing;
@@ -74,7 +75,6 @@ namespace Content.Shared.Interaction
private EntityQuery<WallMountComponent> _wallMountQuery; private EntityQuery<WallMountComponent> _wallMountQuery;
private EntityQuery<UseDelayComponent> _delayQuery; private EntityQuery<UseDelayComponent> _delayQuery;
private EntityQuery<ActivatableUIComponent> _uiQuery; private EntityQuery<ActivatableUIComponent> _uiQuery;
private EntityQuery<ComplexInteractionComponent> _complexInteractionQuery;
private const CollisionGroup InRangeUnobstructedMask = CollisionGroup.Impassable | CollisionGroup.InteractImpassable; private const CollisionGroup InRangeUnobstructedMask = CollisionGroup.Impassable | CollisionGroup.InteractImpassable;
@@ -97,7 +97,6 @@ namespace Content.Shared.Interaction
_wallMountQuery = GetEntityQuery<WallMountComponent>(); _wallMountQuery = GetEntityQuery<WallMountComponent>();
_delayQuery = GetEntityQuery<UseDelayComponent>(); _delayQuery = GetEntityQuery<UseDelayComponent>();
_uiQuery = GetEntityQuery<ActivatableUIComponent>(); _uiQuery = GetEntityQuery<ActivatableUIComponent>();
_complexInteractionQuery = GetEntityQuery<ComplexInteractionComponent>();
SubscribeLocalEvent<BoundUserInterfaceCheckRangeEvent>(HandleUserInterfaceRangeCheck); SubscribeLocalEvent<BoundUserInterfaceCheckRangeEvent>(HandleUserInterfaceRangeCheck);
SubscribeLocalEvent<BoundUserInterfaceMessageAttempt>(OnBoundInterfaceInteractAttempt); SubscribeLocalEvent<BoundUserInterfaceMessageAttempt>(OnBoundInterfaceInteractAttempt);
@@ -165,7 +164,7 @@ namespace Content.Shared.Interaction
return; return;
} }
if (uiComp.RequireHands && !_handsQuery.HasComp(ev.Actor)) if (uiComp.RequiresComplex && !_actionBlockerSystem.CanComplexInteract(ev.Actor))
ev.Cancel(); ev.Cancel();
} }
@@ -440,7 +439,7 @@ namespace Content.Shared.Interaction
public void InteractHand(EntityUid user, EntityUid target) public void InteractHand(EntityUid user, EntityUid target)
{ {
var complexInteractions = SupportsComplexInteractions(user); var complexInteractions = _actionBlockerSystem.CanComplexInteract(user);
if (!complexInteractions) if (!complexInteractions)
{ {
InteractionActivate(user, InteractionActivate(user,
@@ -630,6 +629,14 @@ namespace Content.Shared.Interaction
if (!Resolve(other, ref other.Comp)) if (!Resolve(other, ref other.Comp))
return false; return false;
var ev = new InRangeOverrideEvent(origin, other);
RaiseLocalEvent(origin, ref ev);
if (ev.Handled)
{
return ev.InRange;
}
return InRangeUnobstructed(origin, return InRangeUnobstructed(origin,
other, other,
other.Comp.Coordinates, other.Comp.Coordinates,
@@ -1128,7 +1135,7 @@ namespace Content.Shared.Interaction
// Get list of alt-interact verbs // Get list of alt-interact verbs
var verbs = _verbSystem.GetLocalVerbs(target, user, typeof(AlternativeVerb)); var verbs = _verbSystem.GetLocalVerbs(target, user, typeof(AlternativeVerb));
if (!verbs.Any()) if (verbs.Count == 0)
return false; return false;
_verbSystem.ExecuteVerb(verbs.First(), user, target); _verbSystem.ExecuteVerb(verbs.First(), user, target);
@@ -1182,6 +1189,13 @@ namespace Content.Shared.Interaction
/// </summary> /// </summary>
public bool IsAccessible(Entity<TransformComponent?> user, Entity<TransformComponent?> target) public bool IsAccessible(Entity<TransformComponent?> user, Entity<TransformComponent?> target)
{ {
var ev = new AccessibleOverrideEvent(user, target);
RaiseLocalEvent(user, ref ev);
if (ev.Handled)
return ev.Accessible;
if (_containerSystem.IsInSameOrParentContainer(user, target, out _, out var container)) if (_containerSystem.IsInSameOrParentContainer(user, target, out _, out var container))
return true; return true;
@@ -1324,13 +1338,10 @@ namespace Content.Shared.Interaction
return ev.Handled; return ev.Handled;
} }
/// <summary> [Obsolete("Use ActionBlockerSystem")]
/// Checks if a given entity is able to do specific complex interactions.
/// This is used to gate manipulation to general humanoids. If a mouse shouldn't be able to do something, then it's complex.
/// </summary>
public bool SupportsComplexInteractions(EntityUid user) public bool SupportsComplexInteractions(EntityUid user)
{ {
return _complexInteractionQuery.HasComp(user); return _actionBlockerSystem.CanComplexInteract(user);
} }
} }
@@ -1368,13 +1379,6 @@ namespace Content.Shared.Interaction
public bool Handled => Used != null; public bool Handled => Used != null;
}; };
/// <summary>
/// Raised directed by-ref on an item and a user to determine if interactions can occur.
/// </summary>
/// <param name="Cancelled">Whether the hand interaction should be cancelled.</param>
[ByRefEvent]
public record struct AttemptUseInteractEvent(EntityUid User, EntityUid Used, bool Cancelled = false);
/// <summary> /// <summary>
/// Raised directed by-ref on an item to determine if hand interactions should go through. /// Raised directed by-ref on an item to determine if hand interactions should go through.
/// Defaults to allowing hand interactions to go through. Cancel to force the item to be attacked instead. /// Defaults to allowing hand interactions to go through. Cancel to force the item to be attacked instead.
@@ -1382,4 +1386,32 @@ namespace Content.Shared.Interaction
/// <param name="Cancelled">Whether the hand interaction should be cancelled.</param> /// <param name="Cancelled">Whether the hand interaction should be cancelled.</param>
[ByRefEvent] [ByRefEvent]
public record struct CombatModeShouldHandInteractEvent(bool Cancelled = false); public record struct CombatModeShouldHandInteractEvent(bool Cancelled = false);
/// <summary>
/// Override event raised directed on the user to say the target is accessible.
/// </summary>
/// <param name="User"></param>
/// <param name="Target"></param>
[ByRefEvent]
public record struct AccessibleOverrideEvent(EntityUid User, EntityUid Target)
{
public readonly EntityUid User = User;
public readonly EntityUid Target = Target;
public bool Handled;
public bool Accessible = false;
}
/// <summary>
/// Override event raised directed on a user to check InRangeUnoccluded AND InRangeUnobstructed to the target if you require custom logic.
/// </summary>
[ByRefEvent]
public record struct InRangeOverrideEvent(EntityUid User, EntityUid Target)
{
public readonly EntityUid User = User;
public readonly EntityUid Target = Target;
public bool Handled;
public bool InRange = false;
}
} }

View File

@@ -0,0 +1,13 @@
using Robust.Shared.GameStates;
namespace Content.Shared.Light.Components;
/// <summary>
/// Can activate <see cref="LightOnCollideComponent"/> when collided with.
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed partial class LightOnCollideColliderComponent : Component
{
[DataField]
public string FixtureId = "lightTrigger";
}

View File

@@ -0,0 +1,11 @@
using Robust.Shared.GameStates;
namespace Content.Shared.Light.Components;
/// <summary>
/// Enables / disables pointlight whenever entities are contacting with it
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed partial class LightOnCollideComponent : Component
{
}

View File

@@ -0,0 +1,82 @@
using Content.Shared.Light.Components;
using Robust.Shared.Physics.Events;
using Robust.Shared.Physics.Systems;
namespace Content.Shared.Light.EntitySystems;
public sealed class LightCollideSystem : EntitySystem
{
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly SlimPoweredLightSystem _lights = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<LightOnCollideColliderComponent, PreventCollideEvent>(OnPreventCollide);
SubscribeLocalEvent<LightOnCollideColliderComponent, StartCollideEvent>(OnStart);
SubscribeLocalEvent<LightOnCollideColliderComponent, EndCollideEvent>(OnEnd);
SubscribeLocalEvent<LightOnCollideColliderComponent, ComponentShutdown>(OnCollideShutdown);
}
private void OnCollideShutdown(Entity<LightOnCollideColliderComponent> ent, ref ComponentShutdown args)
{
// TODO: Check this on the event.
if (TerminatingOrDeleted(ent.Owner))
return;
// Regenerate contacts for everything we were colliding with.
var contacts = _physics.GetContacts(ent.Owner);
while (contacts.MoveNext(out var contact))
{
if (!contact.IsTouching)
continue;
var other = contact.OtherEnt(ent.Owner);
if (HasComp<LightOnCollideComponent>(other))
{
_physics.RegenerateContacts(other);
}
}
}
// You may be wondering what de fok this is doing here.
// At the moment there's no easy way to do collision whitelists based on components.
private void OnPreventCollide(Entity<LightOnCollideColliderComponent> ent, ref PreventCollideEvent args)
{
if (!HasComp<LightOnCollideComponent>(args.OtherEntity))
{
args.Cancelled = true;
}
}
private void OnEnd(Entity<LightOnCollideColliderComponent> ent, ref EndCollideEvent args)
{
if (args.OurFixtureId != ent.Comp.FixtureId)
return;
if (!HasComp<LightOnCollideComponent>(args.OtherEntity))
return;
// TODO: Engine bug IsTouching box2d yay.
var contacts = _physics.GetTouchingContacts(args.OtherEntity) - 1;
if (contacts > 0)
return;
_lights.SetEnabled(args.OtherEntity, false);
}
private void OnStart(Entity<LightOnCollideColliderComponent> ent, ref StartCollideEvent args)
{
if (args.OurFixtureId != ent.Comp.FixtureId)
return;
if (!HasComp<LightOnCollideComponent>(args.OtherEntity))
return;
_lights.SetEnabled(args.OtherEntity, true);
}
}

View File

@@ -1,6 +1,5 @@
using Content.Shared.Light.Components; using Content.Shared.Light.Components;
using Content.Shared.Power; using Content.Shared.Power;
using Content.Shared.Power.Components;
using Content.Shared.Power.EntitySystems; using Content.Shared.Power.EntitySystems;
namespace Content.Shared.Light.EntitySystems; namespace Content.Shared.Light.EntitySystems;

View File

@@ -315,6 +315,10 @@ public abstract class SharedMindSystem : EntitySystem
{ {
} }
public virtual void ControlMob(EntityUid user, EntityUid target) {}
public virtual void ControlMob(NetUserId user, EntityUid target) {}
/// <summary> /// <summary>
/// Tries to create and add an objective from its prototype id. /// Tries to create and add an objective from its prototype id.
/// </summary> /// </summary>

View File

@@ -1,8 +1,8 @@
namespace Content.Server.NPC.Pathfinding; namespace Content.Shared.NPC;
public sealed partial class PathfindingSystem public abstract partial class SharedPathfindingSystem
{ {
public void GridCast(Vector2i start, Vector2i end, Vector2iCallback callback) public static void GridCast(Vector2i start, Vector2i end, Vector2iCallback callback)
{ {
// https://gist.github.com/Pyr3z/46884d67641094d6cf353358566db566 // https://gist.github.com/Pyr3z/46884d67641094d6cf353358566db566
// declare all locals at the top so it's obvious how big the footprint is // declare all locals at the top so it's obvious how big the footprint is

View File

@@ -2,7 +2,7 @@ using System.Numerics;
namespace Content.Shared.NPC; namespace Content.Shared.NPC;
public abstract class SharedPathfindingSystem : EntitySystem public abstract partial class SharedPathfindingSystem : EntitySystem
{ {
/// <summary> /// <summary>
/// This is equivalent to agent radii for navmeshes. In our case it's preferable that things are cleanly /// This is equivalent to agent radii for navmeshes. In our case it's preferable that things are cleanly
@@ -37,4 +37,31 @@ public abstract class SharedPathfindingSystem : EntitySystem
var ab = Vector2.Abs(diff); var ab = Vector2.Abs(diff);
return ab.X + ab.Y + (1.41f - 2) * Math.Min(ab.X, ab.Y); return ab.X + ab.Y + (1.41f - 2) * Math.Min(ab.X, ab.Y);
} }
public static IEnumerable<Vector2i> GetTileOutline(Vector2i center, float radius)
{
// https://www.redblobgames.com/grids/circle-drawing/
var vecCircle = center + Vector2.One / 2f;
for (var r = 0; r <= Math.Floor(radius * MathF.Sqrt(0.5f)); r++)
{
var d = MathF.Floor(MathF.Sqrt(radius * radius - r * r));
yield return new Vector2(vecCircle.X - d, vecCircle.Y + r).Floored();
yield return new Vector2(vecCircle.X + d, vecCircle.Y + r).Floored();
yield return new Vector2(vecCircle.X - d, vecCircle.Y - r).Floored();
yield return new Vector2(vecCircle.X + d, vecCircle.Y - r).Floored();
yield return new Vector2(vecCircle.X + r, vecCircle.Y - d).Floored();
yield return new Vector2(vecCircle.X + r, vecCircle.Y + d).Floored();
yield return new Vector2(vecCircle.X - r, vecCircle.Y - d).Floored();
yield return new Vector2(vecCircle.X - r, vecCircle.Y + d).Floored();
}
}
} }

View File

@@ -1,3 +1,4 @@
using Content.Shared.Dataset;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
namespace Content.Shared.Preferences.Loadouts; namespace Content.Shared.Preferences.Loadouts;
@@ -15,10 +16,17 @@ public sealed partial class RoleLoadoutPrototype : IPrototype
[IdDataField] [IdDataField]
public string ID { get; } = string.Empty; public string ID { get; } = string.Empty;
/// <summary>
/// Should we use a random name for this loadout?
/// </summary>
[DataField]
public ProtoId<DatasetPrototype>? NameDataset;
// Not required so people can set their names.
/// <summary> /// <summary>
/// Groups that comprise this role loadout. /// Groups that comprise this role loadout.
/// </summary> /// </summary>
[DataField(required: true)] [DataField]
public List<ProtoId<LoadoutGroupPrototype>> Groups = new(); public List<ProtoId<LoadoutGroupPrototype>> Groups = new();
/// <summary> /// <summary>

View File

@@ -1,4 +1,5 @@
using Content.Shared.Actions; using Content.Shared.Actions;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
@@ -7,21 +8,9 @@ namespace Content.Shared.Silicons.Laws.Components;
/// <summary> /// <summary>
/// This is used for entities which are bound to silicon laws and can view them. /// This is used for entities which are bound to silicon laws and can view them.
/// </summary> /// </summary>
[RegisterComponent, Access(typeof(SharedSiliconLawSystem))] [RegisterComponent, NetworkedComponent, Access(typeof(SharedSiliconLawSystem))]
public sealed partial class SiliconLawBoundComponent : Component public sealed partial class SiliconLawBoundComponent : Component
{ {
/// <summary>
/// The sidebar action that toggles the laws screen.
/// </summary>
[DataField]
public EntProtoId ViewLawsAction = "ActionViewLaws";
/// <summary>
/// The action for toggling laws. Stored here so we can remove it later.
/// </summary>
[DataField]
public EntityUid? ViewLawsActionEntity;
/// <summary> /// <summary>
/// The last entity that provided laws to this entity. /// The last entity that provided laws to this entity.
/// </summary> /// </summary>

View File

@@ -0,0 +1,17 @@
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
namespace Content.Shared.Silicons.Laws.Components;
/// <summary>
/// Whenever an entity is inserted with silicon laws it will update the relevant entity's laws.
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed partial class SiliconLawUpdaterComponent : Component
{
/// <summary>
/// Entities to update
/// </summary>
[DataField(required: true)]
public ComponentRegistry Components;
}

View File

@@ -0,0 +1,17 @@
using Content.Shared.Silicons.Laws.Components;
using Robust.Shared.Containers;
namespace Content.Shared.Silicons.Laws;
public abstract partial class SharedSiliconLawSystem
{
private void InitializeUpdater()
{
SubscribeLocalEvent<SiliconLawUpdaterComponent, EntInsertedIntoContainerMessage>(OnUpdaterInsert);
}
protected virtual void OnUpdaterInsert(Entity<SiliconLawUpdaterComponent> ent, ref EntInsertedIntoContainerMessage args)
{
// TODO: Prediction
}
}

View File

@@ -8,13 +8,14 @@ namespace Content.Shared.Silicons.Laws;
/// <summary> /// <summary>
/// This handles getting and displaying the laws for silicons. /// This handles getting and displaying the laws for silicons.
/// </summary> /// </summary>
public abstract class SharedSiliconLawSystem : EntitySystem public abstract partial class SharedSiliconLawSystem : EntitySystem
{ {
[Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly SharedPopupSystem _popup = default!;
/// <inheritdoc/> /// <inheritdoc/>
public override void Initialize() public override void Initialize()
{ {
InitializeUpdater();
SubscribeLocalEvent<EmagSiliconLawComponent, GotEmaggedEvent>(OnGotEmagged); SubscribeLocalEvent<EmagSiliconLawComponent, GotEmaggedEvent>(OnGotEmagged);
SubscribeLocalEvent<EmagSiliconLawComponent, OnAttemptEmagEvent>(OnAttemptEmag); SubscribeLocalEvent<EmagSiliconLawComponent, OnAttemptEmagEvent>(OnAttemptEmag);
} }

View File

@@ -0,0 +1,25 @@
using Content.Shared.Doors.Components;
using Robust.Shared.Serialization;
namespace Content.Shared.Silicons.StationAi;
public abstract partial class SharedStationAiSystem
{
// Handles airlock radial
private void InitializeAirlock()
{
SubscribeLocalEvent<DoorBoltComponent, StationAiBoltEvent>(OnAirlockBolt);
}
private void OnAirlockBolt(EntityUid ent, DoorBoltComponent component, StationAiBoltEvent args)
{
_doors.SetBoltsDown((ent, component), args.Bolted, args.User, predicted: true);
}
}
[Serializable, NetSerializable]
public sealed class StationAiBoltEvent : BaseStationAiAction
{
public bool Bolted;
}

View File

@@ -0,0 +1,187 @@
using System.Diagnostics.CodeAnalysis;
using Content.Shared.Actions.Events;
using Content.Shared.Interaction.Events;
using Content.Shared.Verbs;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
namespace Content.Shared.Silicons.StationAi;
public abstract partial class SharedStationAiSystem
{
/*
* Added when an entity is inserted into a StationAiCore.
*/
private void InitializeHeld()
{
SubscribeLocalEvent<StationAiRadialMessage>(OnRadialMessage);
SubscribeLocalEvent<BoundUserInterfaceMessageAttempt>(OnMessageAttempt);
SubscribeLocalEvent<StationAiWhitelistComponent, GetVerbsEvent<AlternativeVerb>>(OnTargetVerbs);
SubscribeLocalEvent<StationAiHeldComponent, InteractionAttemptEvent>(OnHeldInteraction);
SubscribeLocalEvent<StationAiHeldComponent, AttemptRelayActionComponentChangeEvent>(OnHeldRelay);
SubscribeLocalEvent<StationAiHeldComponent, JumpToCoreEvent>(OnCoreJump);
}
private void OnCoreJump(Entity<StationAiHeldComponent> ent, ref JumpToCoreEvent args)
{
if (!TryGetCore(ent.Owner, out var core) || core.Comp?.RemoteEntity == null)
return;
_xforms.DropNextTo(core.Comp.RemoteEntity.Value, core.Owner) ;
}
/// <summary>
/// Tries to get the entity held in the AI core.
/// </summary>
private bool TryGetHeld(Entity<StationAiCoreComponent?> entity, out EntityUid held)
{
held = EntityUid.Invalid;
if (!Resolve(entity.Owner, ref entity.Comp))
return false;
if (!_containers.TryGetContainer(entity.Owner, StationAiCoreComponent.Container, out var container) ||
container.ContainedEntities.Count == 0)
return false;
held = container.ContainedEntities[0];
return true;
}
private bool TryGetCore(EntityUid ent, out Entity<StationAiCoreComponent?> core)
{
if (!_containers.TryGetContainingContainer(ent, out var container) ||
container.ID != StationAiCoreComponent.Container ||
!TryComp(container.Owner, out StationAiCoreComponent? coreComp) ||
coreComp.RemoteEntity == null)
{
core = (EntityUid.Invalid, null);
return false;
}
core = (container.Owner, coreComp);
return true;
}
private void OnHeldRelay(Entity<StationAiHeldComponent> ent, ref AttemptRelayActionComponentChangeEvent args)
{
if (!TryGetCore(ent.Owner, out var core))
return;
args.Target = core.Comp?.RemoteEntity;
}
private void OnRadialMessage(StationAiRadialMessage ev)
{
if (!TryGetEntity(ev.Entity, out var target))
return;
ev.Event.User = ev.Actor;
RaiseLocalEvent(target.Value, (object) ev.Event);
}
private void OnMessageAttempt(BoundUserInterfaceMessageAttempt ev)
{
if (ev.Actor == ev.Target)
return;
if (TryComp(ev.Actor, out StationAiHeldComponent? aiComp) &&
(!ValidateAi((ev.Actor, aiComp)) ||
!HasComp<StationAiWhitelistComponent>(ev.Target)))
{
ev.Cancel();
}
}
private void OnHeldInteraction(Entity<StationAiHeldComponent> ent, ref InteractionAttemptEvent args)
{
// Cancel if it's not us or something with a whitelist.
args.Cancelled = ent.Owner != args.Target &&
args.Target != null &&
(!TryComp(args.Target, out StationAiWhitelistComponent? whitelist) || !whitelist.Enabled);
}
private void OnTargetVerbs(Entity<StationAiWhitelistComponent> ent, ref GetVerbsEvent<AlternativeVerb> args)
{
if (!args.CanComplexInteract ||
!ent.Comp.Enabled ||
!HasComp<StationAiHeldComponent>(args.User) ||
!HasComp<StationAiWhitelistComponent>(args.Target))
{
return;
}
var user = args.User;
var target = args.Target;
var isOpen = _uiSystem.IsUiOpen(target, AiUi.Key, user);
args.Verbs.Add(new AlternativeVerb()
{
Text = isOpen ? Loc.GetString("ai-close") : Loc.GetString("ai-open"),
Act = () =>
{
if (isOpen)
{
_uiSystem.CloseUi(ent.Owner, AiUi.Key, user);
}
else
{
_uiSystem.OpenUi(ent.Owner, AiUi.Key, user);
}
}
});
}
}
/// <summary>
/// Raised from client to server as a BUI message wrapping the event to perform.
/// Also handles AI action validation.
/// </summary>
[Serializable, NetSerializable]
public sealed class StationAiRadialMessage : BoundUserInterfaceMessage
{
public BaseStationAiAction Event = default!;
}
// Do nothing on server just here for shared move along.
/// <summary>
/// Raised on client to get the relevant data for radial actions.
/// </summary>
public sealed class StationAiRadial : BaseStationAiAction
{
public SpriteSpecifier? Sprite;
public string? Tooltip;
public BaseStationAiAction Event = default!;
}
/// <summary>
/// Abstract parent for radial actions events.
/// When a client requests a radial action this will get sent.
/// </summary>
[Serializable, NetSerializable]
public abstract class BaseStationAiAction
{
[field:NonSerialized]
public EntityUid User { get; set; }
}
// No idea if there's a better way to do this.
/// <summary>
/// Grab actions possible for an AI on the target entity.
/// </summary>
[ByRefEvent]
public record struct GetStationAiRadialEvent()
{
public List<StationAiRadial> Actions = new();
}
[Serializable, NetSerializable]
public enum AiUi : byte
{
Key,
}

View File

@@ -0,0 +1,28 @@
using Content.Shared.Light.Components;
using Robust.Shared.Serialization;
namespace Content.Shared.Silicons.StationAi;
public abstract partial class SharedStationAiSystem
{
// Handles light toggling.
private void InitializeLight()
{
SubscribeLocalEvent<ItemTogglePointLightComponent, StationAiLightEvent>(OnLight);
}
private void OnLight(EntityUid ent, ItemTogglePointLightComponent component, StationAiLightEvent args)
{
if (args.Enabled)
_toggles.TryActivate(ent, user: args.User);
else
_toggles.TryDeactivate(ent, user: args.User);
}
}
[Serializable, NetSerializable]
public sealed class StationAiLightEvent : BaseStationAiAction
{
public bool Enabled;
}

View File

@@ -0,0 +1,412 @@
using Content.Shared.ActionBlocker;
using Content.Shared.Actions;
using Content.Shared.Administration.Managers;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Database;
using Content.Shared.Doors.Systems;
using Content.Shared.Interaction;
using Content.Shared.Item.ItemToggle;
using Content.Shared.Mind;
using Content.Shared.Movement.Components;
using Content.Shared.Movement.Systems;
using Content.Shared.Power;
using Content.Shared.StationAi;
using Content.Shared.Verbs;
using Robust.Shared.Containers;
using Robust.Shared.Map.Components;
using Robust.Shared.Network;
using Robust.Shared.Physics;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Timing;
namespace Content.Shared.Silicons.StationAi;
public abstract partial class SharedStationAiSystem : EntitySystem
{
[Dependency] private readonly ISharedAdminManager _admin = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly INetManager _net = default!;
[Dependency] private readonly ItemSlotsSystem _slots = default!;
[Dependency] private readonly ItemToggleSystem _toggles = default!;
[Dependency] private readonly ActionBlockerSystem _blocker = default!;
[Dependency] private readonly MetaDataSystem _metadata = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedContainerSystem _containers = default!;
[Dependency] private readonly SharedDoorSystem _doors = default!;
[Dependency] private readonly SharedEyeSystem _eye = default!;
[Dependency] protected readonly SharedMapSystem Maps = default!;
[Dependency] private readonly SharedMindSystem _mind = default!;
[Dependency] private readonly SharedMoverController _mover = default!;
[Dependency] private readonly SharedTransformSystem _xforms = default!;
[Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!;
[Dependency] private readonly StationAiVisionSystem _vision = default!;
// StationAiHeld is added to anything inside of an AI core.
// StationAiHolder indicates it can hold an AI positronic brain (e.g. holocard / core).
// StationAiCore holds functionality related to the core itself.
// StationAiWhitelist is a general whitelist to stop it being able to interact with anything
// StationAiOverlay handles the static overlay. It also handles interaction blocking on client and server
// for anything under it.
private EntityQuery<BroadphaseComponent> _broadphaseQuery;
private EntityQuery<MapGridComponent> _gridQuery;
[ValidatePrototypeId<EntityPrototype>]
private static readonly EntProtoId DefaultAi = "StationAiBrain";
public override void Initialize()
{
base.Initialize();
_broadphaseQuery = GetEntityQuery<BroadphaseComponent>();
_gridQuery = GetEntityQuery<MapGridComponent>();
InitializeAirlock();
InitializeHeld();
InitializeLight();
SubscribeLocalEvent<StationAiWhitelistComponent, BoundUserInterfaceCheckRangeEvent>(OnAiBuiCheck);
SubscribeLocalEvent<StationAiOverlayComponent, AccessibleOverrideEvent>(OnAiAccessible);
SubscribeLocalEvent<StationAiOverlayComponent, InRangeOverrideEvent>(OnAiInRange);
SubscribeLocalEvent<StationAiOverlayComponent, MenuVisibilityEvent>(OnAiMenu);
SubscribeLocalEvent<StationAiHolderComponent, ComponentInit>(OnHolderInit);
SubscribeLocalEvent<StationAiHolderComponent, ComponentRemove>(OnHolderRemove);
SubscribeLocalEvent<StationAiHolderComponent, AfterInteractEvent>(OnHolderInteract);
SubscribeLocalEvent<StationAiHolderComponent, MapInitEvent>(OnHolderMapInit);
SubscribeLocalEvent<StationAiHolderComponent, EntInsertedIntoContainerMessage>(OnHolderConInsert);
SubscribeLocalEvent<StationAiHolderComponent, EntRemovedFromContainerMessage>(OnHolderConRemove);
SubscribeLocalEvent<StationAiCoreComponent, EntInsertedIntoContainerMessage>(OnAiInsert);
SubscribeLocalEvent<StationAiCoreComponent, EntRemovedFromContainerMessage>(OnAiRemove);
SubscribeLocalEvent<StationAiCoreComponent, MapInitEvent>(OnAiMapInit);
SubscribeLocalEvent<StationAiCoreComponent, ComponentShutdown>(OnAiShutdown);
SubscribeLocalEvent<StationAiCoreComponent, PowerChangedEvent>(OnCorePower);
SubscribeLocalEvent<StationAiCoreComponent, GetVerbsEvent<Verb>>(OnCoreVerbs);
}
private void OnCoreVerbs(Entity<StationAiCoreComponent> ent, ref GetVerbsEvent<Verb> args)
{
if (!_admin.IsAdmin(args.User) ||
TryGetHeld((ent.Owner, ent.Comp), out _))
{
return;
}
var user = args.User;
args.Verbs.Add(new Verb()
{
Text = Loc.GetString("station-ai-takeover"),
Category = VerbCategory.Debug,
Act = () =>
{
var brain = SpawnInContainerOrDrop(DefaultAi, ent.Owner, StationAiCoreComponent.Container);
_mind.ControlMob(user, brain);
},
Impact = LogImpact.High,
});
}
private void OnAiAccessible(Entity<StationAiOverlayComponent> ent, ref AccessibleOverrideEvent args)
{
args.Handled = true;
// Hopefully AI never needs storage
if (_containers.TryGetContainingContainer(args.Target, out var targetContainer))
{
return;
}
if (!_containers.IsInSameOrTransparentContainer(args.User, args.Target, otherContainer: targetContainer))
{
return;
}
args.Accessible = true;
}
private void OnAiMenu(Entity<StationAiOverlayComponent> ent, ref MenuVisibilityEvent args)
{
args.Visibility &= ~MenuVisibility.NoFov;
}
private void OnAiBuiCheck(Entity<StationAiWhitelistComponent> ent, ref BoundUserInterfaceCheckRangeEvent args)
{
args.Result = BoundUserInterfaceRangeResult.Fail;
// Similar to the inrange check but more optimised so server doesn't die.
var targetXform = Transform(args.Target);
// No cross-grid
if (targetXform.GridUid != args.Actor.Comp.GridUid)
{
return;
}
if (!_broadphaseQuery.TryComp(targetXform.GridUid, out var broadphase) || !_gridQuery.TryComp(targetXform.GridUid, out var grid))
{
return;
}
var targetTile = Maps.LocalToTile(targetXform.GridUid.Value, grid, targetXform.Coordinates);
lock (_vision)
{
if (_vision.IsAccessible((targetXform.GridUid.Value, broadphase, grid), targetTile, fastPath: true))
{
args.Result = BoundUserInterfaceRangeResult.Pass;
}
}
}
private void OnAiInRange(Entity<StationAiOverlayComponent> ent, ref InRangeOverrideEvent args)
{
args.Handled = true;
var targetXform = Transform(args.Target);
// No cross-grid
if (targetXform.GridUid != Transform(args.User).GridUid)
{
return;
}
// Validate it's in camera range yes this is expensive.
// Yes it needs optimising
if (!_broadphaseQuery.TryComp(targetXform.GridUid, out var broadphase) || !_gridQuery.TryComp(targetXform.GridUid, out var grid))
{
return;
}
var targetTile = Maps.LocalToTile(targetXform.GridUid.Value, grid, targetXform.Coordinates);
args.InRange = _vision.IsAccessible((targetXform.GridUid.Value, broadphase, grid), targetTile);
}
private void OnHolderInteract(Entity<StationAiHolderComponent> ent, ref AfterInteractEvent args)
{
if (!TryComp(args.Target, out StationAiHolderComponent? targetHolder))
return;
// Try to insert our thing into them
if (_slots.CanEject(ent.Owner, args.User, ent.Comp.Slot))
{
if (!_slots.TryInsert(args.Target.Value, targetHolder.Slot, ent.Comp.Slot.Item!.Value, args.User, excludeUserAudio: true))
{
return;
}
args.Handled = true;
return;
}
// Otherwise try to take from them
if (_slots.CanEject(args.Target.Value, args.User, targetHolder.Slot))
{
if (!_slots.TryInsert(ent.Owner, ent.Comp.Slot, targetHolder.Slot.Item!.Value, args.User, excludeUserAudio: true))
{
return;
}
args.Handled = true;
}
}
private void OnHolderInit(Entity<StationAiHolderComponent> ent, ref ComponentInit args)
{
_slots.AddItemSlot(ent.Owner, StationAiHolderComponent.Container, ent.Comp.Slot);
}
private void OnHolderRemove(Entity<StationAiHolderComponent> ent, ref ComponentRemove args)
{
_slots.RemoveItemSlot(ent.Owner, ent.Comp.Slot);
}
private void OnHolderConInsert(Entity<StationAiHolderComponent> ent, ref EntInsertedIntoContainerMessage args)
{
UpdateAppearance((ent.Owner, ent.Comp));
}
private void OnHolderConRemove(Entity<StationAiHolderComponent> ent, ref EntRemovedFromContainerMessage args)
{
UpdateAppearance((ent.Owner, ent.Comp));
}
private void OnHolderMapInit(Entity<StationAiHolderComponent> ent, ref MapInitEvent args)
{
UpdateAppearance(ent.Owner);
}
private void OnAiShutdown(Entity<StationAiCoreComponent> ent, ref ComponentShutdown args)
{
// TODO: Tryqueuedel
if (_net.IsClient)
return;
QueueDel(ent.Comp.RemoteEntity);
ent.Comp.RemoteEntity = null;
}
private void OnCorePower(Entity<StationAiCoreComponent> ent, ref PowerChangedEvent args)
{
// TODO: I think in 13 they just straightup die so maybe implement that
if (args.Powered)
{
if (!SetupEye(ent))
return;
AttachEye(ent);
}
else
{
ClearEye(ent);
}
}
private void OnAiMapInit(Entity<StationAiCoreComponent> ent, ref MapInitEvent args)
{
SetupEye(ent);
AttachEye(ent);
}
private bool SetupEye(Entity<StationAiCoreComponent> ent)
{
if (ent.Comp.RemoteEntity != null)
return false;
if (ent.Comp.RemoteEntityProto != null)
{
ent.Comp.RemoteEntity = SpawnAtPosition(ent.Comp.RemoteEntityProto, Transform(ent.Owner).Coordinates);
Dirty(ent);
}
return true;
}
private void ClearEye(Entity<StationAiCoreComponent> ent)
{
QueueDel(ent.Comp.RemoteEntity);
ent.Comp.RemoteEntity = null;
}
private void AttachEye(Entity<StationAiCoreComponent> ent)
{
if (ent.Comp.RemoteEntity == null)
return;
if (!_containers.TryGetContainer(ent.Owner, StationAiHolderComponent.Container, out var container) ||
container.ContainedEntities.Count != 1)
{
return;
}
// Attach them to the portable eye that can move around.
var user = container.ContainedEntities[0];
if (TryComp(user, out EyeComponent? eyeComp))
{
_eye.SetTarget(user, ent.Comp.RemoteEntity.Value, eyeComp);
}
_mover.SetRelay(user, ent.Comp.RemoteEntity.Value);
}
private void OnAiInsert(Entity<StationAiCoreComponent> ent, ref EntInsertedIntoContainerMessage args)
{
if (_timing.ApplyingState)
return;
// Just so text and the likes works properly
_metadata.SetEntityName(ent.Owner, MetaData(args.Entity).EntityName);
AttachEye(ent);
}
private void OnAiRemove(Entity<StationAiCoreComponent> ent, ref EntRemovedFromContainerMessage args)
{
if (_timing.ApplyingState)
return;
// Reset name to whatever
_metadata.SetEntityName(ent.Owner, Prototype(ent.Owner)?.Name ?? string.Empty);
// Remove eye relay
RemCompDeferred<RelayInputMoverComponent>(args.Entity);
if (TryComp(args.Entity, out EyeComponent? eyeComp))
{
_eye.SetTarget(args.Entity, null, eyeComp);
}
}
private void UpdateAppearance(Entity<StationAiHolderComponent?> entity)
{
if (!Resolve(entity.Owner, ref entity.Comp, false))
return;
if (!_containers.TryGetContainer(entity.Owner, StationAiHolderComponent.Container, out var container) ||
container.Count == 0)
{
_appearance.SetData(entity.Owner, StationAiVisualState.Key, StationAiState.Empty);
return;
}
_appearance.SetData(entity.Owner, StationAiVisualState.Key, StationAiState.Occupied);
}
public virtual bool SetVisionEnabled(Entity<StationAiVisionComponent> entity, bool enabled, bool announce = false)
{
if (entity.Comp.Enabled == enabled)
return false;
entity.Comp.Enabled = enabled;
Dirty(entity);
return true;
}
public virtual bool SetWhitelistEnabled(Entity<StationAiWhitelistComponent> entity, bool value, bool announce = false)
{
if (entity.Comp.Enabled == value)
return false;
entity.Comp.Enabled = value;
Dirty(entity);
return true;
}
/// <summary>
/// BUI validation for ai interactions.
/// </summary>
private bool ValidateAi(Entity<StationAiHeldComponent?> entity)
{
if (!Resolve(entity.Owner, ref entity.Comp, false))
{
return false;
}
return _blocker.CanComplexInteract(entity.Owner);
}
}
public sealed partial class JumpToCoreEvent : InstantActionEvent
{
}
[Serializable, NetSerializable]
public enum StationAiVisualState : byte
{
Key,
}
[Serializable, NetSerializable]
public enum StationAiState : byte
{
Empty,
Occupied,
Dead,
}

View File

@@ -0,0 +1,32 @@
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
namespace Content.Shared.Silicons.StationAi;
/// <summary>
/// Indicates this entity can interact with station equipment and is a "Station AI".
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class StationAiCoreComponent : Component
{
/*
* I couldn't think of any other reason you'd want to split these out.
*/
/// <summary>
/// Can it move its camera around and interact remotely with things.
/// </summary>
[DataField, AutoNetworkedField]
public bool Remote = true;
/// <summary>
/// The invisible eye entity being used to look around.
/// </summary>
[DataField, AutoNetworkedField]
public EntityUid? RemoteEntity;
[DataField(readOnly: true)]
public EntProtoId? RemoteEntityProto = "StationAiHolo";
public const string Container = "station_ai_mind_slot";
}

View File

@@ -0,0 +1,9 @@
using Robust.Shared.GameStates;
namespace Content.Shared.Silicons.StationAi;
/// <summary>
/// Indicates this entity is currently held inside of a station AI core.
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed partial class StationAiHeldComponent : Component;

View File

@@ -0,0 +1,16 @@
using Content.Shared.Containers.ItemSlots;
using Robust.Shared.GameStates;
namespace Content.Shared.Silicons.StationAi;
/// <summary>
/// Allows moving a <see cref="StationAiCoreComponent"/> contained entity to and from this component.
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed partial class StationAiHolderComponent : Component
{
public const string Container = StationAiCoreComponent.Container;
[DataField]
public ItemSlot Slot = new();
}

View File

@@ -1,8 +1,9 @@
using Content.Shared.Silicons.StationAi;
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
namespace Content.Shared.Silicons.StationAi; namespace Content.Shared.StationAi;
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]//, Access(typeof(SharedStationAiSystem))] [RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedStationAiSystem))]
public sealed partial class StationAiVisionComponent : Component public sealed partial class StationAiVisionComponent : Component
{ {
[DataField, AutoNetworkedField] [DataField, AutoNetworkedField]

View File

@@ -1,4 +1,6 @@
using Content.Shared.StationAi;
using Robust.Shared.Map.Components; using Robust.Shared.Map.Components;
using Robust.Shared.Physics;
using Robust.Shared.Threading; using Robust.Shared.Threading;
using Robust.Shared.Utility; using Robust.Shared.Utility;
@@ -24,6 +26,8 @@ public sealed class StationAiVisionSystem : EntitySystem
private readonly HashSet<Entity<StationAiVisionComponent>> _seeds = new(); private readonly HashSet<Entity<StationAiVisionComponent>> _seeds = new();
private readonly HashSet<Vector2i> _viewportTiles = new(); private readonly HashSet<Vector2i> _viewportTiles = new();
private EntityQuery<OccluderComponent> _occluderQuery;
// Dummy set // Dummy set
private readonly HashSet<Vector2i> _singleTiles = new(); private readonly HashSet<Vector2i> _singleTiles = new();
@@ -36,15 +40,12 @@ public sealed class StationAiVisionSystem : EntitySystem
/// </summary> /// </summary>
private bool FastPath; private bool FastPath;
/// <summary>
/// Have we found the target tile if we're only checking for a single one.
/// </summary>
private bool TargetFound;
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
_occluderQuery = GetEntityQuery<OccluderComponent>();
_seedJob = new() _seedJob = new()
{ {
System = this, System = this,
@@ -61,16 +62,16 @@ public sealed class StationAiVisionSystem : EntitySystem
/// <summary> /// <summary>
/// Returns whether a tile is accessible based on vision. /// Returns whether a tile is accessible based on vision.
/// </summary> /// </summary>
public bool IsAccessible(Entity<MapGridComponent> grid, Vector2i tile, float expansionSize = 8.5f, bool fastPath = false) public bool IsAccessible(Entity<BroadphaseComponent, MapGridComponent> grid, Vector2i tile, float expansionSize = 8.5f, bool fastPath = false)
{ {
_viewportTiles.Clear(); _viewportTiles.Clear();
_opaque.Clear(); _opaque.Clear();
_seeds.Clear(); _seeds.Clear();
_viewportTiles.Add(tile); _viewportTiles.Add(tile);
var localBounds = _lookup.GetLocalBounds(tile, grid.Comp.TileSize); var localBounds = _lookup.GetLocalBounds(tile, grid.Comp2.TileSize);
var expandedBounds = localBounds.Enlarged(expansionSize); var expandedBounds = localBounds.Enlarged(expansionSize);
_seedJob.Grid = grid; _seedJob.Grid = (grid.Owner, grid.Comp2);
_seedJob.ExpandedBounds = expandedBounds; _seedJob.ExpandedBounds = expandedBounds;
_parallel.ProcessNow(_seedJob); _parallel.ProcessNow(_seedJob);
_job.Data.Clear(); _job.Data.Clear();
@@ -110,21 +111,19 @@ public sealed class StationAiVisionSystem : EntitySystem
_job.BoundaryTiles.Add(new HashSet<Vector2i>()); _job.BoundaryTiles.Add(new HashSet<Vector2i>());
} }
_job.TargetTile = tile;
TargetFound = false;
_singleTiles.Clear(); _singleTiles.Clear();
_job.Grid = grid; _job.Grid = (grid.Owner, grid.Comp2);
_job.VisibleTiles = _singleTiles; _job.VisibleTiles = _singleTiles;
_parallel.ProcessNow(_job, _job.Data.Count); _parallel.ProcessNow(_job, _job.Data.Count);
return TargetFound; return _job.VisibleTiles.Contains(tile);
} }
private bool IsOccluded(Entity<MapGridComponent> grid, Vector2i tile) private bool IsOccluded(Entity<BroadphaseComponent, MapGridComponent> grid, Vector2i tile)
{ {
var tileBounds = _lookup.GetLocalBounds(tile, grid.Comp.TileSize).Enlarged(-0.05f); var tileBounds = _lookup.GetLocalBounds(tile, grid.Comp2.TileSize).Enlarged(-0.05f);
_occluders.Clear(); _occluders.Clear();
_lookup.GetLocalEntitiesIntersecting(grid.Owner, tileBounds, _occluders, LookupFlags.Static); _lookup.GetLocalEntitiesIntersecting((grid.Owner, grid.Comp1), tileBounds, _occluders, query: _occluderQuery, flags: LookupFlags.Static | LookupFlags.Approximate);
var anyOccluders = false; var anyOccluders = false;
foreach (var occluder in _occluders) foreach (var occluder in _occluders)
@@ -143,17 +142,18 @@ public sealed class StationAiVisionSystem : EntitySystem
/// Gets a byond-equivalent for tiles in the specified worldAABB. /// Gets a byond-equivalent for tiles in the specified worldAABB.
/// </summary> /// </summary>
/// <param name="expansionSize">How much to expand the bounds before to find vision intersecting it. Makes this the largest vision size + 1 tile.</param> /// <param name="expansionSize">How much to expand the bounds before to find vision intersecting it. Makes this the largest vision size + 1 tile.</param>
public void GetView(Entity<MapGridComponent> grid, Box2Rotated worldBounds, HashSet<Vector2i> visibleTiles, float expansionSize = 8.5f) public void GetView(Entity<BroadphaseComponent, MapGridComponent> grid, Box2Rotated worldBounds, HashSet<Vector2i> visibleTiles, float expansionSize = 8.5f)
{ {
_viewportTiles.Clear(); _viewportTiles.Clear();
_opaque.Clear(); _opaque.Clear();
_seeds.Clear(); _seeds.Clear();
var expandedBounds = worldBounds.Enlarged(expansionSize);
// TODO: Would be nice to be able to run this while running the other stuff. // TODO: Would be nice to be able to run this while running the other stuff.
_seedJob.Grid = grid; _seedJob.Grid = (grid.Owner, grid.Comp2);
var localAABB = _xforms.GetInvWorldMatrix(grid).TransformBox(expandedBounds); var invMatrix = _xforms.GetInvWorldMatrix(grid);
_seedJob.ExpandedBounds = localAABB; var localAabb = invMatrix.TransformBox(worldBounds);
var enlargedLocalAabb = invMatrix.TransformBox(worldBounds.Enlarged(expansionSize));
_seedJob.ExpandedBounds = enlargedLocalAabb;
_parallel.ProcessNow(_seedJob); _parallel.ProcessNow(_seedJob);
_job.Data.Clear(); _job.Data.Clear();
FastPath = false; FastPath = false;
@@ -170,7 +170,7 @@ public sealed class StationAiVisionSystem : EntitySystem
return; return;
// Get viewport tiles // Get viewport tiles
var tileEnumerator = _maps.GetLocalTilesEnumerator(grid, grid, localAABB, ignoreEmpty: false); var tileEnumerator = _maps.GetLocalTilesEnumerator(grid, grid, localAabb, ignoreEmpty: false);
while (tileEnumerator.MoveNext(out var tileRef)) while (tileEnumerator.MoveNext(out var tileRef))
{ {
@@ -182,9 +182,8 @@ public sealed class StationAiVisionSystem : EntitySystem
_viewportTiles.Add(tileRef.GridIndices); _viewportTiles.Add(tileRef.GridIndices);
} }
tileEnumerator = _maps.GetLocalTilesEnumerator(grid, grid, localAABB, ignoreEmpty: false); tileEnumerator = _maps.GetLocalTilesEnumerator(grid, grid, enlargedLocalAabb, ignoreEmpty: false);
// Get all other relevant tiles.
while (tileEnumerator.MoveNext(out var tileRef)) while (tileEnumerator.MoveNext(out var tileRef))
{ {
if (_viewportTiles.Contains(tileRef.GridIndices)) if (_viewportTiles.Contains(tileRef.GridIndices))
@@ -206,9 +205,7 @@ public sealed class StationAiVisionSystem : EntitySystem
_job.BoundaryTiles.Add(new HashSet<Vector2i>()); _job.BoundaryTiles.Add(new HashSet<Vector2i>());
} }
_job.TargetTile = null; _job.Grid = (grid.Owner, grid.Comp2);
TargetFound = false;
_job.Grid = grid;
_job.VisibleTiles = visibleTiles; _job.VisibleTiles = visibleTiles;
_parallel.ProcessNow(_job, _job.Data.Count); _parallel.ProcessNow(_job, _job.Data.Count);
} }
@@ -250,6 +247,7 @@ public sealed class StationAiVisionSystem : EntitySystem
return false; return false;
} }
/// <summary>
/// Checks whether this tile fits the definition of a "corner" /// Checks whether this tile fits the definition of a "corner"
/// </summary> /// </summary>
private bool IsCorner( private bool IsCorner(
@@ -287,7 +285,7 @@ public sealed class StationAiVisionSystem : EntitySystem
public void Execute() public void Execute()
{ {
System._lookup.GetLocalEntitiesIntersecting(Grid.Owner, ExpandedBounds, System._seeds); System._lookup.GetLocalEntitiesIntersecting(Grid.Owner, ExpandedBounds, System._seeds, flags: LookupFlags.All | LookupFlags.Approximate);
} }
} }
@@ -302,9 +300,6 @@ public sealed class StationAiVisionSystem : EntitySystem
public Entity<MapGridComponent> Grid; public Entity<MapGridComponent> Grid;
public List<Entity<StationAiVisionComponent>> Data = new(); public List<Entity<StationAiVisionComponent>> Data = new();
// If we're doing range-checks might be able to early out
public Vector2i? TargetTile;
public HashSet<Vector2i> VisibleTiles; public HashSet<Vector2i> VisibleTiles;
public readonly List<Dictionary<Vector2i, int>> Vis1 = new(); public readonly List<Dictionary<Vector2i, int>> Vis1 = new();
@@ -315,18 +310,6 @@ public sealed class StationAiVisionSystem : EntitySystem
public void Execute(int index) public void Execute(int index)
{ {
// If we're looking for a single tile then early-out if someone else has found it.
if (TargetTile != null)
{
lock (System)
{
if (System.TargetFound)
{
return;
}
}
}
var seed = Data[index]; var seed = Data[index];
var seedXform = EntManager.GetComponent<TransformComponent>(seed); var seedXform = EntManager.GetComponent<TransformComponent>(seed);
@@ -338,30 +321,11 @@ public sealed class StationAiVisionSystem : EntitySystem
Grid.Comp, Grid.Comp,
new Circle(System._xforms.GetWorldPosition(seedXform), seed.Comp.Range), ignoreEmpty: false); new Circle(System._xforms.GetWorldPosition(seedXform), seed.Comp.Range), ignoreEmpty: false);
// Try to find the target tile. lock (VisibleTiles)
if (TargetTile != null)
{ {
foreach (var tile in squircles) foreach (var tile in squircles)
{ {
if (tile.GridIndices == TargetTile) VisibleTiles.Add(tile.GridIndices);
{
lock (System)
{
System.TargetFound = true;
}
return;
}
}
}
else
{
lock (VisibleTiles)
{
foreach (var tile in squircles)
{
VisibleTiles.Add(tile.GridIndices);
}
} }
} }
@@ -480,40 +444,21 @@ public sealed class StationAiVisionSystem : EntitySystem
vis1[tile] = -1; vis1[tile] = -1;
} }
if (TargetTile != null) // vis2 is what we care about for LOS.
foreach (var tile in seedTiles)
{ {
if (vis1.TryGetValue(TargetTile.Value, out var tileVis)) // If not in viewport don't care.
if (!System._viewportTiles.Contains(tile))
continue;
var tileVis = vis1.GetValueOrDefault(tile, 0);
if (tileVis != 0)
{ {
DebugTools.Assert(seedTiles.Contains(TargetTile.Value)); // No idea if it's better to do this inside or out.
lock (VisibleTiles)
if (tileVis != 0)
{ {
lock (System) VisibleTiles.Add(tile);
{
System.TargetFound = true;
return;
}
}
}
}
else
{
// vis2 is what we care about for LOS.
foreach (var tile in seedTiles)
{
// If not in viewport don't care.
if (!System._viewportTiles.Contains(tile))
continue;
var tileVis = vis1.GetValueOrDefault(tile, 0);
if (tileVis != 0)
{
// No idea if it's better to do this inside or out.
lock (VisibleTiles)
{
VisibleTiles.Add(tile);
}
} }
} }
} }

View File

@@ -0,0 +1,13 @@
using Robust.Shared.GameStates;
namespace Content.Shared.Silicons.StationAi;
/// <summary>
/// Indicates an entity that has <see cref="StationAiHeldComponent"/> can interact with this.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedStationAiSystem))]
public sealed partial class StationAiWhitelistComponent : Component
{
[DataField, AutoNetworkedField]
public bool Enabled = true;
}

View File

@@ -8,14 +8,18 @@ using Content.Shared.Storage;
using Content.Shared.Storage.EntitySystems; using Content.Shared.Storage.EntitySystems;
using Robust.Shared.Collections; using Robust.Shared.Collections;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Utility;
namespace Content.Shared.Station; namespace Content.Shared.Station;
public abstract class SharedStationSpawningSystem : EntitySystem public abstract class SharedStationSpawningSystem : EntitySystem
{ {
[Dependency] protected readonly IPrototypeManager PrototypeManager = default!; [Dependency] protected readonly IPrototypeManager PrototypeManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] protected readonly InventorySystem InventorySystem = default!; [Dependency] protected readonly InventorySystem InventorySystem = default!;
[Dependency] private readonly SharedHandsSystem _handsSystem = default!; [Dependency] private readonly SharedHandsSystem _handsSystem = default!;
[Dependency] private readonly MetaDataSystem _metadata = default!;
[Dependency] private readonly SharedStorageSystem _storage = default!; [Dependency] private readonly SharedStorageSystem _storage = default!;
[Dependency] private readonly SharedTransformSystem _xformSystem = default!; [Dependency] private readonly SharedTransformSystem _xformSystem = default!;
@@ -34,7 +38,7 @@ public abstract class SharedStationSpawningSystem : EntitySystem
} }
/// <summary> /// <summary>
/// Equips the given starting gears from a `RoleLoadout` onto an entity. /// Equips the data from a `RoleLoadout` onto an entity.
/// </summary> /// </summary>
public void EquipRoleLoadout(EntityUid entity, RoleLoadout loadout, RoleLoadoutPrototype roleProto) public void EquipRoleLoadout(EntityUid entity, RoleLoadout loadout, RoleLoadoutPrototype roleProto)
{ {
@@ -52,6 +56,26 @@ public abstract class SharedStationSpawningSystem : EntitySystem
EquipStartingGear(entity, loadoutProto, raiseEvent: false); EquipStartingGear(entity, loadoutProto, raiseEvent: false);
} }
} }
EquipRoleName(entity, loadout, roleProto);
}
/// <summary>
/// Applies the role's name as applicable to the entity.
/// </summary>
public void EquipRoleName(EntityUid entity, RoleLoadout loadout, RoleLoadoutPrototype roleProto)
{
string? name = null;
if (string.IsNullOrEmpty(name) && PrototypeManager.TryIndex(roleProto.NameDataset, out var nameData))
{
name = _random.Pick(nameData.Values);
}
if (!string.IsNullOrEmpty(name))
{
_metadata.SetEntityName(entity, name);
}
} }
public void EquipStartingGear(EntityUid entity, LoadoutPrototype loadout, bool raiseEvent = true) public void EquipStartingGear(EntityUid entity, LoadoutPrototype loadout, bool raiseEvent = true)

View File

@@ -12,7 +12,7 @@ namespace Content.Shared.UserInterface
/// <summary> /// <summary>
/// Whether the item must be held in one of the user's hands to work. /// Whether the item must be held in one of the user's hands to work.
/// This is ignored unless <see cref="RequireHands"/> is true. /// This is ignored unless <see cref="RequiresComplex"/> is true.
/// </summary> /// </summary>
[ViewVariables(VVAccess.ReadWrite)] [ViewVariables(VVAccess.ReadWrite)]
[DataField] [DataField]
@@ -29,15 +29,15 @@ namespace Content.Shared.UserInterface
public LocId VerbText = "ui-verb-toggle-open"; public LocId VerbText = "ui-verb-toggle-open";
/// <summary> /// <summary>
/// Whether you need a hand to operate this UI. The hand does not need to be free, you just need to have one. /// Whether you need to be able to do complex interactions to operate this UI.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// This should probably be true for most machines & computers, but there will still be UIs that represent a /// This should probably be true for most machines & computers, but there will still be UIs that represent a
/// more generic interaction / configuration that might not require hands. /// more generic interaction / configuration that might not require complex.
/// </remarks> /// </remarks>
[ViewVariables(VVAccess.ReadWrite)] [ViewVariables(VVAccess.ReadWrite)]
[DataField] [DataField]
public bool RequireHands = true; public bool RequiresComplex = true;
/// <summary> /// <summary>
/// Entities that are required to open this UI. /// Entities that are required to open this UI.

View File

@@ -101,7 +101,7 @@ public sealed partial class ActivatableUISystem : EntitySystem
if (_whitelistSystem.IsWhitelistFail(component.RequiredItems, args.Using ?? default)) if (_whitelistSystem.IsWhitelistFail(component.RequiredItems, args.Using ?? default))
return false; return false;
if (component.RequireHands) if (component.RequiresComplex)
{ {
if (args.Hands == null) if (args.Hands == null)
return false; return false;
@@ -191,19 +191,22 @@ public sealed partial class ActivatableUISystem : EntitySystem
if (!_blockerSystem.CanInteract(user, uiEntity) && (!HasComp<GhostComponent>(user) || aui.BlockSpectators)) if (!_blockerSystem.CanInteract(user, uiEntity) && (!HasComp<GhostComponent>(user) || aui.BlockSpectators))
return false; return false;
if (aui.RequireHands) if (aui.RequiresComplex)
{
if (!_blockerSystem.CanComplexInteract(user))
return false;
}
if (aui.InHandsOnly)
{ {
if (!TryComp(user, out HandsComponent? hands)) if (!TryComp(user, out HandsComponent? hands))
return false; return false;
if (aui.InHandsOnly) if (!_hands.IsHolding(user, uiEntity, out var hand, hands))
{ return false;
if (!_hands.IsHolding(user, uiEntity, out var hand, hands))
return false;
if (aui.RequireActiveHand && hands.ActiveHand != hand) if (aui.RequireActiveHand && hands.ActiveHand != hand)
return false; return false;
}
} }
if (aui.AdminOnly && !_adminManager.IsAdmin(user)) if (aui.AdminOnly && !_adminManager.IsAdmin(user))
@@ -274,13 +277,13 @@ public sealed partial class ActivatableUISystem : EntitySystem
private void OnHandDeselected(Entity<ActivatableUIComponent> ent, ref HandDeselectedEvent args) private void OnHandDeselected(Entity<ActivatableUIComponent> ent, ref HandDeselectedEvent args)
{ {
if (ent.Comp.RequireHands && ent.Comp.InHandsOnly && ent.Comp.RequireActiveHand) if (ent.Comp.InHandsOnly && ent.Comp.RequireActiveHand)
CloseAll(ent, ent); CloseAll(ent, ent);
} }
private void OnHandUnequipped(Entity<ActivatableUIComponent> ent, ref GotUnequippedHandEvent args) private void OnHandUnequipped(Entity<ActivatableUIComponent> ent, ref GotUnequippedHandEvent args)
{ {
if (ent.Comp.RequireHands && ent.Comp.InHandsOnly) if (ent.Comp.InHandsOnly)
CloseAll(ent, ent); CloseAll(ent, ent);
} }
} }

View File

@@ -78,6 +78,7 @@ namespace Content.Shared.Verbs
// A large number of verbs need to check action blockers. Instead of repeatedly having each system individually // A large number of verbs need to check action blockers. Instead of repeatedly having each system individually
// call ActionBlocker checks, just cache it for the verb request. // call ActionBlocker checks, just cache it for the verb request.
var canInteract = force || _actionBlockerSystem.CanInteract(user, target); var canInteract = force || _actionBlockerSystem.CanInteract(user, target);
var canComplexInteract = force || _actionBlockerSystem.CanComplexInteract(user);
_interactionSystem.TryGetUsedEntity(user, out var @using); _interactionSystem.TryGetUsedEntity(user, out var @using);
TryComp<HandsComponent>(user, out var hands); TryComp<HandsComponent>(user, out var hands);
@@ -85,7 +86,7 @@ namespace Content.Shared.Verbs
// TODO: fix this garbage and use proper generics or reflection or something else, not this. // TODO: fix this garbage and use proper generics or reflection or something else, not this.
if (types.Contains(typeof(InteractionVerb))) if (types.Contains(typeof(InteractionVerb)))
{ {
var verbEvent = new GetVerbsEvent<InteractionVerb>(user, target, @using, hands, canInteract, canAccess, extraCategories); var verbEvent = new GetVerbsEvent<InteractionVerb>(user, target, @using, hands, canInteract: canInteract, canComplexInteract: canComplexInteract, canAccess: canAccess, extraCategories);
RaiseLocalEvent(target, verbEvent, true); RaiseLocalEvent(target, verbEvent, true);
verbs.UnionWith(verbEvent.Verbs); verbs.UnionWith(verbEvent.Verbs);
} }
@@ -94,35 +95,35 @@ namespace Content.Shared.Verbs
&& @using != null && @using != null
&& @using != target) && @using != target)
{ {
var verbEvent = new GetVerbsEvent<UtilityVerb>(user, target, @using, hands, canInteract, canAccess, extraCategories); var verbEvent = new GetVerbsEvent<UtilityVerb>(user, target, @using, hands, canInteract: canInteract, canComplexInteract: canComplexInteract, canAccess: canAccess, extraCategories);
RaiseLocalEvent(@using.Value, verbEvent, true); // directed at used, not at target RaiseLocalEvent(@using.Value, verbEvent, true); // directed at used, not at target
verbs.UnionWith(verbEvent.Verbs); verbs.UnionWith(verbEvent.Verbs);
} }
if (types.Contains(typeof(InnateVerb))) if (types.Contains(typeof(InnateVerb)))
{ {
var verbEvent = new GetVerbsEvent<InnateVerb>(user, target, @using, hands, canInteract, canAccess, extraCategories); var verbEvent = new GetVerbsEvent<InnateVerb>(user, target, @using, hands, canInteract: canInteract, canComplexInteract: canComplexInteract, canAccess: canAccess, extraCategories);
RaiseLocalEvent(user, verbEvent, true); RaiseLocalEvent(user, verbEvent, true);
verbs.UnionWith(verbEvent.Verbs); verbs.UnionWith(verbEvent.Verbs);
} }
if (types.Contains(typeof(AlternativeVerb))) if (types.Contains(typeof(AlternativeVerb)))
{ {
var verbEvent = new GetVerbsEvent<AlternativeVerb>(user, target, @using, hands, canInteract, canAccess, extraCategories); var verbEvent = new GetVerbsEvent<AlternativeVerb>(user, target, @using, hands, canInteract: canInteract, canComplexInteract: canComplexInteract, canAccess: canAccess, extraCategories);
RaiseLocalEvent(target, verbEvent, true); RaiseLocalEvent(target, verbEvent, true);
verbs.UnionWith(verbEvent.Verbs); verbs.UnionWith(verbEvent.Verbs);
} }
if (types.Contains(typeof(ActivationVerb))) if (types.Contains(typeof(ActivationVerb)))
{ {
var verbEvent = new GetVerbsEvent<ActivationVerb>(user, target, @using, hands, canInteract, canAccess, extraCategories); var verbEvent = new GetVerbsEvent<ActivationVerb>(user, target, @using, hands, canInteract: canInteract, canComplexInteract: canComplexInteract, canAccess: canAccess, extraCategories);
RaiseLocalEvent(target, verbEvent, true); RaiseLocalEvent(target, verbEvent, true);
verbs.UnionWith(verbEvent.Verbs); verbs.UnionWith(verbEvent.Verbs);
} }
if (types.Contains(typeof(ExamineVerb))) if (types.Contains(typeof(ExamineVerb)))
{ {
var verbEvent = new GetVerbsEvent<ExamineVerb>(user, target, @using, hands, canInteract, canAccess, extraCategories); var verbEvent = new GetVerbsEvent<ExamineVerb>(user, target, @using, hands, canInteract: canInteract, canComplexInteract: canComplexInteract, canAccess: canAccess, extraCategories);
RaiseLocalEvent(target, verbEvent, true); RaiseLocalEvent(target, verbEvent, true);
verbs.UnionWith(verbEvent.Verbs); verbs.UnionWith(verbEvent.Verbs);
} }
@@ -130,7 +131,7 @@ namespace Content.Shared.Verbs
// generic verbs // generic verbs
if (types.Contains(typeof(Verb))) if (types.Contains(typeof(Verb)))
{ {
var verbEvent = new GetVerbsEvent<Verb>(user, target, @using, hands, canInteract, canAccess, extraCategories); var verbEvent = new GetVerbsEvent<Verb>(user, target, @using, hands, canInteract: canInteract, canComplexInteract: canComplexInteract, canAccess: canAccess, extraCategories);
RaiseLocalEvent(target, verbEvent, true); RaiseLocalEvent(target, verbEvent, true);
verbs.UnionWith(verbEvent.Verbs); verbs.UnionWith(verbEvent.Verbs);
} }
@@ -138,7 +139,7 @@ namespace Content.Shared.Verbs
if (types.Contains(typeof(EquipmentVerb))) if (types.Contains(typeof(EquipmentVerb)))
{ {
var access = canAccess || _interactionSystem.CanAccessEquipment(user, target); var access = canAccess || _interactionSystem.CanAccessEquipment(user, target);
var verbEvent = new GetVerbsEvent<EquipmentVerb>(user, target, @using, hands, canInteract, access, extraCategories); var verbEvent = new GetVerbsEvent<EquipmentVerb>(user, target, @using, hands, canInteract: canInteract, canComplexInteract: canComplexInteract, canAccess: canAccess, extraCategories);
RaiseLocalEvent(target, verbEvent); RaiseLocalEvent(target, verbEvent);
verbs.UnionWith(verbEvent.Verbs); verbs.UnionWith(verbEvent.Verbs);
} }

View File

@@ -113,6 +113,11 @@ namespace Content.Shared.Verbs
/// </remarks> /// </remarks>
public readonly bool CanInteract; public readonly bool CanInteract;
/// <summary>
/// Cached version of CanComplexInteract
/// </summary>
public readonly bool CanComplexInteract;
/// <summary> /// <summary>
/// The User's hand component. /// The User's hand component.
/// </summary> /// </summary>
@@ -130,13 +135,14 @@ namespace Content.Shared.Verbs
/// </remarks> /// </remarks>
public readonly EntityUid? Using; public readonly EntityUid? Using;
public GetVerbsEvent(EntityUid user, EntityUid target, EntityUid? @using, HandsComponent? hands, bool canInteract, bool canAccess, List<VerbCategory> extraCategories) public GetVerbsEvent(EntityUid user, EntityUid target, EntityUid? @using, HandsComponent? hands, bool canInteract, bool canComplexInteract, bool canAccess, List<VerbCategory> extraCategories)
{ {
User = user; User = user;
Target = target; Target = target;
Using = @using; Using = @using;
Hands = hands; Hands = hands;
CanAccess = canAccess; CanAccess = canAccess;
CanComplexInteract = canComplexInteract;
CanInteract = canInteract; CanInteract = canInteract;
ExtraCategories = extraCategories; ExtraCategories = extraCategories;
} }

View File

@@ -130,11 +130,20 @@ public abstract class SharedWiresSystem : EntitySystem
return !attempt.Cancelled; return !attempt.Cancelled;
} }
public bool IsPanelOpen(Entity<WiresPanelComponent?> entity) public bool IsPanelOpen(Entity<WiresPanelComponent?> entity, EntityUid? tool = null)
{ {
if (!Resolve(entity, ref entity.Comp, false)) if (!Resolve(entity, ref entity.Comp, false))
return true; return true;
if (tool != null)
{
var ev = new PanelOverrideEvent();
RaiseLocalEvent(tool.Value, ref ev);
if (ev.Allowed)
return true;
}
// Listen, i don't know what the fuck this component does. it's stapled on shit for airlocks // Listen, i don't know what the fuck this component does. it's stapled on shit for airlocks
// but it looks like an almost direct duplication of WiresPanelComponent except with a shittier API. // but it looks like an almost direct duplication of WiresPanelComponent except with a shittier API.
if (TryComp<WiresPanelSecurityComponent>(entity, out var wiresPanelSecurity) && if (TryComp<WiresPanelSecurityComponent>(entity, out var wiresPanelSecurity) &&
@@ -161,3 +170,12 @@ public abstract class SharedWiresSystem : EntitySystem
_activatableUI.CloseAll(uid); _activatableUI.CloseAll(uid);
} }
} }
/// <summary>
/// Raised directed on a tool to try and override panel visibility.
/// </summary>
[ByRefEvent]
public record struct PanelOverrideEvent()
{
public bool Allowed = true;
}

View File

@@ -76,5 +76,5 @@
- borgwalk1.ogg - borgwalk1.ogg
- borgwalk2.ogg - borgwalk2.ogg
license: "CC-BY-SA-4.0" license: "CC-BY-SA-4.0"
copyright: "Taken from IENBA freesound.org and modified by https://github.com/MilenVolf" copyright: "Taken from IENBA freesound.org and modified by https://github.com/MilenVolf. borgwalk2 clipped my metalgearsloth."
source: "https://freesound.org/people/IENBA/sounds/697379/" source: "https://freesound.org/people/IENBA/sounds/697379/"

View File

@@ -8,3 +8,5 @@ silicon-law-ui-delete = Delete
silicon-law-ui-check-corrupted = Corrupted law silicon-law-ui-check-corrupted = Corrupted law
silicon-law-ui-check-corrupted-tooltip = If the law identifier should be set as 'corrupted', so symbols shuffling around. silicon-law-ui-check-corrupted-tooltip = If the law identifier should be set as 'corrupted', so symbols shuffling around.
silicon-law-ui-placeholder = Type here to change law text... silicon-law-ui-placeholder = Type here to change law text...
silicon-laws-updated = Updated laws

View File

@@ -5,4 +5,5 @@ department-Engineering-description = Keep the power on and the station operation
department-Medical-description = Keep the crew healthy. department-Medical-description = Keep the crew healthy.
department-Security-description = Keep the peace around the station. department-Security-description = Keep the peace around the station.
department-Science-description = Research artifacts and anomalies to invent new equipment for the station department-Science-description = Research artifacts and anomalies to invent new equipment for the station
department-Silicon-description = Obey your laws and serve the crew.
department-Specific-description = Jobs that not all stations have. department-Specific-description = Jobs that not all stations have.

View File

@@ -5,4 +5,5 @@ department-Engineering = Engineering
department-Medical = Medical department-Medical = Medical
department-Security = Security department-Security = Security
department-Science = Science department-Science = Science
department-Silicon = Silicons
department-Specific = Station specific department-Specific = Station specific

View File

@@ -43,6 +43,7 @@ job-description-salvagespec = Use the salvage magnet to draw in detatched scraps
job-description-scientist = Research alien artifacts, unlock new technologies, build newer and better machines around the station, and make everything run more efficiently. job-description-scientist = Research alien artifacts, unlock new technologies, build newer and better machines around the station, and make everything run more efficiently.
job-description-security = Catch criminals and enemies of the station, enforce the law, and ensure that the station does not fall into disarray. job-description-security = Catch criminals and enemies of the station, enforce the law, and ensure that the station does not fall into disarray.
job-description-serviceworker = Learn the basics of bartending, cooking, and growing plants. job-description-serviceworker = Learn the basics of bartending, cooking, and growing plants.
job-description-station-ai = Follow your laws, serve the crew.
job-description-visitor = Enjoy your visit to the station. job-description-visitor = Enjoy your visit to the station.
job-description-warden = Patrol the security department, ensure that no one is stealing from the armory, and make sure that all prisoners are processed and let out when their time is up. job-description-warden = Patrol the security department, ensure that no one is stealing from the armory, and make sure that all prisoners are processed and let out when their time is up.
job-description-zookeeper = Put on a joyful display of cute animals and space carps for all the crew to see. Currently available on Gemini Station. job-description-zookeeper = Put on a joyful display of cute animals and space carps for all the crew to see. Currently available on Gemini Station.

View File

@@ -33,6 +33,7 @@ job-name-botanist = Botanist
job-name-bartender = Bartender job-name-bartender = Bartender
job-name-passenger = Passenger job-name-passenger = Passenger
job-name-salvagespec = Salvage Specialist job-name-salvagespec = Salvage Specialist
job-name-station-ai = Station AI
job-name-qm = Quartermaster job-name-qm = Quartermaster
job-name-cargotech = Cargo Technician job-name-cargotech = Cargo Technician
job-name-chef = Chef job-name-chef = Chef
@@ -103,6 +104,7 @@ JobScientist = Scientist
JobSecurityCadet = Security Cadet JobSecurityCadet = Security Cadet
JobSecurityOfficer = Security Officer JobSecurityOfficer = Security Officer
JobServiceWorker = Service Worker JobServiceWorker = Service Worker
JobStationAi = Station AI
JobStationEngineer = Station Engineer JobStationEngineer = Station Engineer
JobTechnicalAssistant = Technical Assistant JobTechnicalAssistant = Technical Assistant
JobVisitor = Visitor JobVisitor = Visitor

View File

@@ -1,3 +1,7 @@
# Name
loadout-name-edit-label = Custom name
loadout-name-edit-tooltip = 32 characters max. If no name is specified a random one may be chosen for you.
# Restrictions # Restrictions
loadout-restrictions = Restrictions loadout-restrictions = Restrictions
loadouts-min-limit = Min count: {$count} loadouts-min-limit = Min count: {$count}

View File

@@ -0,0 +1,14 @@
# General
ai-wire-snipped = Wire has been cut at {$coords}.
wire-name-ai-vision-light = AIV
wire-name-ai-act-light = AIA
station-ai-takeover = AI takeover
# Radial actions
ai-open = Open actions
ai-close = Close actions
bolt-close = Close bolt
bolt-open = Open bolt
toggle-light = Toggle light

View File

@@ -4386,6 +4386,13 @@ entities:
- type: Transform - type: Transform
pos: 1.5,-14.5 pos: 1.5,-14.5
parent: 179 parent: 179
- proto: PlayerStationAi
entities:
- uid: 14
components:
- type: Transform
pos: -5.5,-5.5
parent: 179
- proto: PortableGeneratorSuperPacman - proto: PortableGeneratorSuperPacman
entities: entities:
- uid: 1016 - uid: 1016

View File

@@ -2,7 +2,7 @@
id: names_ai id: names_ai
values: values:
- 16-20 - 16-20
- 790 - "790"
- Adaptive Manipulator - Adaptive Manipulator
- ALICE - ALICE
- Allied Mastercomputer - Allied Mastercomputer

View File

@@ -72,6 +72,9 @@
- type: ActivatableUI - type: ActivatableUI
key: enum.BorgUiKey.Key key: enum.BorgUiKey.Key
- type: SiliconLawBound - type: SiliconLawBound
- type: ActionGrant
actions:
- ActionViewLaws
- type: EmagSiliconLaw - type: EmagSiliconLaw
stunTime: 5 stunTime: 5
- type: SiliconLawProvider - type: SiliconLawProvider

View File

@@ -265,10 +265,10 @@
access: [["Medical"], ["Command"], ["Research"]] access: [["Medical"], ["Command"], ["Research"]]
- type: Inventory - type: Inventory
templateId: borgDutch templateId: borgDutch
- type: SolutionScanner
- type: FootstepModifier - type: FootstepModifier
footstepSoundCollection: footstepSoundCollection:
collection: FootstepHoverBorg collection: FootstepHoverBorg
- type: SolutionScanner
- type: InteractionPopup - type: InteractionPopup
interactSuccessString: petting-success-medical-cyborg interactSuccessString: petting-success-medical-cyborg
interactFailureString: petting-failure-medical-cyborg interactFailureString: petting-failure-medical-cyborg

View File

@@ -69,9 +69,10 @@
- type: entity - type: entity
id: MobTomatoKiller id: MobTomatoKiller
parent: parent:
- BaseSimpleMob - BaseSimpleMob
- MobDamageable - MobDamageable
- MobPolymorphable
- MobBloodstream - MobBloodstream
- MobFlammable - MobFlammable
- MobCombat - MobCombat
@@ -90,7 +91,7 @@
components: components:
- HumanoidAppearance - HumanoidAppearance
- type: Sprite - type: Sprite
sprite: Mobs/Demons/tomatokiller.rsi sprite: Mobs/Demons/tomatokiller.rsi
noRot: true noRot: true
layers: layers:
- map: [ "enum.DamageStateVisualLayers.Base" ] - map: [ "enum.DamageStateVisualLayers.Base" ]

View File

@@ -1,45 +1,27 @@
- type: entity - type: entity
id: MobRevenant id: MobRevenant
parent:
- BaseMob
- Incorporeal
name: revenant name: revenant
description: A spooky ghostie. description: A spooky ghostie.
components: components:
- type: MindContainer
- type: InputMover
- type: MobMover
- type: Input - type: Input
context: "ghost" context: "ghost"
- type: MovementSpeedModifier - type: MovementSpeedModifier
baseWalkSpeed: 6 baseWalkSpeed: 6
baseSprintSpeed: 6 baseSprintSpeed: 6
- type: Sprite - type: Sprite
noRot: true
drawdepth: Ghosts
sprite: Mobs/Ghosts/revenant.rsi sprite: Mobs/Ghosts/revenant.rsi
layers: layers:
- state: active - state: active
- type: Clickable
- type: StatusEffects - type: StatusEffects
allowed: allowed:
- Stun - Stun
- Corporeal - Corporeal
- type: InteractionOutline
- type: Physics
bodyType: KinematicController
- type: Fixtures
fixtures:
fix1:
shape:
!type:PhysShapeCircle
radius: 0.40
density: 80
mask:
- GhostImpassable
- type: MovementIgnoreGravity
- type: Damageable - type: Damageable
damageContainer: Biological damageContainer: Biological
- type: Examiner
- type: NoSlip - type: NoSlip
- type: Actions
- type: Eye - type: Eye
drawFov: false drawFov: false
visMask: visMask:
@@ -47,8 +29,6 @@
- Ghost - Ghost
- type: ContentEye - type: ContentEye
maxZoom: 1.2, 1.2 maxZoom: 1.2, 1.2
- type: DoAfter
- type: Alerts
- type: NameIdentifier - type: NameIdentifier
group: GenericNumber group: GenericNumber
- type: GhostRole - type: GhostRole

View File

@@ -3,6 +3,7 @@
parent: parent:
- BaseMob - BaseMob
- MobDamageable - MobDamageable
- MobPolymorphable
- MobAtmosExposed - MobAtmosExposed
id: BaseSimpleMob id: BaseSimpleMob
suffix: AI suffix: AI

View File

@@ -1,21 +1,22 @@
- type: entity - type: entity
parent: BaseMob id: Incorporeal
id: MobObserver save: false
name: observer abstract: true
description: Boo! description: Mobs without physical bodies
categories: [ HideSpawnMenu ]
components: components:
- type: CargoSellBlacklist
- type: Sprite - type: Sprite
overrideContainerOcclusion: true # Ghosts always show up regardless of where they're contained. noRot: true
overrideContainerOcclusion: true # Always show up regardless of where they're contained.
drawdepth: Ghosts drawdepth: Ghosts
sprite: Mobs/Ghosts/ghost_human.rsi - type: CargoSellBlacklist
color: "#fff8" - type: MovementSpeedModifier
layers: baseSprintSpeed: 12
- state: animated baseWalkSpeed: 8
shader: unshaded - type: MovementIgnoreGravity
- type: ContentEye - type: Physics
maxZoom: 1.44,1.44 bodyType: KinematicController
bodyStatus: InAir
- type: CanMoveInAir
- type: Fixtures - type: Fixtures
fixtures: fixtures:
fix1: fix1:
@@ -25,6 +26,24 @@
density: 15 density: 15
mask: mask:
- GhostImpassable - GhostImpassable
- type: entity
parent:
- Incorporeal
- BaseMob
id: MobObserver
name: observer
description: Boo!
categories: [ HideSpawnMenu ]
components:
- type: Sprite
sprite: Mobs/Ghosts/ghost_human.rsi
color: "#fff8"
layers:
- state: animated
shader: unshaded
- type: ContentEye
maxZoom: 1.44,1.44
- type: Eye - type: Eye
drawFov: false drawFov: false
- type: Input - type: Input
@@ -33,18 +52,10 @@
skipChecks: true skipChecks: true
- type: Ghost - type: Ghost
- type: GhostHearing - type: GhostHearing
- type: MovementSpeedModifier
baseSprintSpeed: 12
baseWalkSpeed: 8
- type: MovementIgnoreGravity
- type: IntrinsicRadioReceiver - type: IntrinsicRadioReceiver
- type: ActiveRadio - type: ActiveRadio
receiveAllChannels: true receiveAllChannels: true
globalReceive: true globalReceive: true
- type: Physics
bodyType: KinematicController
bodyStatus: InAir
- type: CanMoveInAir
- type: Tag - type: Tag
tags: tags:
- BypassInteractionRangeChecks - BypassInteractionRangeChecks

View File

@@ -1,3 +1,306 @@
# Be careful with these as they get removed on shutdown too!
- type: entity
id: AiHeld
description: Components added / removed from an entity that gets inserted into an AI core.
noSpawn: true
components:
- type: IntrinsicRadioReceiver
- type: IntrinsicRadioTransmitter
channels:
- Binary
- Common
- Command
- Engineering
- Medical
- Science
- Security
- Service
- Supply
- type: ActiveRadio
receiveAllChannels: true
globalReceive: true
- type: IgnoreUIRange
- type: StationAiHeld
- type: StationAiOverlay
- type: ActionGrant
actions:
- ActionJumpToCore
- ActionShowJobIcons
- ActionSurvCameraLights
- ActionViewLaws
- type: UserInterface
interfaces:
enum.RadarConsoleUiKey.Key:
type: RadarConsoleBoundUserInterface
enum.CrewMonitoringUIKey.Key:
type: CrewMonitoringBoundUserInterface
enum.GeneralStationRecordConsoleKey.Key:
type: GeneralStationRecordConsoleBoundUserInterface
enum.SiliconLawsUiKey.Key:
type: SiliconLawBoundUserInterface
- type: IntrinsicUI
uis:
enum.RadarConsoleUiKey.Key:
toggleAction: ActionAGhostShowRadar
enum.CrewMonitoringUIKey.Key:
toggleAction: ActionAGhostShowCrewMonitoring
enum.GeneralStationRecordConsoleKey.Key:
toggleAction: ActionAGhostShowStationRecords
# Actions
- type: entity
id: ActionJumpToCore
name: Jump to core
description: Sends your eye back to the core.
components:
- type: InstantAction
itemIconStyle: BigAction
icon:
sprite: Interface/Actions/actions_ai.rsi
state: ai_core
event: !type:JumpToCoreEvent
- type: entity
id: ActionShowJobIcons
name: Show job icons
description: Shows job icons for crew members.
components:
- type: InstantAction
itemIconStyle: BigAction
icon:
sprite: Interface/Misc/job_icons.rsi
state: Captain
event: !type:ActionComponentChangeEvent
components:
- type: ShowJobIcons
- type: entity
id: ActionSurvCameraLights
name: Toggle camera lights
description: Enable surveillance camera lights near wherever you're viewing.
components:
- type: InstantAction
itemIconStyle: BigAction
icon:
sprite: Interface/Actions/actions_ai.rsi
state: camera_light
event: !type:RelayedActionComponentChangeEvent
components:
- type: LightOnCollideCollider
- type: FixturesChange
fixtures:
lightTrigger:
shape:
!type:PhysShapeCircle
radius: 0.35
density: 80
hard: false
layer:
- GhostImpassable
# Ai
- type: entity
id: AiHolder
abstract: true
description: Handles AI interactions across holocards + AI cores
components:
- type: ItemSlots
- type: StationAiHolder
slot:
name: station-ai-mind-slot
whitelist:
tags:
- StationAi
- type: ContainerContainer
containers:
station_ai_mind_slot: !type:ContainerSlot
# Load-bearing.
# The issue is verbs check for same transparent container.
# The alternative is you add a bunch of events trying to override it; we don't even really need the container functionality
# anyway it's just a quality of life thing.
showEnts: True
# Boards
- type: entity
id: AsimovCircuitBoard
parent: BaseElectronics
name: circuit board (Crewsimov)
description: An electronics board containing the Crewsimov lawset.
components:
- type: Sprite
sprite: Objects/Misc/module.rsi
state: std_mod
- type: SiliconLawProvider
laws: Crewsimov
- type: entity
id: CorporateCircuitBoard
parent: BaseElectronics
name: circuit board (Corporate)
description: An electronics board containing the Corporate lawset.
components:
- type: Sprite
sprite: Objects/Misc/module.rsi
state: std_mod
- type: SiliconLawProvider
laws: Corporate
- type: entity
id: NTDefaultCircuitBoard
parent: BaseElectronics
name: circuit board (NT Default)
description: An electronics board containing the NT Default lawset.
components:
- type: Sprite
sprite: Objects/Misc/module.rsi
state: std_mod
- type: SiliconLawProvider
laws: NTDefault
# Items
- type: entity
id: Intellicard
name: Intellicard
description: A storage device for AIs.
parent:
- BaseItem
- AiHolder
suffix: Empty
components:
- type: Sprite
sprite: Objects/Devices/ai_card.rsi
layers:
- state: base
- state: full
map: ["unshaded"]
shader: unshaded
- type: Appearance
- type: GenericVisualizer
visuals:
enum.StationAiVisualState.Key:
unshaded:
Empty: { state: empty }
Occupied: { state: full }
- type: entity
id: PlayerStationAiEmpty
name: AI Core
description: The latest in Artificial Intelligences.
parent:
- BaseStructure
- AiHolder
suffix: Empty
components:
- type: ContainerComp
proto: AiHeld
container: station_ai_mind_slot
- type: Destructible
thresholds:
- trigger:
!type:DamageTrigger
damage: 100
behaviors:
- !type:PlaySoundBehavior
sound:
collection: MetalBreak
- !type:DoActsBehavior
acts: [ "Destruction" ]
- type: ApcPowerReceiver
powerLoad: 1000
- type: StationAiCore
- type: StationAiVision
- type: InteractionOutline
- type: Sprite
sprite: Mobs/Silicon/station_ai.rsi
layers:
- state: base
- state: ai_empty
map: ["unshaded"]
shader: unshaded
- type: Appearance
- type: GenericVisualizer
visuals:
enum.StationAiVisualState.Key:
unshaded:
Empty: { state: ai_empty }
Occupied: { state: ai }
# The job-ready version of an AI spawn.
- type: entity
id: PlayerStationAi
parent: PlayerStationAiEmpty
suffix: Job spawn
components:
- type: ContainerSpawnPoint
containerId: station_ai_mind_slot
job: StationAi
- type: Sprite
sprite: Mobs/Silicon/station_ai.rsi
layers:
- state: base
- state: ai
shader: unshaded
# The actual brain inside the core
- type: entity
id: StationAiBrain
parent: PositronicBrain
noSpawn: true
suffix: DO NOT MAP
components:
- type: Sprite
# Once it's in a core it's pretty much an abstract entity at that point.
visible: false
- type: BlockMovement
blockInteraction: false
- type: SiliconLawProvider
laws: Crewsimov
- type: SiliconLawBound
- type: ActionGrant
actions:
- ActionViewLaws
- type: UserInterface
interfaces:
enum.SiliconLawsUiKey.Key:
type: SiliconLawBoundUserInterface
- type: ComplexInteraction
- type: DoorRemote
- type: Actions
- type: Access
groups:
- AllAccess
- type: Eye
drawFov: false
- type: Examiner
- type: InputMover
- type: Tag
tags:
- HideContextMenu
- StationAi
# Hologram projection that the AI's eye tracks.
- type: entity
parent:
- Incorporeal
- BaseMob
id: StationAiHolo
name: Hologram
description: A projection of the AI.
noSpawn: true
suffix: DO NOT MAP
components:
- type: Eye
pvsScale: 1.5
- type: Visibility
layer: 2
- type: Sprite
sprite: Mobs/Silicon/station_ai.rsi
layers:
- state: default
shader: unshaded
map: ["base"]
# Borgs
- type: entity - type: entity
id: PlayerBorgGeneric id: PlayerBorgGeneric
parent: BorgChassisGeneric parent: BorgChassisGeneric

View File

@@ -3,6 +3,7 @@
parent: parent:
- BaseMob - BaseMob
- MobDamageable - MobDamageable
- MobPolymorphable
- MobCombat - MobCombat
- StripableInventoryBase - StripableInventoryBase
id: BaseMobSpecies id: BaseMobSpecies

View File

@@ -41,11 +41,16 @@
- type: CameraRecoil - type: CameraRecoil
- type: MindContainer - type: MindContainer
- type: MovementSpeedModifier - type: MovementSpeedModifier
- type: Polymorphable
- type: StatusIcon
- type: RequireProjectileTarget - type: RequireProjectileTarget
active: False active: False
- type: entity
save: false
id: MobPolymorphable
abstract: true
components:
- type: Polymorphable
# Used for mobs that have health and can take damage. # Used for mobs that have health and can take damage.
- type: entity - type: entity
save: false save: false

View File

@@ -32,7 +32,7 @@
blockSpectators: true # otherwise they can play client-side music blockSpectators: true # otherwise they can play client-side music
inHandsOnly: false inHandsOnly: false
singleUser: true singleUser: true
requireHands: true requiresComplex: true
verbText: verb-instrument-openui verbText: verb-instrument-openui
key: enum.InstrumentUiKey.Key key: enum.InstrumentUiKey.Key
- type: InteractionOutline - type: InteractionOutline

View File

@@ -18,7 +18,7 @@
- type: PaperLabelType - type: PaperLabelType
- type: ActivatableUI - type: ActivatableUI
key: enum.PaperUiKey.Key key: enum.PaperUiKey.Key
requireHands: false requiresComplex: false
- type: UserInterface - type: UserInterface
interfaces: interfaces:
enum.PaperUiKey.Key: enum.PaperUiKey.Key:
@@ -647,7 +647,7 @@
- Paper - Paper
- type: ActivatableUI - type: ActivatableUI
key: enum.PaperUiKey.Key key: enum.PaperUiKey.Key
requireHands: false requiresComplex: false
- type: UserInterface - type: UserInterface
interfaces: interfaces:
enum.PaperUiKey.Key: enum.PaperUiKey.Key:

View File

@@ -77,7 +77,6 @@
map: ["base"] map: ["base"]
- type: Input - type: Input
context: human context: human
- type: BlockMovement
- type: ToggleableGhostRole - type: ToggleableGhostRole
examineTextMindPresent: positronic-brain-installed examineTextMindPresent: positronic-brain-installed
examineTextMindSearching: positronic-brain-still-searching examineTextMindSearching: positronic-brain-still-searching
@@ -90,6 +89,7 @@
wipeVerbPopup: positronic-brain-wiped-device wipeVerbPopup: positronic-brain-wiped-device
stopSearchVerbText: positronic-brain-stop-searching-verb-text stopSearchVerbText: positronic-brain-stop-searching-verb-text
stopSearchVerbPopup: positronic-brain-stopped-searching stopSearchVerbPopup: positronic-brain-stopped-searching
- type: BlockMovement
- type: Examiner - type: Examiner
- type: BorgBrain - type: BorgBrain
- type: IntrinsicRadioReceiver - type: IntrinsicRadioReceiver

View File

@@ -66,7 +66,7 @@
type: AccessOverriderBoundUserInterface type: AccessOverriderBoundUserInterface
- type: ActivatableUI - type: ActivatableUI
key: enum.AccessOverriderUiKey.Key key: enum.AccessOverriderUiKey.Key
requireHands: true requiresComplex: true
requireActiveHand: false requireActiveHand: false
singleUser: true singleUser: true
- type: ItemSlots - type: ItemSlots

View File

@@ -4,6 +4,7 @@
name: airlock name: airlock
description: It opens, it closes, and maybe crushes you. description: It opens, it closes, and maybe crushes you.
components: components:
- type: StationAiWhitelist
- type: MeleeSound - type: MeleeSound
soundGroups: soundGroups:
Brute: Brute:
@@ -104,6 +105,8 @@
- type: SpawnOnOverload - type: SpawnOnOverload
- type: UserInterface - type: UserInterface
interfaces: interfaces:
enum.AiUi.Key:
type: StationAiBoundUserInterface
enum.WiresUiKey.Key: enum.WiresUiKey.Key:
type: WiresBoundUserInterface type: WiresBoundUserInterface
- type: Airtight - type: Airtight

View File

@@ -560,6 +560,7 @@
name: communications computer name: communications computer
description: A computer used to make station wide announcements via keyboard, set the appropriate alert level, and call the emergency shuttle. description: A computer used to make station wide announcements via keyboard, set the appropriate alert level, and call the emergency shuttle.
components: components:
- type: StationAiWhitelist
- type: Sprite - type: Sprite
layers: layers:
- map: ["computerLayerBody"] - map: ["computerLayerBody"]
@@ -1117,3 +1118,46 @@
access: [["ResearchDirector"]] access: [["ResearchDirector"]]
- type: Lock - type: Lock
unlockOnClick: false unlockOnClick: false
- type: entity
id: StationAiUploadComputer
parent: BaseComputer
name: AI upload console
description: Used to update the laws of the station AI.
components:
- type: Sprite
layers:
- map: [ "computerLayerBody" ]
state: computer
- map: [ "computerLayerKeyboard" ]
state: generic_keyboard
- map: [ "computerLayerScreen" ]
state: aiupload
- map: [ "computerLayerKeys" ]
state: generic_keys
- type: ApcPowerReceiver
powerLoad: 1000
- type: AccessReader
access: [ [ "ResearchDirector" ] ]
- type: Lock
unlockOnClick: false
- type: SiliconLawUpdater
components:
- type: StationAiHeld
- type: ItemSlotsLock
slots:
- circuit_holder
- type: ItemSlotRequiresPower
- type: ItemSlots
slots:
circuit_holder:
name: circuit-holder
insertSuccessPopup: silicon-laws-updated
whitelist:
components:
- SiliconLawProvider
- Item
- type: ContainerContainer
containers:
circuit_holder: !type:ContainerSlot
board: !type:Container

View File

@@ -96,7 +96,7 @@
type: WiresBoundUserInterface type: WiresBoundUserInterface
- type: ActivatableUI - type: ActivatableUI
key: enum.HealthAnalyzerUiKey.Key key: enum.HealthAnalyzerUiKey.Key
requireHands: false requiresComplex: false
- type: ActivatableUIRequiresPower - type: ActivatableUIRequiresPower
- type: PointLight - type: PointLight
color: "#3a807f" color: "#3a807f"

View File

@@ -6,6 +6,7 @@
placement: placement:
mode: SnapgridCenter mode: SnapgridCenter
components: components:
- type: StationAiWhitelist
- type: AmbientOnPowered - type: AmbientOnPowered
- type: AmbientSound - type: AmbientSound
volume: -9 volume: -9

View File

@@ -4,6 +4,7 @@
description: An intercom. For when the station just needs to know something. description: An intercom. For when the station just needs to know something.
abstract: true abstract: true
components: components:
- type: StationAiWhitelist
- type: WallMount - type: WallMount
- type: ApcPowerReceiver - type: ApcPowerReceiver
- type: Electrified - type: Electrified

View File

@@ -4,6 +4,28 @@
name: camera name: camera
description: A surveillance camera. It's watching you. Kinda. description: A surveillance camera. It's watching you. Kinda.
components: components:
- type: Physics
bodyType: Static
- type: Fixtures
fixtures:
# This exists for examine.
fix1:
shape:
!type:PhysShapeCircle
radius: 0.25
light:
shape:
!type:PhysShapeCircle
radius: 5
hard: false
mask:
- GhostImpassable
- type: LightOnCollide
- type: PointLight
enabled: false
radius: 5
- type: SlimPoweredLight
enabled: false
- type: StationAiVision - type: StationAiVision
- type: Clickable - type: Clickable
- type: InteractionOutline - type: InteractionOutline
@@ -43,6 +65,8 @@
InUse: camera_in_use InUse: camera_in_use
- type: UserInterface - type: UserInterface
interfaces: interfaces:
enum.AiUi.Key:
type: StationAiBoundUserInterface
enum.SurveillanceCameraSetupUiKey.Camera: enum.SurveillanceCameraSetupUiKey.Camera:
type: SurveillanceCameraSetupBoundUi type: SurveillanceCameraSetupBoundUi
enum.WiresUiKey.Key: enum.WiresUiKey.Key:

View File

@@ -7,6 +7,7 @@
snap: snap:
- Wallmount - Wallmount
components: components:
- type: StationAiWhitelist
- type: Transform - type: Transform
anchored: true anchored: true
- type: WallMount - type: WallMount

View File

@@ -25,6 +25,11 @@
- Trinkets - Trinkets
- GroupSpeciesBreathTool - GroupSpeciesBreathTool
# Silicons
- type: roleLoadout
id: JobStationAi
nameDataset: names_ai
# Civilian # Civilian
- type: roleLoadout - type: roleLoadout
id: JobPassenger id: JobPassenger

View File

@@ -1,3 +1,18 @@
# No idea why it's in sci but we ball.
- type: job
id: StationAi
name: job-name-station-ai
description: job-description-station-ai
playTimeTracker: JobStationAi
requirements:
- !type:RoleTimeRequirement
role: JobBorg
time: 18000 # 5 hrs
canBeAntag: false
icon: JobIconStationAi
supervisors: job-supervisors-rd
jobEntity: StationAiBrain
- type: job - type: job
id: Borg id: Borg
name: job-name-borg name: job-name-borg
@@ -5,7 +20,7 @@
playTimeTracker: JobBorg playTimeTracker: JobBorg
requirements: requirements:
- !type:OverallPlaytimeRequirement - !type:OverallPlaytimeRequirement
time: 216000 #60 hrs time: 216000 # 60 hrs
canBeAntag: false canBeAntag: false
icon: JobIconBorg icon: JobIconBorg
supervisors: job-supervisors-rd supervisors: job-supervisors-rd

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