[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly ISharedPlayerManager _playerManager = default!;
- private readonly SpriteSystem _spriteSystem;
- private readonly EntityWhitelistSystem _whitelistSystem;
-
public event Action<ProtoId<EmotePrototype>>? OnPlayEmote;
public EmotesMenu()
IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this);
- _spriteSystem = _entManager.System<SpriteSystem>();
- _whitelistSystem = _entManager.System<EntityWhitelistSystem>();
+ var spriteSystem = _entManager.System<SpriteSystem>();
+ var whitelistSystem = _entManager.System<EntityWhitelistSystem>();
var main = FindControl<RadialContainer>("Main");
var player = _playerManager.LocalSession?.AttachedEntity;
if (emote.Category == EmoteCategory.Invalid ||
emote.ChatTriggers.Count == 0 ||
- !(player.HasValue && _whitelistSystem.IsWhitelistPassOrNull(emote.Whitelist, player.Value)) ||
- _whitelistSystem.IsBlacklistPass(emote.Blacklist, player.Value))
+ !(player.HasValue && whitelistSystem.IsWhitelistPassOrNull(emote.Whitelist, player.Value)) ||
+ whitelistSystem.IsBlacklistPass(emote.Blacklist, player.Value))
continue;
if (!emote.Available &&
{
VerticalAlignment = VAlignment.Center,
HorizontalAlignment = HAlignment.Center,
- Texture = _spriteSystem.Frame0(emote.Icon),
+ Texture = spriteSystem.Frame0(emote.Icon),
TextureScale = new Vector2(2f, 2f),
};
<controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
+ xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
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"
- VerticalExpand="True"
- HorizontalExpand="True">
+ VerticalExpand="True"
+ HorizontalExpand="True">
</VerticalTabContainer>
+ </BoxContainer>
</controls:FancyWindow>
+using System.Numerics;
using Content.Client.UserInterface.Controls;
using Content.Shared.Preferences;
using Content.Shared.Preferences.Loadouts;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
+using Robust.Shared.Random;
namespace Content.Client.Lobby.UI.Loadouts;
Profile = profile;
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))
- continue;
+ LoadoutGroupsContainer.Visible = false;
+ SetSize = Vector2.Zero;
+ }
+ else
+ {
+ foreach (var group in proto.Groups)
+ {
+ if (!protoManager.TryIndex(group, out var groupProto))
+ continue;
- if (groupProto.Hidden)
- continue;
+ if (groupProto.Hidden)
+ continue;
- var container = new LoadoutGroupContainer(profile, loadout, protoManager.Index(group), session, collection);
- LoadoutGroupsContainer.AddTab(container, Loc.GetString(groupProto.Name));
- _groups.Add(container);
+ 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.OnLoadoutPressed += args =>
+ {
+ OnLoadoutPressed?.Invoke(group, args);
+ };
- container.OnLoadoutUnpressed += args =>
- {
- OnLoadoutUnpressed?.Invoke(group, args);
- };
+ container.OnLoadoutUnpressed += args =>
+ {
+ OnLoadoutUnpressed?.Invoke(group, args);
+ };
+ }
}
}
public sealed class SiliconLawEui : BaseEui
{
- public readonly EntityManager _entityManager = default!;
+ private readonly EntityManager _entityManager;
private SiliconLawUi _siliconLawUi;
private EntityUid _target;
--- /dev/null
+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,
+ });
+ };
+ }
+}
--- /dev/null
+<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>
--- /dev/null
+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;
+}
using Robust.Client.Player;
using Robust.Shared.Enums;
using Robust.Shared.Map.Components;
+using Robust.Shared.Physics;
using Robust.Shared.Prototypes;
+using Robust.Shared.Timing;
namespace Content.Client.Silicons.StationAi;
{
[Dependency] private readonly IClyde _clyde = default!;
[Dependency] private readonly IEntityManager _entManager = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
private IRenderTexture? _staticTexture;
private IRenderTexture? _stencilTexture;
+ private float _updateRate = 1f / 30f;
+ private float _accumulator;
+
public StationAiOverlay()
{
IoCManager.InjectDependencies(this);
_entManager.TryGetComponent(playerEnt, out TransformComponent? playerXform);
var gridUid = playerXform?.GridUid ?? EntityUid.Invalid;
_entManager.TryGetComponent(gridUid, out MapGridComponent? grid);
+ _entManager.TryGetComponent(gridUid, out BroadphaseComponent? broadphase);
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 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 matty = Matrix3x2.Multiply(gridMatrix, invMatrix);
--- /dev/null
+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,
+ }
+ });
+ }
+}
--- /dev/null
+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
+ }
+ });
+ }
+}
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 IPlayerManager _player = default!;
public override void Initialize()
{
base.Initialize();
- // InitializeAirlock();
- // InitializePowerToggle();
+ InitializeAirlock();
+ InitializePowerToggle();
SubscribeLocalEvent<StationAiOverlayComponent, LocalPlayerAttachedEvent>(OnAiAttached);
SubscribeLocalEvent<StationAiOverlayComponent, LocalPlayerDetachedEvent>(OnAiDetached);
// Get entities
List<EntityUid> entities;
+ var examineFlags = LookupFlags.All & ~LookupFlags.Sensors;
// Do we have to do FoV checks?
if ((visibility & MenuVisibility.NoFov) == 0)
TryComp(player.Value, out ExaminerComponent? examiner);
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))
entities.Add(ent);
}
else
{
- entities = _entityLookup.GetEntitiesInRange(targetPos, EntityMenuLookupSize).ToList();
+ entities = _entityLookup.GetEntitiesInRange(targetPos, EntityMenuLookupSize, flags: examineFlags).ToList();
}
if (entities.Count == 0)
using System.Linq;
using System.Numerics;
using Content.Server.Silicons.Laws;
+using Content.Shared.Silicons.Laws;
using Content.Shared.Silicons.Laws.Components;
using Robust.Server.Player;
using Robust.Shared.Physics.Components;
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))
{
return _accessReaderSystem.IsAllowed(user, console, accessReaderComponent);
foreach (var role in profile.Loadouts)
{
- var loadout = new RoleLoadout(role.RoleName);
+ var loadout = new RoleLoadout(role.RoleName)
+ {
+ };
foreach (var group in role.Groups)
{
using Content.Server.DeviceNetwork.Components;
using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems;
-using Content.Shared.Power.EntitySystems;
namespace Content.Server.DeviceNetwork.Systems;
using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems;
using Content.Shared.Power;
-using Content.Shared.Power.Components;
namespace Content.Server.Light.EntitySystems
{
using Content.Shared.Damage.Systems;
using Content.Shared.Damage.Components;
using Content.Shared.Power;
-using Content.Shared.Power.Components;
namespace Content.Server.Light.EntitySystems
{
}
}
- public void ControlMob(EntityUid user, EntityUid target)
+ public override void ControlMob(EntityUid user, EntityUid target)
{
if (TryComp(user, out ActorComponent? actor))
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);
using JetBrains.Annotations;
using Robust.Shared.Containers;
using System.Diagnostics.CodeAnalysis;
-using Content.Shared.Power.Components;
using Content.Shared.Storage.Components;
using Robust.Server.Containers;
using Content.Shared.Whitelist;
}
else
{
- // add how much each item is charged it
+ // add how much each item is charged it
foreach (var contained in container.ContainedEntities)
{
if (!TryComp<BatteryComponent>(contained, out var battery))
return CellChargerStatus.Charging;
}
-
+
private void TransferPower(EntityUid uid, EntityUid targetEntity, ChargerComponent component, float frameTime)
{
if (!TryComp(uid, out ApcPowerReceiverComponent? receiverComponent))
using Content.Server.Power.Pow3r;
using Content.Shared.CCVar;
using Content.Shared.Power;
-using Content.Shared.Power.Components;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Shared.Configuration;
using Content.Shared.DeviceNetwork;
using Content.Shared.Examine;
using Content.Shared.Power;
-using Content.Shared.Power.Components;
using Content.Shared.Power.Generation.Teg;
using Content.Shared.Rounding;
using Robust.Server.GameObjects;
using Content.Server.Power.Components;
using Content.Shared.Atmos;
using Content.Shared.Power;
-using Content.Shared.Power.Components;
namespace Content.Server.Power.Generator;
using System.Threading.Tasks;
using Content.Server.NPC.Pathfinding;
using Content.Shared.Maps;
+using Content.Shared.NPC;
using Content.Shared.Procedural;
using Content.Shared.Procedural.DungeonGenerators;
using Robust.Shared.Collections;
var pathfinder = _entManager.System<PathfindingSystem>();
// Gridcast
- pathfinder.GridCast(startTile, position, tile =>
+ SharedPathfindingSystem.GridCast(startTile, position, tile =>
{
if (!_maps.TryGetTileRef(_gridUid, _grid, tile, out var tileRef) ||
tileRef.Tile.IsSpace(_tileDefManager))
using Content.Server.Radio.Components;
using Content.Server.Roles;
using Content.Server.Station.Systems;
-using Content.Shared.Actions;
using Content.Shared.Administration;
using Content.Shared.Chat;
using Content.Shared.Emag.Components;
using Content.Shared.Emag.Systems;
-using Content.Shared.Examine;
using Content.Shared.Mind;
using Content.Shared.Mind.Components;
using Content.Shared.Roles;
using Content.Shared.Stunnable;
using Content.Shared.Wires;
using Robust.Server.GameObjects;
+using Robust.Shared.Containers;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Toolshed;
-using Robust.Shared.Utility;
namespace Content.Server.Silicons.Laws;
[Dependency] private readonly IChatManager _chatManager = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly SharedMindSystem _mind = default!;
- [Dependency] private readonly SharedActionsSystem _actions = default!;
[Dependency] private readonly StationSystem _station = default!;
[Dependency] private readonly UserInterfaceSystem _userInterface = default!;
[Dependency] private readonly SharedStunSystem _stunSystem = default!;
- [Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly SharedRoleSystem _roles = default!;
/// <inheritdoc/>
{
base.Initialize();
- SubscribeLocalEvent<SiliconLawBoundComponent, ComponentShutdown>(OnComponentShutdown);
SubscribeLocalEvent<SiliconLawBoundComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<SiliconLawBoundComponent, MindAddedMessage>(OnMindAdded);
SubscribeLocalEvent<SiliconLawBoundComponent, ToggleLawsScreenEvent>(OnToggleLawsScreen);
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)
{
- _actions.AddAction(uid, ref component.ViewLawsActionEntity, component.ViewLawsAction);
GetLaws(uid, component);
}
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 state = new SiliconLawBuiState(GetLaws(uid).Laws, radioChannels);
/// <summary>
/// Extract all the laws from a lawset's prototype ids.
/// </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()
{
Laws = new List<SiliconLaw>(proto.Laws.Count)
component.Lawset.Laws = newLaws;
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)]
--- /dev/null
+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)
+ {
+ }
+}
--- /dev/null
+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.
+ }
+}
--- /dev/null
+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.
+ }
+}
using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems;
using Content.Shared.Power;
-using Content.Shared.Power.Components;
using Content.Shared.Sound;
using Content.Shared.Sound.Components;
using Content.Shared.PDA;
using Content.Shared.Preferences;
using Content.Shared.Preferences.Loadouts;
+using Content.Shared.Preferences.Loadouts.Effects;
using Content.Shared.Random;
using Content.Shared.Random.Helpers;
using Content.Shared.Roles;
EntityUid? entity = null)
{
_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 (prototype?.JobEntity != null)
DebugTools.Assert(entity is null);
var jobEntity = EntityManager.SpawnEntity(prototype.JobEntity, coordinates);
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);
_identity.QueueIdentityUpdate(jobEntity);
return jobEntity;
profile = HumanoidCharacterProfile.RandomWithSpecies(speciesId);
}
- var jobLoadout = LoadoutSystem.GetJobPrototype(prototype?.ID);
-
- if (_prototypeManager.TryIndex(jobLoadout, out RoleLoadoutPrototype? roleProto))
+ if (loadout != null)
{
- RoleLoadout? loadout = null;
- 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);
+ EquipRoleLoadout(entity.Value, loadout, roleProto!);
}
if (prototype?.StartingGear != null)
if (args.Handled)
return;
- if (!_toolSystem.HasQuality(args.Used, "Pulsing"))
+ if (!_toolSystem.HasQuality(args.Used, SharedToolSystem.PulseQuality))
return;
args.Handled = true;
if (args.Handled)
return;
- if (!_toolSystem.HasQuality(args.Used, "Pulsing"))
+ if (!_toolSystem.HasQuality(args.Used, SharedToolSystem.PulseQuality))
return;
args.Handled = _artifactSystem.TryActivateArtifact(uid, args.User);
using Content.Shared.Emoting;
using Content.Shared.Hands;
using Content.Shared.Interaction;
+using Content.Shared.Interaction.Components;
using Content.Shared.Interaction.Events;
using Content.Shared.Item;
using Content.Shared.Movement.Components;
{
[Dependency] private readonly SharedContainerSystem _container = default!;
+ private EntityQuery<ComplexInteractionComponent> _complexInteractionQuery;
+
public override void Initialize()
{
base.Initialize();
+
+ _complexInteractionQuery = GetEntityQuery<ComplexInteractionComponent>();
+
SubscribeLocalEvent<InputMoverComponent, ComponentStartup>(OnMoverStartup);
}
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>
/// Raises an event directed at both the user and the target entity to check whether a user is capable of
/// interacting with this entity.
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)
- 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;
- }
+ return;
}
}
+ // TODO: Is this even needed anymore?
foreach (var otherFixture in args.OurFixture.Contacts.Keys)
{
// If it's the other fixture then ignore em
using System.Text.RegularExpressions;
using Content.Shared.Tools;
+using Content.Shared.Tools.Systems;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
public Dictionary<string, string?> Config = new();
[DataField("qualityNeeded", customTypeSerializer: typeof(PrototypeIdSerializer<ToolQualityPrototype>))]
- public string QualityNeeded = "Pulsing";
+ public string QualityNeeded = SharedToolSystem.PulseQuality;
[DataField("validation")]
public Regex Validation = new("^[a-zA-Z0-9 ]*$", RegexOptions.Compiled);
PowerIndicator,
BoltIndicator,
BoltLightIndicator,
- AIControlIndicator,
+ AiControlIndicator,
TimingIndicator,
SafetyIndicator,
}
if (EntityManager.GetComponent<TransformComponent>(examiner).MapID != target.MapId)
return false;
- return InRangeUnOccluded(
- _transform.GetMapCoordinates(examiner),
- target,
- GetExaminerRange(examiner),
- predicate: predicate,
- ignoreInsideBlocker: true);
+ // Do target InRangeUnoccluded which has different checks.
+ if (examined != null)
+ {
+ return InRangeUnOccluded(
+ examiner,
+ examined.Value,
+ GetExaminerRange(examiner),
+ predicate: predicate,
+ ignoreInsideBlocker: true);
+ }
+ else
+ {
+ return InRangeUnOccluded(
+ examiner,
+ target,
+ GetExaminerRange(examiner),
+ predicate: predicate,
+ ignoreInsideBlocker: true);
+ }
}
/// <summary>
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 otherPos = _transform.GetMapCoordinates(other);
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)
{
if (!Resolve(entity, ref entity.Comp))
using Content.Shared.Movement.Pulling.Systems;
using Content.Shared.Physics;
using Content.Shared.Popups;
+using Content.Shared.Silicons.StationAi;
using Content.Shared.Storage;
using Content.Shared.Tag;
using Content.Shared.Timing;
private EntityQuery<WallMountComponent> _wallMountQuery;
private EntityQuery<UseDelayComponent> _delayQuery;
private EntityQuery<ActivatableUIComponent> _uiQuery;
- private EntityQuery<ComplexInteractionComponent> _complexInteractionQuery;
private const CollisionGroup InRangeUnobstructedMask = CollisionGroup.Impassable | CollisionGroup.InteractImpassable;
_wallMountQuery = GetEntityQuery<WallMountComponent>();
_delayQuery = GetEntityQuery<UseDelayComponent>();
_uiQuery = GetEntityQuery<ActivatableUIComponent>();
- _complexInteractionQuery = GetEntityQuery<ComplexInteractionComponent>();
SubscribeLocalEvent<BoundUserInterfaceCheckRangeEvent>(HandleUserInterfaceRangeCheck);
SubscribeLocalEvent<BoundUserInterfaceMessageAttempt>(OnBoundInterfaceInteractAttempt);
return;
}
- if (uiComp.RequireHands && !_handsQuery.HasComp(ev.Actor))
+ if (uiComp.RequiresComplex && !_actionBlockerSystem.CanComplexInteract(ev.Actor))
ev.Cancel();
}
public void InteractHand(EntityUid user, EntityUid target)
{
- var complexInteractions = SupportsComplexInteractions(user);
+ var complexInteractions = _actionBlockerSystem.CanComplexInteract(user);
if (!complexInteractions)
{
InteractionActivate(user,
if (!Resolve(other, ref other.Comp))
return false;
+ var ev = new InRangeOverrideEvent(origin, other);
+ RaiseLocalEvent(origin, ref ev);
+
+ if (ev.Handled)
+ {
+ return ev.InRange;
+ }
+
return InRangeUnobstructed(origin,
other,
other.Comp.Coordinates,
// Get list of alt-interact verbs
var verbs = _verbSystem.GetLocalVerbs(target, user, typeof(AlternativeVerb));
- if (!verbs.Any())
+ if (verbs.Count == 0)
return false;
_verbSystem.ExecuteVerb(verbs.First(), user, target);
/// </summary>
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))
return true;
return ev.Handled;
}
- /// <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>
+ [Obsolete("Use ActionBlockerSystem")]
public bool SupportsComplexInteractions(EntityUid user)
{
- return _complexInteractionQuery.HasComp(user);
+ return _actionBlockerSystem.CanComplexInteract(user);
}
}
};
/// <summary>
- /// Raised directed by-ref on an item and a user to determine if interactions can occur.
+ /// 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.
/// </summary>
/// <param name="Cancelled">Whether the hand interaction should be cancelled.</param>
[ByRefEvent]
- public record struct AttemptUseInteractEvent(EntityUid User, EntityUid Used, bool Cancelled = false);
+ public record struct CombatModeShouldHandInteractEvent(bool Cancelled = false);
/// <summary>
- /// 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.
+ /// Override event raised directed on the user to say the target is accessible.
/// </summary>
- /// <param name="Cancelled">Whether the hand interaction should be cancelled.</param>
+ /// <param name="User"></param>
+ /// <param name="Target"></param>
[ByRefEvent]
- public record struct CombatModeShouldHandInteractEvent(bool Cancelled = false);
+ 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;
+ }
}
--- /dev/null
+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";
+}
--- /dev/null
+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
+{
+}
--- /dev/null
+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);
+ }
+}
using Content.Shared.Light.Components;
using Content.Shared.Power;
-using Content.Shared.Power.Components;
using Content.Shared.Power.EntitySystems;
namespace Content.Shared.Light.EntitySystems;
{
}
+ public virtual void ControlMob(EntityUid user, EntityUid target) {}
+
+ public virtual void ControlMob(NetUserId user, EntityUid target) {}
+
/// <summary>
/// Tries to create and add an objective from its prototype id.
/// </summary>
-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
// declare all locals at the top so it's obvious how big the footprint is
namespace Content.Shared.NPC;
-public abstract class SharedPathfindingSystem : EntitySystem
+public abstract partial class SharedPathfindingSystem : EntitySystem
{
/// <summary>
/// This is equivalent to agent radii for navmeshes. In our case it's preferable that things are cleanly
var ab = Vector2.Abs(diff);
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();
+ }
+ }
}
+using Content.Shared.Dataset;
using Robust.Shared.Prototypes;
namespace Content.Shared.Preferences.Loadouts;
[IdDataField]
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>
/// Groups that comprise this role loadout.
/// </summary>
- [DataField(required: true)]
+ [DataField]
public List<ProtoId<LoadoutGroupPrototype>> Groups = new();
/// <summary>
using Content.Shared.Actions;
+using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
/// <summary>
/// This is used for entities which are bound to silicon laws and can view them.
/// </summary>
-[RegisterComponent, Access(typeof(SharedSiliconLawSystem))]
+[RegisterComponent, NetworkedComponent, Access(typeof(SharedSiliconLawSystem))]
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>
/// The last entity that provided laws to this entity.
/// </summary>
--- /dev/null
+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;
+}
--- /dev/null
+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
+ }
+}
/// <summary>
/// This handles getting and displaying the laws for silicons.
/// </summary>
-public abstract class SharedSiliconLawSystem : EntitySystem
+public abstract partial class SharedSiliconLawSystem : EntitySystem
{
[Dependency] private readonly SharedPopupSystem _popup = default!;
/// <inheritdoc/>
public override void Initialize()
{
+ InitializeUpdater();
SubscribeLocalEvent<EmagSiliconLawComponent, GotEmaggedEvent>(OnGotEmagged);
SubscribeLocalEvent<EmagSiliconLawComponent, OnAttemptEmagEvent>(OnAttemptEmag);
}
--- /dev/null
+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;
+}
--- /dev/null
+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,
+}
--- /dev/null
+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;
+}
--- /dev/null
+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,
+}
--- /dev/null
+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";
+}
--- /dev/null
+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;
--- /dev/null
+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();
+}
+using Content.Shared.Silicons.StationAi;
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
{
[DataField, AutoNetworkedField]
+using Content.Shared.StationAi;
using Robust.Shared.Map.Components;
+using Robust.Shared.Physics;
using Robust.Shared.Threading;
using Robust.Shared.Utility;
private readonly HashSet<Entity<StationAiVisionComponent>> _seeds = new();
private readonly HashSet<Vector2i> _viewportTiles = new();
+ private EntityQuery<OccluderComponent> _occluderQuery;
+
// Dummy set
private readonly HashSet<Vector2i> _singleTiles = new();
/// </summary>
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()
{
base.Initialize();
+ _occluderQuery = GetEntityQuery<OccluderComponent>();
+
_seedJob = new()
{
System = this,
/// <summary>
/// Returns whether a tile is accessible based on vision.
/// </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();
_opaque.Clear();
_seeds.Clear();
_viewportTiles.Add(tile);
- var localBounds = _lookup.GetLocalBounds(tile, grid.Comp.TileSize);
+ var localBounds = _lookup.GetLocalBounds(tile, grid.Comp2.TileSize);
var expandedBounds = localBounds.Enlarged(expansionSize);
- _seedJob.Grid = grid;
+ _seedJob.Grid = (grid.Owner, grid.Comp2);
_seedJob.ExpandedBounds = expandedBounds;
_parallel.ProcessNow(_seedJob);
_job.Data.Clear();
_job.BoundaryTiles.Add(new HashSet<Vector2i>());
}
- _job.TargetTile = tile;
- TargetFound = false;
_singleTiles.Clear();
- _job.Grid = grid;
+ _job.Grid = (grid.Owner, grid.Comp2);
_job.VisibleTiles = _singleTiles;
_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();
- _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;
foreach (var occluder in _occluders)
/// Gets a byond-equivalent for tiles in the specified worldAABB.
/// </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>
- 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();
_opaque.Clear();
_seeds.Clear();
- var expandedBounds = worldBounds.Enlarged(expansionSize);
// TODO: Would be nice to be able to run this while running the other stuff.
- _seedJob.Grid = grid;
- var localAABB = _xforms.GetInvWorldMatrix(grid).TransformBox(expandedBounds);
- _seedJob.ExpandedBounds = localAABB;
+ _seedJob.Grid = (grid.Owner, grid.Comp2);
+ var invMatrix = _xforms.GetInvWorldMatrix(grid);
+ var localAabb = invMatrix.TransformBox(worldBounds);
+ var enlargedLocalAabb = invMatrix.TransformBox(worldBounds.Enlarged(expansionSize));
+ _seedJob.ExpandedBounds = enlargedLocalAabb;
_parallel.ProcessNow(_seedJob);
_job.Data.Clear();
FastPath = false;
return;
// 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))
{
_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))
{
if (_viewportTiles.Contains(tileRef.GridIndices))
_job.BoundaryTiles.Add(new HashSet<Vector2i>());
}
- _job.TargetTile = null;
- TargetFound = false;
- _job.Grid = grid;
+ _job.Grid = (grid.Owner, grid.Comp2);
_job.VisibleTiles = visibleTiles;
_parallel.ProcessNow(_job, _job.Data.Count);
}
return false;
}
+ /// <summary>
/// Checks whether this tile fits the definition of a "corner"
/// </summary>
private bool IsCorner(
public void Execute()
{
- System._lookup.GetLocalEntitiesIntersecting(Grid.Owner, ExpandedBounds, System._seeds);
+ System._lookup.GetLocalEntitiesIntersecting(Grid.Owner, ExpandedBounds, System._seeds, flags: LookupFlags.All | LookupFlags.Approximate);
}
}
public Entity<MapGridComponent> Grid;
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 readonly List<Dictionary<Vector2i, int>> Vis1 = new();
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 seedXform = EntManager.GetComponent<TransformComponent>(seed);
Grid.Comp,
new Circle(System._xforms.GetWorldPosition(seedXform), seed.Comp.Range), ignoreEmpty: false);
- // Try to find the target tile.
- if (TargetTile != null)
+ lock (VisibleTiles)
{
foreach (var tile in squircles)
{
- if (tile.GridIndices == TargetTile)
- {
- lock (System)
- {
- System.TargetFound = true;
- }
-
- return;
- }
- }
- }
- else
- {
- lock (VisibleTiles)
- {
- foreach (var tile in squircles)
- {
- VisibleTiles.Add(tile.GridIndices);
- }
+ VisibleTiles.Add(tile.GridIndices);
}
}
vis1[tile] = -1;
}
- if (TargetTile != null)
- {
- if (vis1.TryGetValue(TargetTile.Value, out var tileVis))
- {
- DebugTools.Assert(seedTiles.Contains(TargetTile.Value));
-
- if (tileVis != 0)
- {
- lock (System)
- {
- System.TargetFound = true;
- return;
- }
- }
- }
- }
- else
+ // vis2 is what we care about for LOS.
+ foreach (var tile in seedTiles)
{
- // 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;
+ // If not in viewport don't care.
+ if (!System._viewportTiles.Contains(tile))
+ continue;
- var tileVis = vis1.GetValueOrDefault(tile, 0);
+ var tileVis = vis1.GetValueOrDefault(tile, 0);
- if (tileVis != 0)
+ if (tileVis != 0)
+ {
+ // No idea if it's better to do this inside or out.
+ lock (VisibleTiles)
{
- // No idea if it's better to do this inside or out.
- lock (VisibleTiles)
- {
- VisibleTiles.Add(tile);
- }
+ VisibleTiles.Add(tile);
}
}
}
--- /dev/null
+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;
+}
using Content.Shared.Storage.EntitySystems;
using Robust.Shared.Collections;
using Robust.Shared.Prototypes;
+using Robust.Shared.Random;
+using Robust.Shared.Utility;
namespace Content.Shared.Station;
public abstract class SharedStationSpawningSystem : EntitySystem
{
[Dependency] protected readonly IPrototypeManager PrototypeManager = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
[Dependency] protected readonly InventorySystem InventorySystem = default!;
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
+ [Dependency] private readonly MetaDataSystem _metadata = default!;
[Dependency] private readonly SharedStorageSystem _storage = default!;
[Dependency] private readonly SharedTransformSystem _xformSystem = default!;
}
/// <summary>
- /// Equips the given starting gears from a `RoleLoadout` onto an entity.
+ /// Equips the data from a `RoleLoadout` onto an entity.
/// </summary>
public void EquipRoleLoadout(EntityUid entity, RoleLoadout loadout, RoleLoadoutPrototype roleProto)
{
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)
/// <summary>
/// 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>
[ViewVariables(VVAccess.ReadWrite)]
[DataField]
public LocId VerbText = "ui-verb-toggle-open";
/// <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>
/// <remarks>
/// 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>
[ViewVariables(VVAccess.ReadWrite)]
[DataField]
- public bool RequireHands = true;
+ public bool RequiresComplex = true;
/// <summary>
/// Entities that are required to open this UI.
if (_whitelistSystem.IsWhitelistFail(component.RequiredItems, args.Using ?? default))
return false;
- if (component.RequireHands)
+ if (component.RequiresComplex)
{
if (args.Hands == null)
return false;
if (!_blockerSystem.CanInteract(user, uiEntity) && (!HasComp<GhostComponent>(user) || aui.BlockSpectators))
return false;
- if (aui.RequireHands)
+ if (aui.RequiresComplex)
+ {
+ if (!_blockerSystem.CanComplexInteract(user))
+ return false;
+ }
+
+ if (aui.InHandsOnly)
{
if (!TryComp(user, out HandsComponent? hands))
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)
- return false;
- }
+ if (aui.RequireActiveHand && hands.ActiveHand != hand)
+ return false;
}
if (aui.AdminOnly && !_adminManager.IsAdmin(user))
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);
}
private void OnHandUnequipped(Entity<ActivatableUIComponent> ent, ref GotUnequippedHandEvent args)
{
- if (ent.Comp.RequireHands && ent.Comp.InHandsOnly)
+ if (ent.Comp.InHandsOnly)
CloseAll(ent, ent);
}
}
// 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.
var canInteract = force || _actionBlockerSystem.CanInteract(user, target);
+ var canComplexInteract = force || _actionBlockerSystem.CanComplexInteract(user);
_interactionSystem.TryGetUsedEntity(user, out var @using);
TryComp<HandsComponent>(user, out var hands);
// TODO: fix this garbage and use proper generics or reflection or something else, not this.
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);
verbs.UnionWith(verbEvent.Verbs);
}
&& @using != null
&& @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
verbs.UnionWith(verbEvent.Verbs);
}
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);
verbs.UnionWith(verbEvent.Verbs);
}
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);
verbs.UnionWith(verbEvent.Verbs);
}
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);
verbs.UnionWith(verbEvent.Verbs);
}
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);
verbs.UnionWith(verbEvent.Verbs);
}
// generic verbs
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);
verbs.UnionWith(verbEvent.Verbs);
}
if (types.Contains(typeof(EquipmentVerb)))
{
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);
verbs.UnionWith(verbEvent.Verbs);
}
/// </remarks>
public readonly bool CanInteract;
+ /// <summary>
+ /// Cached version of CanComplexInteract
+ /// </summary>
+ public readonly bool CanComplexInteract;
+
/// <summary>
/// The User's hand component.
/// </summary>
/// </remarks>
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;
Target = target;
Using = @using;
Hands = hands;
CanAccess = canAccess;
+ CanComplexInteract = canComplexInteract;
CanInteract = canInteract;
ExtraCategories = extraCategories;
}
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))
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
// but it looks like an almost direct duplication of WiresPanelComponent except with a shittier API.
if (TryComp<WiresPanelSecurityComponent>(entity, out var wiresPanelSecurity) &&
_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;
+}
- borgwalk1.ogg
- borgwalk2.ogg
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/"
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-placeholder = Type here to change law text...
+
+silicon-laws-updated = Updated laws
department-Medical-description = Keep the crew healthy.
department-Security-description = Keep the peace around 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-Medical = Medical
department-Security = Security
department-Science = Science
+department-Silicon = Silicons
department-Specific = Station specific
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-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-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-name-bartender = Bartender
job-name-passenger = Passenger
job-name-salvagespec = Salvage Specialist
+job-name-station-ai = Station AI
job-name-qm = Quartermaster
job-name-cargotech = Cargo Technician
job-name-chef = Chef
JobSecurityCadet = Security Cadet
JobSecurityOfficer = Security Officer
JobServiceWorker = Service Worker
+JobStationAi = Station AI
JobStationEngineer = Station Engineer
JobTechnicalAssistant = Technical Assistant
JobVisitor = Visitor
+# 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
loadout-restrictions = Restrictions
loadouts-min-limit = Min count: {$count}
--- /dev/null
+# 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
- type: Transform
pos: 1.5,-14.5
parent: 179
+- proto: PlayerStationAi
+ entities:
+ - uid: 14
+ components:
+ - type: Transform
+ pos: -5.5,-5.5
+ parent: 179
- proto: PortableGeneratorSuperPacman
entities:
- uid: 1016
id: names_ai
values:
- 16-20
- - 790
+ - "790"
- Adaptive Manipulator
- ALICE
- Allied Mastercomputer
- type: ActivatableUI
key: enum.BorgUiKey.Key
- type: SiliconLawBound
+ - type: ActionGrant
+ actions:
+ - ActionViewLaws
- type: EmagSiliconLaw
stunTime: 5
- type: SiliconLawProvider
access: [["Medical"], ["Command"], ["Research"]]
- type: Inventory
templateId: borgDutch
- - type: SolutionScanner
- type: FootstepModifier
footstepSoundCollection:
collection: FootstepHoverBorg
+ - type: SolutionScanner
- type: InteractionPopup
interactSuccessString: petting-success-medical-cyborg
interactFailureString: petting-failure-medical-cyborg
- type: entity
id: MobTomatoKiller
- parent:
+ parent:
- BaseSimpleMob
- MobDamageable
+ - MobPolymorphable
- MobBloodstream
- MobFlammable
- MobCombat
components:
- HumanoidAppearance
- type: Sprite
- sprite: Mobs/Demons/tomatokiller.rsi
+ sprite: Mobs/Demons/tomatokiller.rsi
noRot: true
layers:
- map: [ "enum.DamageStateVisualLayers.Base" ]
- type: entity
id: MobRevenant
+ parent:
+ - BaseMob
+ - Incorporeal
name: revenant
description: A spooky ghostie.
components:
- - type: MindContainer
- - type: InputMover
- - type: MobMover
- type: Input
context: "ghost"
- type: MovementSpeedModifier
baseWalkSpeed: 6
baseSprintSpeed: 6
- type: Sprite
- noRot: true
- drawdepth: Ghosts
sprite: Mobs/Ghosts/revenant.rsi
layers:
- state: active
- - type: Clickable
- type: StatusEffects
allowed:
- Stun
- 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
damageContainer: Biological
- - type: Examiner
- type: NoSlip
- - type: Actions
- type: Eye
drawFov: false
visMask:
- Ghost
- type: ContentEye
maxZoom: 1.2, 1.2
- - type: DoAfter
- - type: Alerts
- type: NameIdentifier
group: GenericNumber
- type: GhostRole
parent:
- BaseMob
- MobDamageable
+ - MobPolymorphable
- MobAtmosExposed
id: BaseSimpleMob
suffix: AI
- type: entity
- parent: BaseMob
+ id: Incorporeal
+ save: false
+ abstract: true
+ description: Mobs without physical bodies
+ components:
+ - type: Sprite
+ noRot: true
+ overrideContainerOcclusion: true # Always show up regardless of where they're contained.
+ drawdepth: Ghosts
+ - type: CargoSellBlacklist
+ - type: MovementSpeedModifier
+ baseSprintSpeed: 12
+ baseWalkSpeed: 8
+ - type: MovementIgnoreGravity
+ - type: Physics
+ bodyType: KinematicController
+ bodyStatus: InAir
+ - type: CanMoveInAir
+ - type: Fixtures
+ fixtures:
+ fix1:
+ shape:
+ !type:PhysShapeCircle
+ radius: 0.35
+ density: 15
+ mask:
+ - GhostImpassable
+
+- type: entity
+ parent:
+ - Incorporeal
+ - BaseMob
id: MobObserver
name: observer
description: Boo!
categories: [ HideSpawnMenu ]
components:
- - type: CargoSellBlacklist
- type: Sprite
- overrideContainerOcclusion: true # Ghosts always show up regardless of where they're contained.
- drawdepth: Ghosts
sprite: Mobs/Ghosts/ghost_human.rsi
color: "#fff8"
layers:
shader: unshaded
- type: ContentEye
maxZoom: 1.44,1.44
- - type: Fixtures
- fixtures:
- fix1:
- shape:
- !type:PhysShapeCircle
- radius: 0.35
- density: 15
- mask:
- - GhostImpassable
- type: Eye
drawFov: false
- type: Input
skipChecks: true
- type: Ghost
- type: GhostHearing
- - type: MovementSpeedModifier
- baseSprintSpeed: 12
- baseWalkSpeed: 8
- - type: MovementIgnoreGravity
- type: IntrinsicRadioReceiver
- type: ActiveRadio
receiveAllChannels: true
globalReceive: true
- - type: Physics
- bodyType: KinematicController
- bodyStatus: InAir
- - type: CanMoveInAir
- type: Tag
tags:
- BypassInteractionRangeChecks
+# 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
id: PlayerBorgGeneric
parent: BorgChassisGeneric
parent:
- BaseMob
- MobDamageable
+ - MobPolymorphable
- MobCombat
- StripableInventoryBase
id: BaseMobSpecies
- type: CameraRecoil
- type: MindContainer
- type: MovementSpeedModifier
- - type: Polymorphable
- - type: StatusIcon
- type: RequireProjectileTarget
active: False
+- type: entity
+ save: false
+ id: MobPolymorphable
+ abstract: true
+ components:
+ - type: Polymorphable
+
# Used for mobs that have health and can take damage.
- type: entity
save: false
blockSpectators: true # otherwise they can play client-side music
inHandsOnly: false
singleUser: true
- requireHands: true
+ requiresComplex: true
verbText: verb-instrument-openui
key: enum.InstrumentUiKey.Key
- type: InteractionOutline
- type: PaperLabelType
- type: ActivatableUI
key: enum.PaperUiKey.Key
- requireHands: false
+ requiresComplex: false
- type: UserInterface
interfaces:
enum.PaperUiKey.Key:
- Paper
- type: ActivatableUI
key: enum.PaperUiKey.Key
- requireHands: false
+ requiresComplex: false
- type: UserInterface
interfaces:
enum.PaperUiKey.Key:
map: ["base"]
- type: Input
context: human
- - type: BlockMovement
- type: ToggleableGhostRole
examineTextMindPresent: positronic-brain-installed
examineTextMindSearching: positronic-brain-still-searching
wipeVerbPopup: positronic-brain-wiped-device
stopSearchVerbText: positronic-brain-stop-searching-verb-text
stopSearchVerbPopup: positronic-brain-stopped-searching
+ - type: BlockMovement
- type: Examiner
- type: BorgBrain
- type: IntrinsicRadioReceiver
type: AccessOverriderBoundUserInterface
- type: ActivatableUI
key: enum.AccessOverriderUiKey.Key
- requireHands: true
+ requiresComplex: true
requireActiveHand: false
singleUser: true
- type: ItemSlots
name: airlock
description: It opens, it closes, and maybe crushes you.
components:
+ - type: StationAiWhitelist
- type: MeleeSound
soundGroups:
Brute:
- type: SpawnOnOverload
- type: UserInterface
interfaces:
+ enum.AiUi.Key:
+ type: StationAiBoundUserInterface
enum.WiresUiKey.Key:
type: WiresBoundUserInterface
- type: Airtight
name: communications computer
description: A computer used to make station wide announcements via keyboard, set the appropriate alert level, and call the emergency shuttle.
components:
+ - type: StationAiWhitelist
- type: Sprite
layers:
- map: ["computerLayerBody"]
access: [["ResearchDirector"]]
- type: Lock
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
type: WiresBoundUserInterface
- type: ActivatableUI
key: enum.HealthAnalyzerUiKey.Key
- requireHands: false
+ requiresComplex: false
- type: ActivatableUIRequiresPower
- type: PointLight
color: "#3a807f"
placement:
mode: SnapgridCenter
components:
+ - type: StationAiWhitelist
- type: AmbientOnPowered
- type: AmbientSound
volume: -9
description: An intercom. For when the station just needs to know something.
abstract: true
components:
+ - type: StationAiWhitelist
- type: WallMount
- type: ApcPowerReceiver
- type: Electrified
name: camera
description: A surveillance camera. It's watching you. Kinda.
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: Clickable
- type: InteractionOutline
InUse: camera_in_use
- type: UserInterface
interfaces:
+ enum.AiUi.Key:
+ type: StationAiBoundUserInterface
enum.SurveillanceCameraSetupUiKey.Camera:
type: SurveillanceCameraSetupBoundUi
enum.WiresUiKey.Key:
snap:
- Wallmount
components:
+ - type: StationAiWhitelist
- type: Transform
anchored: true
- type: WallMount
- Trinkets
- GroupSpeciesBreathTool
+# Silicons
+- type: roleLoadout
+ id: JobStationAi
+ nameDataset: names_ai
+
# Civilian
- type: roleLoadout
id: JobPassenger
+# 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
id: Borg
name: job-name-borg
playTimeTracker: JobBorg
requirements:
- !type:OverallPlaytimeRequirement
- time: 216000 #60 hrs
+ time: 216000 # 60 hrs
canBeAntag: false
icon: JobIconBorg
supervisors: job-supervisors-rd
weight: -10
roles:
- Bartender
- - Borg
- Botanist
- Boxer
- Chaplain
- Scientist
- ResearchAssistant
+- type: department
+ id: Silicon
+ name: department-Silicon
+ description: department-Silicon-description
+ color: "#D381C9"
+ roles:
+ - Borg
+ - StationAi
+
- type: department
id: Specific
name: department-Specific
- type: playTimeTracker
id: JobServiceWorker
+- type: playTimeTracker
+ id: JobStationAi
+
- type: playTimeTracker
id: JobStationEngineer
state: Borg
jobName: job-name-borg
+- type: jobIcon
+ parent: JobIcon
+ id: JobIconStationAi
+ icon:
+ sprite: /Textures/Interface/Misc/job_icons.rsi
+ state: StationAi
+ jobName: job-name-station-ai
+
- type: jobIcon
parent: JobIcon
id: JobIconBotanist
- !type:DoorBoltLightWireAction
- !type:DoorTimingWireAction
- !type:DoorSafetyWireAction
+ - !type:AiInteractWireAction
- type: wireLayout
parent: Airlock
- type: wireLayout
id: SurveillanceCamera
- dummyWires: 4
+ dummyWires: 2
wires:
- !type:PowerWireAction
+ - !type:AiVisionWireAction
- type: wireLayout
id: CryoPod
- type: Tag
id: Soup
+
- type: Tag
id: Spear
- type: Tag
id: SpreaderIgnore
+- type: Tag
+ id: StationAi
+
- type: Tag
id: StationMapElectronics
--- /dev/null
+{
+ "version": 1,
+ "license": "CC-BY-SA-3.0",
+ "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/blob/c473a8bcc28fbd80827dfca5660d81ca6e833e2c/icons/hud/screen_ai.dmi",
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "states": [
+ {
+ "name": "ai_core"
+ },
+ {
+ "name": "camera_light"
+ },
+ {
+ "name": "crew_monitor"
+ },
+ {
+ "name": "manifest"
+ },
+ {
+ "name": "state_laws"
+ }
+ ]
+}
{
"version": 1,
"license": "CC-BY-SA-3.0",
- "copyright": "Taken from https://github.com/vgstation-coders/vgstation13/blob/e71d6c4fba5a51f99b81c295dcaec4fc2f58fb19/icons/mob/screen1.dmi | Brigmedic icon made by PuroSlavKing (Github) | Zombie icon made by RamZ | Zookeper by netwy (discort) | Rev and Head Rev icon taken from https://tgstation13.org/wiki/HUD and edited by coolmankid12345 (Discord) | Mindshield icon taken from https://github.com/tgstation/tgstation/blob/master/icons/mob/huds/hud.dmi | Admin recolored from MedicalIntern by TsjipTsjip",
+ "copyright": "Taken from https://github.com/vgstation-coders/vgstation13/blob/e71d6c4fba5a51f99b81c295dcaec4fc2f58fb19/icons/mob/screen1.dmi | Brigmedic icon made by PuroSlavKing (Github) | Zombie icon made by RamZ | Zookeper by netwy (discort) | Rev and Head Rev icon taken from https://tgstation13.org/wiki/HUD and edited by coolmankid12345 (Discord) | Mindshield icon taken from https://github.com/tgstation/tgstation/blob/ce6beb8a4d61235d9a597a7126c407160ed674ea/icons/mob/huds/hud.dmi | Admin recolored from MedicalIntern by TsjipTsjip",
"size": {
"x": 8,
[1.0,1.0]
]
},
+ {
+ "name": "StationAi"
+ },
{
"name": "Syndicate"
},
--- /dev/null
+{
+ "version": 1,
+ "license": "CC-BY-SA-3.0",
+ "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/blob/4b4e9dff1d7d891cfb75d25ca5bf5172d1c02be6/icons/hud/screen_gen.dmi",
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "states": [
+ {
+ "name": "noise",
+ "delays": [
+ [
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ]
+ ]
+ }
+ ]
+}
\ No newline at end of file
--- /dev/null
+{"version": 1, "size": {"x": 32, "y": 32}, "license": "CC-BY-SA-3.0", "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/blob/2a19963297f91efb452dbb5c1d4eb28a14776b0a/icons/mob/silicon/ai.dmi", "states": [{"name": "ai", "directions": 1, "delays": [[0.2, 0.2, 0.2, 0.1, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.1]]}, {"name": "ai-banned", "directions": 1, "delays": [[0.7, 0.7, 0.7, 0.7, 0.7, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7]]}, {"name": "ai-banned-unshaded", "directions": 1, "delays": [[0.7, 0.7, 0.7, 0.7, 0.7, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7]]}, {"name": "ai-banned_dead", "directions": 1, "delays": [[0.7, 0.7]]}, {"name": "ai-banned_dead-unshaded", "directions": 1, "delays": [[0.7, 0.7]]}, {"name": "ai-empty", "directions": 1, "delays": [[0.7, 0.7]]}, {"name": "ai-empty-unshaded", "directions": 1, "delays": [[0.7, 0.7]]}, {"name": "ai-holo-old", "directions": 4, "delays": [[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]]}, {"name": "ai-holo-old-unshaded", "directions": 4, "delays": [[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]]}, {"name": "ai-unshaded", "directions": 1, "delays": [[0.2, 0.2, 0.2, 0.1, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.1]]}, {"name": "ai_dead", "directions": 1, "delays": [[1.0]]}, {"name": "ai_dead-unshaded", "directions": 1, "delays": [[1.0]]}, {"name": "default", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "default-unshaded", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "floating_face", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "floating_face-unshaded", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "horror", "directions": 1, "delays": [[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]]}, {"name": "horror-unshaded", "directions": 1, "delays": [[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]]}, {"name": "xeno_queen", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "xeno_queen-unshaded", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}]}
\ No newline at end of file
--- /dev/null
+{
+ "version": 1,
+ "license": "CC-BY-SA-3.0",
+ "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/blob/2a19963297f91efb452dbb5c1d4eb28a14776b0a/icons/mob/silicon/ai.dmi",
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "states": [
+ {
+ "name": "ai",
+ "delays": [
+ [
+ 0.2,
+ 0.2,
+ 0.1,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.1
+ ]
+ ]
+ },
+ {
+ "name": "ai_dead"
+ },
+ {
+ "name": "ai_empty",
+ "delays": [
+ [
+ 0.7,
+ 0.7
+ ]
+ ]
+ },
+ {
+ "name": "default",
+ "directions": 4
+ },
+ {
+ "name": "base"
+ }
+ ]
+}
\ No newline at end of file
--- /dev/null
+{
+ "version": 1,
+ "license": "CC-BY-SA-3.0",
+ "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/blob/1feffb747a33434a9d28450fc52ade75253aeba5/icons/obj/aicards.dmi",
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "states": [
+ {
+ "name": "base"
+ },
+ {
+ "name": "inhand-left",
+ "directions": 4
+ },
+ {
+ "name": "inhand-right",
+ "directions": 4
+ },
+ {
+ "name": "empty",
+ "delays": [
+ [
+ 0.4,
+ 0.4
+ ]
+ ]
+ },
+ {
+ "name": "full",
+ "delays": [
+ [
+ 0.4,
+ 0.4,
+ 0.4,
+ 0.4,
+ 0.4,
+ 0.4,
+ 0.4,
+ 0.4,
+ 0.4,
+ 0.4,
+ 0.4,
+ 0.4,
+ 0.4,
+ 0.4,
+ 0.4,
+ 0.4,
+ 0.4,
+ 0.4,
+ 0.4,
+ 0.4
+ ]
+ ]
+ }
+ ]
+}
\ No newline at end of file
--- /dev/null
+{"version": 1, "size": {"x": 32, "y": 32}, "license": "CC-BY-SA-3.0", "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/blob/1feffb747a33434a9d28450fc52ade75253aeba5/icons/obj/aicards.dmi", "states": [{"name": "aicard", "directions": 1, "delays": [[0.4, 0.4]]}, {"name": "aicard-full", "directions": 1, "delays": [[0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4]]}, {"name": "aicard-full-unshaded", "directions": 1, "delays": [[0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4]]}, {"name": "aicard-unshaded", "directions": 1, "delays": [[0.4, 0.4]]}]}
\ No newline at end of file