diff --git a/Content.Client/Bed/SleepingSystem.cs b/Content.Client/Bed/SleepingSystem.cs deleted file mode 100644 index addf855bf36..00000000000 --- a/Content.Client/Bed/SleepingSystem.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Content.Server.Bed.Sleep; - -namespace Content.Client.Bed; - -public sealed class SleepingSystem : SharedSleepingSystem -{ - -} diff --git a/Content.Client/Chat/UI/EmotesMenu.xaml.cs b/Content.Client/Chat/UI/EmotesMenu.xaml.cs index 33407553438..f3b7837f21a 100644 --- a/Content.Client/Chat/UI/EmotesMenu.xaml.cs +++ b/Content.Client/Chat/UI/EmotesMenu.xaml.cs @@ -19,9 +19,6 @@ public sealed partial class EmotesMenu : RadialMenu [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly ISharedPlayerManager _playerManager = default!; - private readonly SpriteSystem _spriteSystem; - private readonly EntityWhitelistSystem _whitelistSystem; - public event Action>? OnPlayEmote; public EmotesMenu() @@ -29,8 +26,8 @@ public EmotesMenu() IoCManager.InjectDependencies(this); RobustXamlLoader.Load(this); - _spriteSystem = _entManager.System(); - _whitelistSystem = _entManager.System(); + var spriteSystem = _entManager.System(); + var whitelistSystem = _entManager.System(); var main = FindControl("Main"); @@ -40,8 +37,8 @@ public EmotesMenu() 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 && @@ -63,7 +60,7 @@ public EmotesMenu() { VerticalAlignment = VAlignment.Center, HorizontalAlignment = HAlignment.Center, - Texture = _spriteSystem.Frame0(emote.Icon), + Texture = spriteSystem.Frame0(emote.Icon), TextureScale = new Vector2(2f, 2f), }; diff --git a/Content.Client/Commands/SetMenuVisibilityCommand.cs b/Content.Client/Commands/SetMenuVisibilityCommand.cs index ddfb0b16920..17a544dabaf 100644 --- a/Content.Client/Commands/SetMenuVisibilityCommand.cs +++ b/Content.Client/Commands/SetMenuVisibilityCommand.cs @@ -1,4 +1,5 @@ using Content.Client.Verbs; +using Content.Shared.Verbs; using JetBrains.Annotations; using Robust.Shared.Console; diff --git a/Content.Client/ContextMenu/UI/EntityMenuUIController.cs b/Content.Client/ContextMenu/UI/EntityMenuUIController.cs index a60619baa35..b6f4dc1be2a 100644 --- a/Content.Client/ContextMenu/UI/EntityMenuUIController.cs +++ b/Content.Client/ContextMenu/UI/EntityMenuUIController.cs @@ -9,6 +9,7 @@ using Content.Shared.Examine; using Content.Shared.IdentityManagement; using Content.Shared.Input; +using Content.Shared.Verbs; using Robust.Client.GameObjects; using Robust.Client.Graphics; using Robust.Client.Input; @@ -194,8 +195,20 @@ public override void FrameUpdate(FrameEventArgs args) return; // Do we need to do in-range unOccluded checks? - var ignoreFov = !_eyeManager.CurrentEye.DrawFov || - (_verbSystem.Visibility & MenuVisibility.NoFov) == MenuVisibility.NoFov; + var visibility = _verbSystem.Visibility; + + if (!_eyeManager.CurrentEye.DrawFov) + { + visibility &= ~MenuVisibility.NoFov; + } + + var ev = new MenuVisibilityEvent() + { + Visibility = visibility, + }; + + _entityManager.EventBus.RaiseLocalEvent(player, ref ev); + visibility = ev.Visibility; _entityManager.TryGetComponent(player, out ExaminerComponent? examiner); var xformQuery = _entityManager.GetEntityQuery(); @@ -209,7 +222,7 @@ public override void FrameUpdate(FrameEventArgs args) continue; } - if (ignoreFov) + if ((visibility & MenuVisibility.NoFov) == MenuVisibility.NoFov) continue; var pos = new MapCoordinates(_xform.GetWorldPosition(xform, xformQuery), xform.MapID); diff --git a/Content.Client/Interaction/DragDropSystem.cs b/Content.Client/Interaction/DragDropSystem.cs index 8baa4d15fe4..d249766bbcc 100644 --- a/Content.Client/Interaction/DragDropSystem.cs +++ b/Content.Client/Interaction/DragDropSystem.cs @@ -495,7 +495,7 @@ private void RemoveHighlights() // CanInteract() doesn't support checking a second "target" entity. // Doing so manually: var ev = new GettingInteractedWithAttemptEvent(user, dragged); - RaiseLocalEvent(dragged, ev, true); + RaiseLocalEvent(dragged, ref ev); if (ev.Cancelled) return false; diff --git a/Content.Client/Replay/Spectator/ReplaySpectatorSystem.Blockers.cs b/Content.Client/Replay/Spectator/ReplaySpectatorSystem.Blockers.cs index 2fa862f3df7..99d85350b5e 100644 --- a/Content.Client/Replay/Spectator/ReplaySpectatorSystem.Blockers.cs +++ b/Content.Client/Replay/Spectator/ReplaySpectatorSystem.Blockers.cs @@ -17,7 +17,7 @@ private void InitializeBlockers() SubscribeLocalEvent(OnAttempt); SubscribeLocalEvent(OnAttempt); SubscribeLocalEvent(OnAttempt); - SubscribeLocalEvent(OnAttempt); + SubscribeLocalEvent(OnInteractAttempt); SubscribeLocalEvent(OnAttempt); SubscribeLocalEvent(OnAttempt); SubscribeLocalEvent(OnAttempt); @@ -27,6 +27,11 @@ private void InitializeBlockers() SubscribeLocalEvent(OnPullAttempt); } + private void OnInteractAttempt(Entity ent, ref InteractionAttemptEvent args) + { + args.Cancelled = true; + } + private void OnAttempt(EntityUid uid, ReplaySpectatorComponent component, CancellableEntityEventArgs args) { args.Cancel(); diff --git a/Content.Client/Silicons/Laws/SiliconLawEditUi/SiliconLawEui.cs b/Content.Client/Silicons/Laws/SiliconLawEditUi/SiliconLawEui.cs index a4d59d1f315..03c74032f73 100644 --- a/Content.Client/Silicons/Laws/SiliconLawEditUi/SiliconLawEui.cs +++ b/Content.Client/Silicons/Laws/SiliconLawEditUi/SiliconLawEui.cs @@ -6,7 +6,7 @@ namespace Content.Client.Silicons.Laws.SiliconLawEditUi; public sealed class SiliconLawEui : BaseEui { - public readonly EntityManager _entityManager = default!; + private readonly EntityManager _entityManager; private SiliconLawUi _siliconLawUi; private EntityUid _target; diff --git a/Content.Client/Silicons/StationAi/StationAiBoundUserInterface.cs b/Content.Client/Silicons/StationAi/StationAiBoundUserInterface.cs new file mode 100644 index 00000000000..68318305a0c --- /dev/null +++ b/Content.Client/Silicons/StationAi/StationAiBoundUserInterface.cs @@ -0,0 +1,28 @@ +using Content.Shared.Silicons.StationAi; +using Robust.Client.UserInterface; + +namespace Content.Client.Silicons.StationAi; + +public sealed class StationAiBoundUserInterface : BoundUserInterface +{ + private StationAiMenu? _menu; + + public StationAiBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) + { + } + + protected override void Open() + { + base.Open(); + _menu = this.CreateWindow(); + _menu.Track(Owner); + + _menu.OnAiRadial += args => + { + SendPredictedMessage(new StationAiRadialMessage() + { + Event = args, + }); + }; + } +} diff --git a/Content.Client/Silicons/StationAi/StationAiMenu.xaml b/Content.Client/Silicons/StationAi/StationAiMenu.xaml new file mode 100644 index 00000000000..d56fc832898 --- /dev/null +++ b/Content.Client/Silicons/StationAi/StationAiMenu.xaml @@ -0,0 +1,13 @@ + + + + + + + diff --git a/Content.Client/Silicons/StationAi/StationAiMenu.xaml.cs b/Content.Client/Silicons/StationAi/StationAiMenu.xaml.cs new file mode 100644 index 00000000000..24a802a60fe --- /dev/null +++ b/Content.Client/Silicons/StationAi/StationAiMenu.xaml.cs @@ -0,0 +1,128 @@ +using System.Numerics; +using Content.Client.UserInterface.Controls; +using Content.Shared.Silicons.StationAi; +using Robust.Client.AutoGenerated; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Timing; + +namespace Content.Client.Silicons.StationAi; + +[GenerateTypedNameReferences] +public sealed partial class StationAiMenu : RadialMenu +{ + [Dependency] private readonly IClyde _clyde = default!; + [Dependency] private readonly IEntityManager _entManager = default!; + [Dependency] private readonly IEyeManager _eyeManager = default!; + + public event Action? 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("Main"); + main.DisposeAllChildren(); + var sprites = _entManager.System(); + + 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().GetSpriteScreenCoordinates((_tracked, null, xform)); + + if (!coords.IsValid) + { + Close(); + return; + } + + OpenScreenAt(coords.Position, _clyde); + } +} + +public sealed class StationAiMenuButton(BaseStationAiAction action) : RadialMenuTextureButton +{ + public BaseStationAiAction Action = action; +} diff --git a/Content.Client/Silicons/StationAi/StationAiOverlay.cs b/Content.Client/Silicons/StationAi/StationAiOverlay.cs index efa1b8dbeff..15a8a3a63fe 100644 --- a/Content.Client/Silicons/StationAi/StationAiOverlay.cs +++ b/Content.Client/Silicons/StationAi/StationAiOverlay.cs @@ -4,7 +4,9 @@ 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; @@ -12,6 +14,7 @@ public sealed class StationAiOverlay : Overlay { [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!; @@ -22,6 +25,9 @@ public sealed class StationAiOverlay : Overlay private IRenderTexture? _staticTexture; private IRenderTexture? _stencilTexture; + private float _updateRate = 1f / 30f; + private float _accumulator; + public StationAiOverlay() { IoCManager.InjectDependencies(this); @@ -47,19 +53,22 @@ protected override void Draw(in OverlayDrawArgs args) _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(); var xforms = _entManager.System(); - _entManager.System().GetView((gridUid, grid), worldBounds, _visibleTiles); + + if (_accumulator <= 0f) + { + _accumulator = MathF.Max(0f, _accumulator + _updateRate); + _visibleTiles.Clear(); + _entManager.System().GetView((gridUid, broadphase, grid), worldBounds, _visibleTiles); + } var gridMatrix = xforms.GetWorldMatrix(gridUid); var matty = Matrix3x2.Multiply(gridMatrix, invMatrix); diff --git a/Content.Client/Silicons/StationAi/StationAiSystem.Airlock.cs b/Content.Client/Silicons/StationAi/StationAiSystem.Airlock.cs new file mode 100644 index 00000000000..bf6b65a9697 --- /dev/null +++ b/Content.Client/Silicons/StationAi/StationAiSystem.Airlock.cs @@ -0,0 +1,30 @@ +using Content.Shared.Doors.Components; +using Content.Shared.Silicons.StationAi; +using Robust.Shared.Utility; + +namespace Content.Client.Silicons.StationAi; + +public sealed partial class StationAiSystem +{ + private void InitializeAirlock() + { + SubscribeLocalEvent(OnDoorBoltGetRadial); + } + + private void OnDoorBoltGetRadial(Entity 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, + } + }); + } +} diff --git a/Content.Client/Silicons/StationAi/StationAiSystem.Light.cs b/Content.Client/Silicons/StationAi/StationAiSystem.Light.cs new file mode 100644 index 00000000000..cf2f6136207 --- /dev/null +++ b/Content.Client/Silicons/StationAi/StationAiSystem.Light.cs @@ -0,0 +1,32 @@ +using Content.Shared.Item.ItemToggle.Components; +using Content.Shared.Light.Components; +using Content.Shared.Silicons.StationAi; +using Robust.Shared.Utility; + +namespace Content.Client.Silicons.StationAi; + +public sealed partial class StationAiSystem +{ + // Used for surveillance camera lights + + private void InitializePowerToggle() + { + SubscribeLocalEvent(OnLightGetRadial); + } + + private void OnLightGetRadial(Entity 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 + } + }); + } +} diff --git a/Content.Client/Silicons/StationAi/StationAiSystem.cs b/Content.Client/Silicons/StationAi/StationAiSystem.cs index 2ed06175252..ab9ace3c1d5 100644 --- a/Content.Client/Silicons/StationAi/StationAiSystem.cs +++ b/Content.Client/Silicons/StationAi/StationAiSystem.cs @@ -5,7 +5,7 @@ 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!; @@ -15,8 +15,8 @@ public sealed partial class StationAiSystem : EntitySystem public override void Initialize() { base.Initialize(); - // InitializeAirlock(); - // InitializePowerToggle(); + InitializeAirlock(); + InitializePowerToggle(); SubscribeLocalEvent(OnAiAttached); SubscribeLocalEvent(OnAiDetached); diff --git a/Content.Client/Verbs/VerbSystem.cs b/Content.Client/Verbs/VerbSystem.cs index 5f1f49e5fd0..e28f48d6a50 100644 --- a/Content.Client/Verbs/VerbSystem.cs +++ b/Content.Client/Verbs/VerbSystem.cs @@ -67,9 +67,18 @@ public bool TryGetEntityMenuEntities(MapCoordinates targetPos, [NotNullWhen(true ? Visibility : Visibility | MenuVisibility.NoFov; + var ev = new MenuVisibilityEvent() + { + TargetPos = targetPos, + Visibility = visibility, + }; + + RaiseLocalEvent(player.Value, ref ev); + visibility = ev.Visibility; // Get entities List entities; + var examineFlags = LookupFlags.All & ~LookupFlags.Sensors; // Do we have to do FoV checks? if ((visibility & MenuVisibility.NoFov) == 0) @@ -77,15 +86,10 @@ public bool TryGetEntityMenuEntities(MapCoordinates targetPos, [NotNullWhen(true var entitiesUnderMouse = gameScreenBase.GetClickableEntities(targetPos).ToHashSet(); bool Predicate(EntityUid e) => e == player || entitiesUnderMouse.Contains(e); - // first check the general location. - if (!_examine.CanExamine(player.Value, targetPos, Predicate)) - return false; - TryComp(player.Value, out ExaminerComponent? examiner); - // Then check every entity 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); @@ -93,7 +97,7 @@ public bool TryGetEntityMenuEntities(MapCoordinates targetPos, [NotNullWhen(true } else { - entities = _entityLookup.GetEntitiesInRange(targetPos, EntityMenuLookupSize).ToList(); + entities = _entityLookup.GetEntitiesInRange(targetPos, EntityMenuLookupSize, flags: examineFlags).ToList(); } if (entities.Count == 0) @@ -137,27 +141,6 @@ public bool TryGetEntityMenuEntities(MapCoordinates targetPos, [NotNullWhen(true } } - // Remove any entities that do not have LOS - if ((visibility & MenuVisibility.NoFov) == 0) - { - var xformQuery = GetEntityQuery(); - var playerPos = _transform.GetMapCoordinates(player.Value, xform: xformQuery.GetComponent(player.Value)); - - for (var i = entities.Count - 1; i >= 0; i--) - { - var entity = entities[i]; - - if (!_examine.InRangeUnOccluded( - playerPos, - _transform.GetMapCoordinates(entity, xform: xformQuery.GetComponent(entity)), - ExamineSystemShared.ExamineRange, - null)) - { - entities.RemoveSwap(i); - } - } - } - if (entities.Count == 0) return false; @@ -229,15 +212,4 @@ private void HandleVerbResponse(VerbsResponseEvent msg) OnVerbsResponse?.Invoke(msg); } } - - [Flags] - public enum MenuVisibility - { - // What entities can a user see on the entity menu? - Default = 0, // They can only see entities in FoV. - NoFov = 1 << 0, // They ignore FoV restrictions - InContainer = 1 << 1, // They can see through containers. - Invisible = 1 << 2, // They can see entities without sprites and the "HideContextMenu" tag is ignored. - All = NoFov | InContainer | Invisible - } } diff --git a/Content.Server/Administration/Commands/SetStationAiName.cs b/Content.Server/Administration/Commands/SetStationAiName.cs new file mode 100644 index 00000000000..0c4236f4302 --- /dev/null +++ b/Content.Server/Administration/Commands/SetStationAiName.cs @@ -0,0 +1,57 @@ +using Content.Server.Station.Systems; +using Content.Shared.Administration; +using Content.Shared.Roles; +using Robust.Shared.Console; +using Robust.Shared.Prototypes; + + +namespace Content.Server.Administration.Commands; + + +[AdminCommand(AdminFlags.Admin)] +public sealed class SetStationAiNameCommand : IConsoleCommand +{ + [Dependency] private readonly IEntityManager _entManager = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + + private readonly ProtoId _stationAiJob = "StationAi"; + + public string Command => "setstationainame"; + public string Description => Loc.GetString("set-station-ai-name-command-description"); + public string Help => Loc.GetString("set-station-ai-name-command-help-text", ("command", Command)); + + public void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (args.Length != 1) + { + shell.WriteError(Loc.GetString("shell-wrong-arguments-number")); + return; + } + + if (!int.TryParse(args[0], out var entInt)) + { + shell.WriteLine(Loc.GetString("shell-entity-uid-must-be-number")); + return; + } + + var netEntity = new NetEntity(entInt); + + if (!_entManager.TryGetEntity(netEntity, out var target)) + { + shell.WriteLine(Loc.GetString("shell-invalid-entity-id")); + return; + } + + var hasStationAi = _prototypeManager.TryIndex(_stationAiJob, out var job); + + if (!hasStationAi) + { + shell.WriteLine(Loc.GetString("set-station-ai-name-command-no-station-ai")); + return; + } + + var spawningSystem = _entManager.System(); + spawningSystem.EquipJobName(target.Value, job!); + shell.WriteLine(Loc.GetString("shell-command-success")); + } +} diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.cs b/Content.Server/Administration/Systems/AdminVerbSystem.cs index 1e701794ffc..d9ccb730d0a 100644 --- a/Content.Server/Administration/Systems/AdminVerbSystem.cs +++ b/Content.Server/Administration/Systems/AdminVerbSystem.cs @@ -36,6 +36,7 @@ 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; diff --git a/Content.Server/Bed/BedSystem.cs b/Content.Server/Bed/BedSystem.cs index 2335859f0b8..7220c04ea8c 100644 --- a/Content.Server/Bed/BedSystem.cs +++ b/Content.Server/Bed/BedSystem.cs @@ -1,6 +1,5 @@ using Content.Server.Actions; using Content.Server.Bed.Components; -using Content.Server.Bed.Sleep; using Content.Server.Body.Systems; using Content.Server.Construction; using Content.Server.Power.Components; diff --git a/Content.Server/Bed/Sleep/SleepingSystem.cs b/Content.Server/Bed/Sleep/SleepingSystem.cs deleted file mode 100644 index 47966c4814f..00000000000 --- a/Content.Server/Bed/Sleep/SleepingSystem.cs +++ /dev/null @@ -1,261 +0,0 @@ -using Content.Server.Popups; -using Content.Server.Sound; -using Content.Shared.Sound.Components; -using Content.Shared.Actions; -using Content.Shared.Audio; -using Content.Shared.Bed.Sleep; -using Content.Shared.Damage; -using Content.Shared.Examine; -using Content.Shared.IdentityManagement; -using Content.Shared.Interaction; -using Content.Shared.Interaction.Events; -using Content.Shared.Mobs; -using Content.Shared.Mobs.Components; -using Content.Shared.Slippery; -using Content.Shared.StatusEffect; -using Content.Shared.Stunnable; -using Content.Shared.Verbs; -using Robust.Shared.Audio; -using Robust.Shared.Audio.Systems; -using Robust.Shared.Player; -using Robust.Shared.Prototypes; -using Robust.Shared.Random; -using Robust.Shared.Timing; - -namespace Content.Server.Bed.Sleep -{ - public sealed class SleepingSystem : SharedSleepingSystem - { - [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly IRobustRandom _robustRandom = default!; - [Dependency] private readonly PopupSystem _popupSystem = default!; - [Dependency] private readonly SharedAudioSystem _audio = default!; - [Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!; - [Dependency] private readonly EmitSoundSystem _emitSound = default!; - - [ValidatePrototypeId] public const string SleepActionId = "ActionSleep"; - - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(OnSleepStateChanged); - SubscribeLocalEvent(OnDamageChanged); - SubscribeLocalEvent(OnSleepAction); - SubscribeLocalEvent(OnBedSleepAction); - SubscribeLocalEvent(OnWakeAction); - SubscribeLocalEvent(OnMobStateChanged); - SubscribeLocalEvent>(AddWakeVerb); - SubscribeLocalEvent(OnInteractHand); - SubscribeLocalEvent(OnExamined); - SubscribeLocalEvent(OnSlip); - SubscribeLocalEvent(OnConsciousAttempt); - SubscribeLocalEvent(OnInit); - } - - /// - /// when sleeping component is added or removed, we do some stuff with other components. - /// - private void OnSleepStateChanged(EntityUid uid, MobStateComponent component, SleepStateChangedEvent args) - { - if (args.FellAsleep) - { - // Expiring status effects would remove the components needed for sleeping - _statusEffectsSystem.TryRemoveStatusEffect(uid, "Stun"); - _statusEffectsSystem.TryRemoveStatusEffect(uid, "KnockedDown"); - - EnsureComp(uid); - EnsureComp(uid); - - if (TryComp(uid, out var sleepSound)) - { - var emitSound = EnsureComp(uid); - if (HasComp(uid)) - { - emitSound.Sound = sleepSound.Snore; - } - emitSound.MinInterval = sleepSound.Interval; - emitSound.MaxInterval = sleepSound.MaxInterval; - emitSound.PopUp = sleepSound.PopUp; - } - - return; - } - - RemComp(uid); - RemComp(uid); - RemComp(uid); - } - - /// - /// Wake up on taking an instance of damage at least the value of WakeThreshold. - /// - private void OnDamageChanged(EntityUid uid, SleepingComponent component, DamageChangedEvent args) - { - if (!args.DamageIncreased || args.DamageDelta == null) - return; - - /* Shitmed Change Start - Surgery needs this, sorry! If the nocturine gamers get too feisty - I'll probably just increase the threshold */ - - if (args.DamageDelta.GetTotal() >= component.WakeThreshold - && !HasComp(uid)) - TryWaking(uid, component); - - // Shitmed Change End - - } - - private void OnSleepAction(EntityUid uid, MobStateComponent component, SleepActionEvent args) - { - TrySleeping(uid); - } - - private void OnBedSleepAction(EntityUid uid, ActionsContainerComponent component, SleepActionEvent args) - { - TrySleeping(args.Performer); - } - - private void OnWakeAction(EntityUid uid, MobStateComponent component, WakeActionEvent args) - { - if (!TryWakeCooldown(uid)) - return; - - if (TryWaking(uid)) - args.Handled = true; - } - - /// - /// In crit, we wake up if we are not being forced to sleep. - /// And, you can't sleep when dead... - /// - private void OnMobStateChanged(EntityUid uid, SleepingComponent component, MobStateChangedEvent args) - { - if (args.NewMobState == MobState.Dead) - { - RemComp(uid); - RemComp(uid); - return; - } - if (TryComp(uid, out var spam)) - _emitSound.SetEnabled((uid, spam), args.NewMobState == MobState.Alive); - } - - private void AddWakeVerb(EntityUid uid, SleepingComponent component, GetVerbsEvent args) - { - if (!args.CanInteract || !args.CanAccess) - return; - - AlternativeVerb verb = new() - { - Act = () => - { - if (!TryWakeCooldown(uid)) - return; - - TryWaking(args.Target, user: args.User); - }, - Text = Loc.GetString("action-name-wake"), - Priority = 2 - }; - - args.Verbs.Add(verb); - } - - /// - /// When you click on a sleeping person with an empty hand, try to wake them. - /// - private void OnInteractHand(EntityUid uid, SleepingComponent component, InteractHandEvent args) - { - args.Handled = true; - - if (!TryWakeCooldown(uid)) - return; - - TryWaking(args.Target, user: args.User); - } - - private void OnExamined(EntityUid uid, SleepingComponent component, ExaminedEvent args) - { - if (args.IsInDetailsRange) - { - args.PushMarkup(Loc.GetString("sleep-examined", ("target", Identity.Entity(uid, EntityManager)))); - } - } - - private void OnSlip(EntityUid uid, SleepingComponent component, SlipAttemptEvent args) - { - args.Cancel(); - } - - private void OnConsciousAttempt(EntityUid uid, SleepingComponent component, ConsciousAttemptEvent args) - { - args.Cancel(); - } - - - private void OnInit(EntityUid uid, ForcedSleepingComponent component, ComponentInit args) - { - TrySleeping(uid); - } - - /// - /// Try sleeping. Only mobs can sleep. - /// - public bool TrySleeping(EntityUid uid) - { - if (!HasComp(uid)) - return false; - - var tryingToSleepEvent = new TryingToSleepEvent(uid); - RaiseLocalEvent(uid, ref tryingToSleepEvent); - if (tryingToSleepEvent.Cancelled) - return false; - - EnsureComp(uid); - return true; - } - - private bool TryWakeCooldown(EntityUid uid, SleepingComponent? component = null) - { - if (!Resolve(uid, ref component, false)) - return false; - - var curTime = _gameTiming.CurTime; - - if (curTime < component.CoolDownEnd) - { - return false; - } - - component.CoolDownEnd = curTime + component.Cooldown; - return true; - } - - /// - /// Try to wake up. - /// - public bool TryWaking(EntityUid uid, SleepingComponent? component = null, bool force = false, EntityUid? user = null) - { - if (!Resolve(uid, ref component, false)) - return false; - - if (!force && HasComp(uid)) - { - if (user != null) - { - _audio.PlayPvs("/Audio/Effects/thudswoosh.ogg", uid, AudioHelpers.WithVariation(0.05f, _robustRandom)); - _popupSystem.PopupEntity(Loc.GetString("wake-other-failure", ("target", Identity.Entity(uid, EntityManager))), uid, Filter.Entities(user.Value), true, Shared.Popups.PopupType.SmallCaution); - } - return false; - } - - if (user != null) - { - _audio.PlayPvs("/Audio/Effects/thudswoosh.ogg", uid, AudioHelpers.WithVariation(0.05f, _robustRandom)); - _popupSystem.PopupEntity(Loc.GetString("wake-other-success", ("target", Identity.Entity(uid, EntityManager))), uid, Filter.Entities(user.Value), true); - } - RemComp(uid); - return true; - } - } -} diff --git a/Content.Server/Carrying/CarryingSystem.cs b/Content.Server/Carrying/CarryingSystem.cs index 72cfeff91d5..224e38bcbde 100644 --- a/Content.Server/Carrying/CarryingSystem.cs +++ b/Content.Server/Carrying/CarryingSystem.cs @@ -168,7 +168,7 @@ private void OnInteractionAttempt(EntityUid uid, BeingCarriedComponent component var targetParent = Transform(args.Target.Value).ParentUid; if (args.Target.Value != component.Carrier && targetParent != component.Carrier && targetParent != uid) - args.Cancel(); + args.Cancelled = true; } /// @@ -202,7 +202,7 @@ private void OnStandAttempt(EntityUid uid, BeingCarriedComponent component, Stan private void OnInteractedWith(EntityUid uid, BeingCarriedComponent component, GettingInteractedWithAttemptEvent args) { if (args.Uid != component.Carrier) - args.Cancel(); + args.Cancelled = true; } private void OnPullAttempt(EntityUid uid, BeingCarriedComponent component, PullAttemptEvent args) diff --git a/Content.Server/Clothing/Systems/LoadoutSystem.cs b/Content.Server/Clothing/Systems/LoadoutSystem.cs index 4c357c58642..537ca097856 100644 --- a/Content.Server/Clothing/Systems/LoadoutSystem.cs +++ b/Content.Server/Clothing/Systems/LoadoutSystem.cs @@ -2,6 +2,7 @@ using Content.Server.GameTicking; using Content.Server.Paint; using Content.Server.Players.PlayTimeTracking; +using Content.Server.Station.Systems; using Content.Shared.CCVar; using Content.Shared.Clothing.Loadouts.Prototypes; using Content.Shared.Clothing.Loadouts.Systems; @@ -33,6 +34,7 @@ public sealed class LoadoutSystem : EntitySystem [Dependency] private readonly ISerializationManager _serialization = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IComponentFactory _componentFactory = default!; + [Dependency] private readonly StationSpawningSystem _stationSpawning = default!; public override void Initialize() @@ -43,10 +45,13 @@ public override void Initialize() private void OnPlayerSpawnComplete(PlayerSpawnCompleteEvent ev) { - if (ev.JobId == null || - !_configurationManager.GetCVar(CCVars.GameLoadoutsEnabled)) + if (ev.JobId == null + || !_protoMan.TryIndex(ev.JobId, out var job) + || !_configurationManager.GetCVar(CCVars.GameLoadoutsEnabled)) return; + _stationSpawning.EquipJobName(ev.Mob, job); + ApplyCharacterLoadout( ev.Mob, ev.JobId, @@ -108,7 +113,6 @@ public void ApplyCharacterLoadout( function.OnPlayerSpawn(uid, loadout.Item1, _componentFactory, EntityManager, _serialization); } - // Pick the heirloom if (heirlooms.Any()) { diff --git a/Content.Server/Communications/CommunicationsConsoleSystem.cs b/Content.Server/Communications/CommunicationsConsoleSystem.cs index c3e5b912549..de77d611c37 100644 --- a/Content.Server/Communications/CommunicationsConsoleSystem.cs +++ b/Content.Server/Communications/CommunicationsConsoleSystem.cs @@ -182,10 +182,6 @@ private static bool CanAnnounce(CommunicationsConsoleComponent comp) 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(console, out var accessReaderComponent) && !HasComp(console)) { return _accessReaderSystem.IsAllowed(user, console, accessReaderComponent); diff --git a/Content.Server/Damage/ForceSay/DamageForceSaySystem.cs b/Content.Server/Damage/ForceSay/DamageForceSaySystem.cs index 186fc91c46e..bc61c5d141a 100644 --- a/Content.Server/Damage/ForceSay/DamageForceSaySystem.cs +++ b/Content.Server/Damage/ForceSay/DamageForceSaySystem.cs @@ -1,3 +1,4 @@ +using Content.Shared.Bed.Sleep; using Content.Shared.Damage; using Content.Shared.Damage.ForceSay; using Content.Shared.FixedPoint; diff --git a/Content.Server/DeviceNetwork/Systems/DeviceNetworkRequiresPowerSystem.cs b/Content.Server/DeviceNetwork/Systems/DeviceNetworkRequiresPowerSystem.cs index f47a5df8ac4..6e7bd255c5d 100644 --- a/Content.Server/DeviceNetwork/Systems/DeviceNetworkRequiresPowerSystem.cs +++ b/Content.Server/DeviceNetwork/Systems/DeviceNetworkRequiresPowerSystem.cs @@ -1,7 +1,6 @@ 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; diff --git a/Content.Server/Flight/FlightSystem.cs b/Content.Server/Flight/FlightSystem.cs index 4493967fe9e..5e3ef354f22 100644 --- a/Content.Server/Flight/FlightSystem.cs +++ b/Content.Server/Flight/FlightSystem.cs @@ -1,4 +1,5 @@ +using Content.Shared.Bed.Sleep; using Content.Shared.Cuffs.Components; using Content.Shared.Damage.Components; using Content.Shared.DoAfter; diff --git a/Content.Server/InteractionVerbs/Actions/ToggleSleepingAction.cs b/Content.Server/InteractionVerbs/Actions/ToggleSleepingAction.cs index 588853a8d4f..97fd86fd915 100644 --- a/Content.Server/InteractionVerbs/Actions/ToggleSleepingAction.cs +++ b/Content.Server/InteractionVerbs/Actions/ToggleSleepingAction.cs @@ -1,4 +1,3 @@ -using Content.Server.Bed.Sleep; using Content.Shared.Bed.Sleep; using Content.Shared.InteractionVerbs; using Content.Shared.Mobs.Components; diff --git a/Content.Server/Light/EntitySystems/LitOnPoweredSystem.cs b/Content.Server/Light/EntitySystems/LitOnPoweredSystem.cs index 3c5f7eaecb2..5c66d65b573 100644 --- a/Content.Server/Light/EntitySystems/LitOnPoweredSystem.cs +++ b/Content.Server/Light/EntitySystems/LitOnPoweredSystem.cs @@ -2,7 +2,6 @@ using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; using Content.Shared.Power; -using Content.Shared.Power.Components; namespace Content.Server.Light.EntitySystems { diff --git a/Content.Server/Light/EntitySystems/PoweredLightSystem.cs b/Content.Server/Light/EntitySystems/PoweredLightSystem.cs index c4a07b56a86..58ffd4f7b9d 100644 --- a/Content.Server/Light/EntitySystems/PoweredLightSystem.cs +++ b/Content.Server/Light/EntitySystems/PoweredLightSystem.cs @@ -27,7 +27,6 @@ using Content.Shared.Damage.Systems; using Content.Shared.Damage.Components; using Content.Shared.Power; -using Content.Shared.Power.Components; namespace Content.Server.Light.EntitySystems { diff --git a/Content.Server/Mind/MindSystem.cs b/Content.Server/Mind/MindSystem.cs index 4271d76b445..2e7c31ec7a6 100644 --- a/Content.Server/Mind/MindSystem.cs +++ b/Content.Server/Mind/MindSystem.cs @@ -341,13 +341,13 @@ public override void SetUserId(EntityUid mindId, NetUserId? userId, MindComponen } } - 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); diff --git a/Content.Server/Power/EntitySystems/ChargerSystem.cs b/Content.Server/Power/EntitySystems/ChargerSystem.cs index 2aa69024df3..40b998a95d0 100644 --- a/Content.Server/Power/EntitySystems/ChargerSystem.cs +++ b/Content.Server/Power/EntitySystems/ChargerSystem.cs @@ -8,7 +8,6 @@ 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; @@ -44,7 +43,37 @@ private void OnStartup(EntityUid uid, ChargerComponent component, ComponentStart private void OnChargerExamine(EntityUid uid, ChargerComponent component, ExaminedEvent args) { - args.PushMarkup(Loc.GetString("charger-examine", ("color", "yellow"), ("chargeRate", (int) component.ChargeRate))); + using (args.PushGroup(nameof(ChargerComponent))) + { + // rate at which the charger charges + args.PushMarkup(Loc.GetString("charger-examine", ("color", "yellow"), ("chargeRate", (int) component.ChargeRate))); + + // try to get contents of the charger + if (!_container.TryGetContainer(uid, component.SlotId, out var container)) + return; + + if (HasComp(uid)) + return; + + // if charger is empty and not a power cell type charger, add empty message + // power cells have their own empty message by default, for things like flash lights + if (container.ContainedEntities.Count == 0) + { + args.PushMarkup(Loc.GetString("charger-empty")); + } + else + { + // add how much each item is charged it + foreach (var contained in container.ContainedEntities) + { + if (!TryComp(contained, out var battery)) + continue; + + var chargePercentage = (battery.CurrentCharge / battery.MaxCharge) * 100; + args.PushMarkup(Loc.GetString("charger-content", ("chargePercentage", (int) chargePercentage))); + } + } + } } public override void Update(float frameTime) @@ -202,7 +231,7 @@ private CellChargerStatus GetStatus(EntityUid uid, ChargerComponent component) return CellChargerStatus.Charging; } - + private void TransferPower(EntityUid uid, EntityUid targetEntity, ChargerComponent component, float frameTime) { if (!TryComp(uid, out ApcPowerReceiverComponent? receiverComponent)) diff --git a/Content.Server/Power/EntitySystems/PowerNetSystem.cs b/Content.Server/Power/EntitySystems/PowerNetSystem.cs index 8dcb6240a56..6c35ba20083 100644 --- a/Content.Server/Power/EntitySystems/PowerNetSystem.cs +++ b/Content.Server/Power/EntitySystems/PowerNetSystem.cs @@ -5,7 +5,6 @@ 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; diff --git a/Content.Server/Power/Generation/Teg/TegSystem.cs b/Content.Server/Power/Generation/Teg/TegSystem.cs index dd09467efe0..43749dab8fa 100644 --- a/Content.Server/Power/Generation/Teg/TegSystem.cs +++ b/Content.Server/Power/Generation/Teg/TegSystem.cs @@ -11,7 +11,6 @@ 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; diff --git a/Content.Server/Power/Generator/GasPowerReceiverSystem.cs b/Content.Server/Power/Generator/GasPowerReceiverSystem.cs index 5a1bd31a15c..e3979a65192 100644 --- a/Content.Server/Power/Generator/GasPowerReceiverSystem.cs +++ b/Content.Server/Power/Generator/GasPowerReceiverSystem.cs @@ -6,7 +6,6 @@ using Content.Server.Power.Components; using Content.Shared.Atmos; using Content.Shared.Power; -using Content.Shared.Power.Components; namespace Content.Server.Power.Generator; diff --git a/Content.Server/Shadowkin/ShowEtherealSystem.cs b/Content.Server/Shadowkin/ShowEtherealSystem.cs index 151c379afbb..e908bd9ce78 100644 --- a/Content.Server/Shadowkin/ShowEtherealSystem.cs +++ b/Content.Server/Shadowkin/ShowEtherealSystem.cs @@ -70,7 +70,7 @@ private void OnInteractionAttempt(EntityUid uid, ShowEtherealComponent component || !HasComp(args.Target)) return; - args.Cancel(); + args.Cancelled = true; if (_gameTiming.InPrediction) return; @@ -85,4 +85,4 @@ private void OnAttackAttempt(EntityUid uid, ShowEtherealComponent component, Att args.Cancel(); } -} \ No newline at end of file +} diff --git a/Content.Server/Shuttles/Systems/ArrivalsSystem.cs b/Content.Server/Shuttles/Systems/ArrivalsSystem.cs index 9dabf6f40a0..f7b5f220e25 100644 --- a/Content.Server/Shuttles/Systems/ArrivalsSystem.cs +++ b/Content.Server/Shuttles/Systems/ArrivalsSystem.cs @@ -10,6 +10,7 @@ using Content.Server.Shuttles.Components; using Content.Server.Shuttles.Events; using Content.Server.Spawners.Components; +using Content.Server.Spawners.EntitySystems; using Content.Server.Station.Components; using Content.Server.Station.Events; using Content.Server.Station.Systems; @@ -20,6 +21,7 @@ using Content.Shared.Movement.Components; using Content.Shared.Parallax.Biomes; using Content.Shared.Roles; +using Content.Shared.Preferences; using Content.Shared.Salvage; using Content.Shared.Shuttles.Components; using Content.Shared.Tiles; @@ -64,6 +66,11 @@ public sealed class ArrivalsSystem : EntitySystem /// public bool Enabled { get; private set; } + /// + /// Flags if all players spawning at the departure terminal have godmode until they leave the terminal. + /// + public bool ArrivalsGodmode { get; private set; } + /// /// The first arrival is a little early, to save everyone 10s /// @@ -80,6 +87,8 @@ public override void Initialize() { base.Initialize(); + SubscribeLocalEvent(HandlePlayerSpawning, before: new []{ typeof(ContainerSpawnPointSystem), typeof(SpawnPointSystem)}); + SubscribeLocalEvent(OnStationPostInit); SubscribeLocalEvent(OnShuttleStartup); @@ -95,7 +104,10 @@ public override void Initialize() // Don't invoke immediately as it will get set in the natural course of things. Enabled = _cfgManager.GetCVar(CCVars.ArrivalsShuttles); - Subs.CVar(_cfgManager, CCVars.ArrivalsShuttles, SetArrivals); + ArrivalsGodmode = _cfgManager.GetCVar(CCVars.GodmodeArrivals); + + _cfgManager.OnValueChanged(CCVars.ArrivalsShuttles, SetArrivals); + _cfgManager.OnValueChanged(CCVars.GodmodeArrivals, b => ArrivalsGodmode = b); // Command so admins can set these for funsies _console.RegisterCommand("arrivals", ArrivalsCommand, ArrivalsCompletion); @@ -309,6 +321,9 @@ public void HandlePlayerSpawning(PlayerSpawningEvent ev) if (ev.SpawnResult != null) return; + if (ev.HumanoidCharacterProfile?.SpawnPriority != SpawnPriorityPreference.Arrivals) + return; + // Only works on latejoin even if enabled. if (!Enabled || _ticker.RunLevel != GameRunLevel.InRound) return; diff --git a/Content.Server/Silicon/Charge/Systems/SiliconChargeDeathSystem.cs b/Content.Server/Silicon/Charge/Systems/SiliconChargeDeathSystem.cs index d4d1db5ed96..41ee269788d 100644 --- a/Content.Server/Silicon/Charge/Systems/SiliconChargeDeathSystem.cs +++ b/Content.Server/Silicon/Charge/Systems/SiliconChargeDeathSystem.cs @@ -1,6 +1,5 @@ using Content.Server.Power.Components; using Content.Shared.Silicon.Systems; -using Content.Server.Bed.Sleep; using Content.Shared.Bed.Sleep; using Content.Server.Silicon.Charge; using Content.Server.Humanoid; @@ -63,7 +62,7 @@ private void SiliconDead(EntityUid uid, SiliconDownOnDeadComponent siliconDeadCo private void SiliconUnDead(EntityUid uid, SiliconDownOnDeadComponent siliconDeadComp, BatteryComponent? batteryComp, EntityUid batteryUid) { RemComp(uid); - _sleep.TryWaking(uid, null, true); + _sleep.TryWaking(uid, true); siliconDeadComp.Dead = false; diff --git a/Content.Server/Silicons/Laws/SiliconLawSystem.cs b/Content.Server/Silicons/Laws/SiliconLawSystem.cs index 0c0f68c23f3..6b7df52a6eb 100644 --- a/Content.Server/Silicons/Laws/SiliconLawSystem.cs +++ b/Content.Server/Silicons/Laws/SiliconLawSystem.cs @@ -5,12 +5,10 @@ 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; @@ -19,10 +17,10 @@ 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; @@ -32,11 +30,9 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem [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!; /// @@ -44,7 +40,6 @@ public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnComponentShutdown); SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent(OnMindAdded); SubscribeLocalEvent(OnToggleLawsScreen); @@ -58,15 +53,8 @@ public override void Initialize() SubscribeLocalEvent(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); } @@ -92,7 +80,7 @@ private void OnToggleLawsScreen(EntityUid uid, SiliconLawBoundComponent componen private void OnBoundUIOpened(EntityUid uid, SiliconLawBoundComponent component, BoundUIOpenedEvent args) { - _entityManager.TryGetComponent(uid, out var intrinsicRadio); + TryComp(uid, out IntrinsicRadioTransmitterComponent? intrinsicRadio); var radioChannels = intrinsicRadio?.Channels; var state = new SiliconLawBuiState(GetLaws(uid).Laws, radioChannels); @@ -264,9 +252,9 @@ public void NotifyLawsChanged(EntityUid uid) /// /// Extract all the laws from a lawset's prototype ids. /// - public SiliconLawset GetLawset(string lawset) + public SiliconLawset GetLawset(ProtoId lawset) { - var proto = _prototype.Index(lawset); + var proto = _prototype.Index(lawset); var laws = new SiliconLawset() { Laws = new List(proto.Laws.Count) @@ -294,6 +282,21 @@ public void SetLaws(List newLaws, EntityUid target) component.Lawset.Laws = newLaws; NotifyLawsChanged(target); } + + protected override void OnUpdaterInsert(Entity 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)] diff --git a/Content.Server/Silicons/StationAi/AiInteractWireAction.cs b/Content.Server/Silicons/StationAi/AiInteractWireAction.cs new file mode 100644 index 00000000000..c92c825b32b --- /dev/null +++ b/Content.Server/Silicons/StationAi/AiInteractWireAction.cs @@ -0,0 +1,37 @@ +using Content.Server.Wires; +using Content.Shared.Doors; +using Content.Shared.Silicons.StationAi; +using Content.Shared.Wires; + +namespace Content.Server.Silicons.StationAi; + +/// +/// Controls whether an AI can interact with the target entity. +/// +public sealed partial class AiInteractWireAction : ComponentWireAction +{ + 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() + .SetWhitelistEnabled((component.Owner, component), false, announce: true); + } + + public override bool Mend(EntityUid user, Wire wire, StationAiWhitelistComponent component) + { + return EntityManager.System() + .SetWhitelistEnabled((component.Owner, component), true); + } + + public override void Pulse(EntityUid user, Wire wire, StationAiWhitelistComponent component) + { + } +} diff --git a/Content.Server/Silicons/StationAi/AiVisionWireAction.cs b/Content.Server/Silicons/StationAi/AiVisionWireAction.cs new file mode 100644 index 00000000000..3523f4d38f0 --- /dev/null +++ b/Content.Server/Silicons/StationAi/AiVisionWireAction.cs @@ -0,0 +1,40 @@ +using Content.Server.Wires; +using Content.Shared.Doors; +using Content.Shared.Silicons.StationAi; +using Content.Shared.StationAi; +using Content.Shared.Wires; + +namespace Content.Server.Silicons.StationAi; + +/// +/// Handles StationAiVision functionality for the attached entity. +/// +public sealed partial class AiVisionWireAction : ComponentWireAction +{ + 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() + .SetVisionEnabled((component.Owner, component), false, announce: true); + } + + public override bool Mend(EntityUid user, Wire wire, StationAiVisionComponent component) + { + return EntityManager.System() + .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. + } +} diff --git a/Content.Server/Silicons/StationAi/StationAiSystem.cs b/Content.Server/Silicons/StationAi/StationAiSystem.cs new file mode 100644 index 00000000000..846497387d2 --- /dev/null +++ b/Content.Server/Silicons/StationAi/StationAiSystem.cs @@ -0,0 +1,76 @@ +using System.Linq; +using Content.Server.Chat.Managers; +using Content.Server.Chat.Systems; +using Content.Shared.Chat; +using Content.Shared.Silicons.StationAi; +using Content.Shared.StationAi; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Map.Components; +using Robust.Shared.Player; + +namespace Content.Server.Silicons.StationAi; + +public sealed class StationAiSystem : SharedStationAiSystem +{ + [Dependency] private readonly IChatManager _chats = default!; + [Dependency] private readonly EntityLookupSystem _lookup = default!; + + private readonly HashSet> _ais = new(); + + public override bool SetVisionEnabled(Entity 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 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. + } +} diff --git a/Content.Server/Sound/SpamEmitSoundRequirePowerSystem.cs b/Content.Server/Sound/SpamEmitSoundRequirePowerSystem.cs index d2c2a8a1ca7..1ad1bb6c0a1 100644 --- a/Content.Server/Sound/SpamEmitSoundRequirePowerSystem.cs +++ b/Content.Server/Sound/SpamEmitSoundRequirePowerSystem.cs @@ -1,7 +1,6 @@ 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; diff --git a/Content.Server/Spawners/EntitySystems/ContainerSpawnPointSystem.cs b/Content.Server/Spawners/EntitySystems/ContainerSpawnPointSystem.cs index 4d38571b90f..6b0033124e5 100644 --- a/Content.Server/Spawners/EntitySystems/ContainerSpawnPointSystem.cs +++ b/Content.Server/Spawners/EntitySystems/ContainerSpawnPointSystem.cs @@ -1,8 +1,11 @@ using Content.Server.GameTicking; using Content.Server.Spawners.Components; using Content.Server.Station.Systems; +using Content.Shared.Preferences; +using Content.Shared.Roles; using Robust.Server.Containers; using Robust.Shared.Containers; +using Robust.Shared.Prototypes; using Robust.Shared.Random; namespace Content.Server.Spawners.EntitySystems; @@ -11,17 +14,25 @@ public sealed class ContainerSpawnPointSystem : EntitySystem { [Dependency] private readonly GameTicker _gameTicker = default!; [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly IPrototypeManager _proto = default!; [Dependency] private readonly ContainerSystem _container = default!; [Dependency] private readonly StationSystem _station = default!; [Dependency] private readonly StationSpawningSystem _stationSpawning = default!; + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(HandlePlayerSpawning, before: new []{ typeof(SpawnPointSystem) }); + } + public void HandlePlayerSpawning(PlayerSpawningEvent args) { if (args.SpawnResult != null) return; - // DeltaV - Ignore these two desired spawn types - if (args.DesiredSpawnPointType is SpawnPointType.Observer or SpawnPointType.LateJoin) + // If it's just a spawn pref check if it's for cryo (silly). + if (args.HumanoidCharacterProfile?.SpawnPriority != SpawnPriorityPreference.Cryosleep && + (!_proto.TryIndex(args.Job?.Prototype, out var jobProto) || jobProto.JobEntity == null)) return; var query = EntityQueryEnumerator(); @@ -33,11 +44,12 @@ public void HandlePlayerSpawning(PlayerSpawningEvent args) continue; // DeltaV - Custom override for override spawnpoints, only used for prisoners currently. This shouldn't run for any other jobs - if (args.DesiredSpawnPointType == SpawnPointType.Job) + if (args.DesiredSpawnPointType == SpawnPointType.Job + && spawnPoint.SpawnType == SpawnPointType.Job + && args.Job is not null + && spawnPoint.Job is not "" + && spawnPoint.Job == args.Job.Prototype) { - if (spawnPoint.SpawnType != SpawnPointType.Job || spawnPoint.Job != args.Job?.Prototype) - continue; - possibleContainers.Add((uid, spawnPoint, container, xform)); continue; } diff --git a/Content.Server/Station/Systems/StationSpawningSystem.cs b/Content.Server/Station/Systems/StationSpawningSystem.cs index 8375622c1eb..85f5662b421 100644 --- a/Content.Server/Station/Systems/StationSpawningSystem.cs +++ b/Content.Server/Station/Systems/StationSpawningSystem.cs @@ -53,19 +53,11 @@ public sealed class StationSpawningSystem : SharedStationSpawningSystem private bool _randomizeCharacters; - private Dictionary> _spawnerCallbacks = new(); - /// public override void Initialize() { base.Initialize(); Subs.CVar(_configurationManager, CCVars.ICRandomCharacters, e => _randomizeCharacters = e, true); - - _spawnerCallbacks = new Dictionary>() - { - { SpawnPriorityPreference.Arrivals, _arrivalsSystem.HandlePlayerSpawning }, - { SpawnPriorityPreference.Cryosleep, _containerSpawnPointSystem.HandlePlayerSpawning } - }; } /// @@ -88,31 +80,7 @@ public override void Initialize() var ev = new PlayerSpawningEvent(job, profile, station, spawnPointType); - if (station != null && profile != null) - { - // Try to call the character's preferred spawner first. - if (_spawnerCallbacks.TryGetValue(profile.SpawnPriority, out var preferredSpawner)) - { - preferredSpawner(ev); - - foreach (var (key, remainingSpawner) in _spawnerCallbacks) - { - if (key == profile.SpawnPriority) - continue; - - remainingSpawner(ev); - } - } - else - { - // Call all of them in the typical order. - foreach (var typicalSpawner in _spawnerCallbacks.Values) - typicalSpawner(ev); - } - } - RaiseLocalEvent(ev); - DebugTools.Assert(ev.SpawnResult is { Valid: true } or null); return ev.SpawnResult; @@ -131,7 +99,7 @@ public override void Initialize() /// The station this player is being spawned on. /// The entity to use, if one already exists. /// The spawned entity - public EntityUid SpawnPlayerMob( + public EntityUid SpawnPlayerMob( EntityCoordinates coordinates, JobComponent? job, HumanoidCharacterProfile? profile, diff --git a/Content.Server/StationEvents/Components/RandomSentienceRuleComponent.cs b/Content.Server/StationEvents/Components/RandomSentienceRuleComponent.cs index 98ebf06595d..369fce9725c 100644 --- a/Content.Server/StationEvents/Components/RandomSentienceRuleComponent.cs +++ b/Content.Server/StationEvents/Components/RandomSentienceRuleComponent.cs @@ -1,9 +1,13 @@ -using Content.Server.StationEvents.Events; +using Content.Server.StationEvents.Events; namespace Content.Server.StationEvents.Components; [RegisterComponent, Access(typeof(RandomSentienceRule))] public sealed partial class RandomSentienceRuleComponent : Component { + [DataField] + public int MinSentiences = 1; + [DataField] + public int MaxSentiences = 1; } diff --git a/Content.Server/StationEvents/Components/SentienceTargetComponent.cs b/Content.Server/StationEvents/Components/SentienceTargetComponent.cs index f8f7e587c19..4fb5e4be599 100644 --- a/Content.Server/StationEvents/Components/SentienceTargetComponent.cs +++ b/Content.Server/StationEvents/Components/SentienceTargetComponent.cs @@ -1,10 +1,13 @@ -using Content.Server.StationEvents.Events; +using Content.Server.StationEvents.Events; namespace Content.Server.StationEvents.Components; [RegisterComponent, Access(typeof(RandomSentienceRule))] public sealed partial class SentienceTargetComponent : Component { - [DataField("flavorKind", required: true)] + [DataField(required: true)] public string FlavorKind = default!; + + [DataField] + public float Weight = 1.0f; } diff --git a/Content.Server/StationEvents/Events/RandomSentienceRule.cs b/Content.Server/StationEvents/Events/RandomSentienceRule.cs index 2fb733e1a67..308f37fdeb6 100644 --- a/Content.Server/StationEvents/Events/RandomSentienceRule.cs +++ b/Content.Server/StationEvents/Events/RandomSentienceRule.cs @@ -1,9 +1,13 @@ -using System.Linq; using Content.Server.Announcements.Systems; +using System.Linq; +using Content.Shared.Dataset; using Content.Server.Ghost.Roles.Components; using Content.Server.Station.Components; using Content.Server.StationEvents.Components; using Content.Shared.GameTicking.Components; +using Content.Shared.Random.Helpers; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; namespace Content.Server.StationEvents.Events; @@ -11,26 +15,46 @@ public sealed class RandomSentienceRule : StationEventSystem stationsToNotify = new(); + if (!TryGetRandomStation(out var randomStation)) + return; var targetList = new List>(); - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var targetUid, out var target)) + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var targetUid, out var target, out var xform)) { + if (StationSystem.GetOwningStation(targetUid, xform) != randomStation) + continue; + targetList.Add((targetUid, target)); } - RobustRandom.Shuffle(targetList); - - var toMakeSentient = RobustRandom.Next(2, 5); + var toMakeSentient = _random.Next(component.MinSentiences, component.MaxSentiences); + var stationsToNotify = new List(); var groups = new HashSet(); - foreach (var target in targetList) + for (var i = 0; i < toMakeSentient && targetList.Count > 0; i++) { - if (toMakeSentient-- == 0) - break; + // weighted random to pick a sentience target + var totalWeight = targetList.Sum(x => x.Comp.Weight); + // This initial target should never be picked. + // It's just so that target doesn't need to be nullable and as a safety fallback for id floating point errors ever mess up the comparison in the foreach. + var target = targetList[0]; + var chosenWeight = _random.NextFloat(totalWeight); + var currentWeight = 0.0; + foreach (var potentialTarget in targetList) + { + currentWeight += potentialTarget.Comp.Weight; + if (currentWeight > chosenWeight) + { + target = potentialTarget; + break; + } + } + targetList.Remove(target); RemComp(target); var ghostRole = EnsureComp(target); @@ -50,10 +74,10 @@ protected override void Started(EntityUid uid, RandomSentienceRuleComponent comp foreach (var target in targetList) { - var station = StationSystem.GetOwningStation(target); - if(station == null) + var targetStation = StationSystem.GetOwningStation(target); + if(targetStation == null) continue; - stationsToNotify.Add((EntityUid) station); + stationsToNotify.Add((EntityUid) targetStation); } foreach (var station in stationsToNotify) { @@ -63,10 +87,11 @@ protected override void Started(EntityUid uid, RandomSentienceRuleComponent comp "station-event-random-sentience-announcement", null, Color.Gold, - null, null, + null, + null, ("kind1", kind1), ("kind2", kind2), ("kind3", kind3), ("amount", groupList.Count), - ("data", Loc.GetString($"random-sentience-event-data-{RobustRandom.Next(1, 6)}")), - ("strength", Loc.GetString($"random-sentience-event-strength-{RobustRandom.Next(1, 8)}")) + ("data", _random.Pick(_prototype.Index("RandomSentienceEventData"))), + ("strength", _random.Pick(_prototype.Index("RandomSentienceEventStrength"))) ); } } diff --git a/Content.Server/Traits/Assorted/SingerSystem.cs b/Content.Server/Traits/Assorted/SingerSystem.cs index 78a4101a65f..95087c0f1ee 100644 --- a/Content.Server/Traits/Assorted/SingerSystem.cs +++ b/Content.Server/Traits/Assorted/SingerSystem.cs @@ -2,6 +2,7 @@ using Content.Server.Speech.Components; using Content.Server.UserInterface; using Content.Shared.ActionBlocker; +using Content.Shared.Bed.Sleep; using Content.Shared.Damage; using Content.Shared.Damage.ForceSay; using Content.Shared.FixedPoint; diff --git a/Content.Server/Traits/TraitSystem.cs b/Content.Server/Traits/TraitSystem.cs index 3e0866a2bab..57103c4a5d0 100644 --- a/Content.Server/Traits/TraitSystem.cs +++ b/Content.Server/Traits/TraitSystem.cs @@ -48,6 +48,9 @@ private void OnPlayerSpawnComplete(PlayerSpawnCompleteEvent args) { var pointsTotal = _configuration.GetCVar(CCVars.GameTraitsDefaultPoints); var traitSelections = _configuration.GetCVar(CCVars.GameTraitsMax); + if (args.JobId is not null && !_prototype.TryIndex(args.JobId, out var jobPrototype) + && jobPrototype is not null && !jobPrototype.ApplyTraits) + return; foreach (var traitId in args.Profile.TraitPreferences) { diff --git a/Content.Server/Weapons/Melee/EnergySword/EnergySwordSystem.cs b/Content.Server/Weapons/Melee/EnergySword/EnergySwordSystem.cs index 5970e163196..c9be87c6231 100644 --- a/Content.Server/Weapons/Melee/EnergySword/EnergySwordSystem.cs +++ b/Content.Server/Weapons/Melee/EnergySword/EnergySwordSystem.cs @@ -38,7 +38,7 @@ private void OnInteractUsing(EntityUid uid, EnergySwordComponent comp, InteractU if (args.Handled) return; - if (!_toolSystem.HasQuality(args.Used, "Pulsing")) + if (!_toolSystem.HasQuality(args.Used, SharedToolSystem.PulseQuality)) return; args.Handled = true; diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactElectricityTriggerSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactElectricityTriggerSystem.cs index 019e09bbbbc..9d2fd58980f 100644 --- a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactElectricityTriggerSystem.cs +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactElectricityTriggerSystem.cs @@ -44,7 +44,7 @@ private void OnInteractUsing(EntityUid uid, ArtifactElectricityTriggerComponent 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); diff --git a/Content.Server/_Shitmed/Autodoc/Systems/AutodocSystem.cs b/Content.Server/_Shitmed/Autodoc/Systems/AutodocSystem.cs index d1af790eaa9..7002734fd3e 100644 --- a/Content.Server/_Shitmed/Autodoc/Systems/AutodocSystem.cs +++ b/Content.Server/_Shitmed/Autodoc/Systems/AutodocSystem.cs @@ -6,7 +6,8 @@ using Content.Server.Power.EntitySystems; using Content.Shared._Shitmed.Autodoc.Components; using Content.Shared._Shitmed.Autodoc.Systems; -using Content.Server.Bed.Sleep; +using Content.Shared.Bed.Sleep; + namespace Content.Server._Shitmed.Autodoc.Systems; @@ -15,7 +16,7 @@ public sealed class AutodocSystem : SharedAutodocSystem [Dependency] private readonly InternalsSystem _internals = default!; [Dependency] private readonly ChatSystem _chat = default!; [Dependency] private readonly PowerReceiverSystem _power = default!; - [Dependency] private readonly SleepingSystem _sleepingSystem = default!; // Sleeping isnt shared yet. + [Dependency] private readonly SleepingSystem _sleepingSystem = default!; public override void Update(float frameTime) { diff --git a/Content.Shared/ActionBlocker/ActionBlockerSystem.cs b/Content.Shared/ActionBlocker/ActionBlockerSystem.cs index d2883b5ef5b..457e3875d47 100644 --- a/Content.Shared/ActionBlocker/ActionBlockerSystem.cs +++ b/Content.Shared/ActionBlocker/ActionBlockerSystem.cs @@ -4,6 +4,7 @@ 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.Mobs; @@ -26,9 +27,14 @@ public sealed class ActionBlockerSystem : EntitySystem { [Dependency] private readonly SharedContainerSystem _container = default!; + private EntityQuery _complexInteractionQuery; + public override void Initialize() { base.Initialize(); + + _complexInteractionQuery = GetEntityQuery(); + SubscribeLocalEvent(OnMoverStartup); } @@ -57,6 +63,15 @@ public bool UpdateCanMove(EntityUid uid, InputMoverComponent? component = null) return !ev.Cancelled; } + /// + /// 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. + /// + public bool CanComplexInteract(EntityUid user) + { + return _complexInteractionQuery.HasComp(user); + } + /// /// Raises an event directed at both the user and the target entity to check whether a user is capable of /// interacting with this entity. @@ -74,7 +89,7 @@ public bool CanInteract(EntityUid user, EntityUid? target) return false; var ev = new InteractionAttemptEvent(user, target); - RaiseLocalEvent(user, ev); + RaiseLocalEvent(user, ref ev); if (ev.Cancelled) return false; @@ -83,7 +98,7 @@ public bool CanInteract(EntityUid user, EntityUid? target) return true; var targetEv = new GettingInteractedWithAttemptEvent(user, target); - RaiseLocalEvent(target.Value, targetEv); + RaiseLocalEvent(target.Value, ref targetEv); return !targetEv.Cancelled; } @@ -114,7 +129,7 @@ public bool CanUseHeldEntity(EntityUid user, EntityUid used) public bool CanConsciouslyPerformAction(EntityUid user) { var ev = new ConsciousAttemptEvent(user); - RaiseLocalEvent(user, ev); + RaiseLocalEvent(user, ref ev); return !ev.Cancelled; } diff --git a/Content.Shared/Administration/SharedAdminFrozenSystem.cs b/Content.Shared/Administration/SharedAdminFrozenSystem.cs index 2fa22e00052..259df2bdf2a 100644 --- a/Content.Shared/Administration/SharedAdminFrozenSystem.cs +++ b/Content.Shared/Administration/SharedAdminFrozenSystem.cs @@ -11,6 +11,7 @@ namespace Content.Shared.Administration; +// TODO deduplicate with BlockMovementComponent public abstract class SharedAdminFrozenSystem : EntitySystem { [Dependency] private readonly ActionBlockerSystem _blocker = default!; @@ -23,7 +24,7 @@ public override void Initialize() SubscribeLocalEvent(OnAttempt); SubscribeLocalEvent(OnAttempt); SubscribeLocalEvent(OnAttempt); - SubscribeLocalEvent(OnAttempt); + SubscribeLocalEvent(OnInteractAttempt); SubscribeLocalEvent(OnStartup); SubscribeLocalEvent(UpdateCanMove); SubscribeLocalEvent(OnUpdateCanMove); @@ -34,6 +35,11 @@ public override void Initialize() SubscribeLocalEvent(OnSpeakAttempt); } + private void OnInteractAttempt(Entity ent, ref InteractionAttemptEvent args) + { + args.Cancelled = true; + } + private void OnSpeakAttempt(EntityUid uid, AdminFrozenComponent component, SpeakAttemptEvent args) { if (!component.Muted) diff --git a/Content.Shared/Bed/Sleep/SharedSleepingSystem.cs b/Content.Shared/Bed/Sleep/SharedSleepingSystem.cs deleted file mode 100644 index aa5578a3a98..00000000000 --- a/Content.Shared/Bed/Sleep/SharedSleepingSystem.cs +++ /dev/null @@ -1,102 +0,0 @@ -using Content.Shared.Actions; -using Content.Shared.Bed.Sleep; -using Content.Shared.Damage.ForceSay; -using Content.Shared.Eye.Blinding.Systems; -using Content.Shared.Pointing; -using Content.Shared.Speech; -using Robust.Shared.Network; -using Robust.Shared.Prototypes; -using Robust.Shared.Timing; - -namespace Content.Server.Bed.Sleep -{ - public abstract class SharedSleepingSystem : EntitySystem - { - [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly SharedActionsSystem _actionsSystem = default!; - [Dependency] private readonly BlindableSystem _blindableSystem = default!; - - [ValidatePrototypeId] private const string WakeActionId = "ActionWake"; - - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(OnMapInit); - SubscribeLocalEvent(OnShutdown); - SubscribeLocalEvent(OnSpeakAttempt); - SubscribeLocalEvent(OnSeeAttempt); - SubscribeLocalEvent(OnPointAttempt); - } - - - private void OnMapInit(EntityUid uid, SleepingComponent component, MapInitEvent args) - { - component.SleepingSince = _gameTiming.CurTime; - - var ev = new SleepStateChangedEvent(true); - RaiseLocalEvent(uid, ev); - _blindableSystem.UpdateIsBlind(uid); - _actionsSystem.AddAction(uid, ref component.WakeAction, WakeActionId, uid); - - // TODO remove hardcoded time. - _actionsSystem.SetCooldown(component.WakeAction, _gameTiming.CurTime, _gameTiming.CurTime + TimeSpan.FromSeconds(2f)); - } - - private void OnShutdown(EntityUid uid, SleepingComponent component, ComponentShutdown args) - { - _actionsSystem.RemoveAction(uid, component.WakeAction); - var ev = new SleepStateChangedEvent(false) - { - TimeSlept = _gameTiming.CurTime - component.SleepingSince - }; - RaiseLocalEvent(uid, ev); - _blindableSystem.UpdateIsBlind(uid); - } - - private void OnSpeakAttempt(EntityUid uid, SleepingComponent component, SpeakAttemptEvent args) - { - // TODO reduce duplication of this behavior with MobStateSystem somehow - if (HasComp(uid)) - { - RemCompDeferred(uid); - return; - } - - args.Cancel(); - } - - private void OnSeeAttempt(EntityUid uid, SleepingComponent component, CanSeeAttemptEvent args) - { - if (component.LifeStage <= ComponentLifeStage.Running) - args.Cancel(); - } - - private void OnPointAttempt(EntityUid uid, SleepingComponent component, PointAttemptEvent args) - { - args.Cancel(); - } - } -} - - -public sealed partial class SleepActionEvent : InstantActionEvent {} - -public sealed partial class WakeActionEvent : InstantActionEvent {} - -/// -/// Raised on an entity when they fall asleep or wake up. -/// -public sealed class SleepStateChangedEvent : EntityEventArgs -{ - public bool FellAsleep = false; - - /// - /// The amount of time this entity slept for. Null if is true. - /// - public TimeSpan? TimeSlept; - - public SleepStateChangedEvent(bool fellAsleep) - { - FellAsleep = fellAsleep; - } -} diff --git a/Content.Shared/Bed/Sleep/SleepingComponent.cs b/Content.Shared/Bed/Sleep/SleepingComponent.cs index e87aca2203b..e43c71769ab 100644 --- a/Content.Shared/Bed/Sleep/SleepingComponent.cs +++ b/Content.Shared/Bed/Sleep/SleepingComponent.cs @@ -1,37 +1,48 @@ using Content.Shared.FixedPoint; +using Robust.Shared.Audio; using Robust.Shared.GameStates; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; namespace Content.Shared.Bed.Sleep; /// /// Added to entities when they go to sleep. /// -[NetworkedComponent, RegisterComponent, AutoGenerateComponentPause(Dirty = true)] +[NetworkedComponent, RegisterComponent] +[AutoGenerateComponentState, AutoGenerateComponentPause(Dirty = true)] public sealed partial class SleepingComponent : Component { /// /// How much damage of any type it takes to wake this entity. /// - [DataField("wakeThreshold")] + [DataField] public FixedPoint2 WakeThreshold = FixedPoint2.New(2); /// /// Cooldown time between users hand interaction. /// - [DataField("cooldown")] - [ViewVariables(VVAccess.ReadWrite)] + [DataField] public TimeSpan Cooldown = TimeSpan.FromSeconds(1f); - [DataField("cooldownEnd", customTypeSerializer:typeof(TimeOffsetSerializer))] - [AutoPausedField] - public TimeSpan CoolDownEnd; - - [DataField("wakeAction")] public EntityUid? WakeAction; + [DataField] + [AutoNetworkedField, AutoPausedField] + public TimeSpan CooldownEnd; /// /// The moment this entity went to sleep. Initialized on MapInit. /// [DataField] public TimeSpan SleepingSince; + + [DataField] + [AutoNetworkedField] + public EntityUid? WakeAction; + + /// + /// Sound to play when another player attempts to wake this entity. + /// + [DataField] + public SoundSpecifier WakeAttemptSound = new SoundPathSpecifier("/Audio/Effects/thudswoosh.ogg") + { + Params = AudioParams.Default.WithVariation(0.05f) + }; } diff --git a/Content.Shared/Bed/Sleep/SleepingSystem.cs b/Content.Shared/Bed/Sleep/SleepingSystem.cs new file mode 100644 index 00000000000..8e6f1193ac7 --- /dev/null +++ b/Content.Shared/Bed/Sleep/SleepingSystem.cs @@ -0,0 +1,333 @@ +using Content.Shared.Actions; +using Content.Shared.Damage; +using Content.Shared.Damage.ForceSay; +using Content.Shared.Examine; +using Content.Shared.Eye.Blinding.Systems; +using Content.Shared.IdentityManagement; +using Content.Shared.Interaction; +using Content.Shared.Interaction.Events; +using Content.Shared.Mobs; +using Content.Shared.Mobs.Components; +using Content.Shared.Pointing; +using Content.Shared.Popups; +using Content.Shared.Slippery; +using Content.Shared.Sound; +using Content.Shared.Sound.Components; +using Content.Shared.Speech; +using Content.Shared.StatusEffect; +using Content.Shared.Stunnable; +using Content.Shared.Verbs; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; + +namespace Content.Shared.Bed.Sleep; + +public sealed partial class SleepingSystem : EntitySystem +{ + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly SharedActionsSystem _actionsSystem = default!; + [Dependency] private readonly BlindableSystem _blindableSystem = default!; + [Dependency] private readonly SharedPopupSystem _popupSystem = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedEmitSoundSystem _emitSound = default!; + [Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!; + + public static readonly EntProtoId SleepActionId = "ActionSleep"; + public static readonly EntProtoId WakeActionId = "ActionWake"; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnBedSleepAction); + + SubscribeLocalEvent(OnSleepStateChanged); + SubscribeLocalEvent(OnWakeAction); + SubscribeLocalEvent(OnSleepAction); + + SubscribeLocalEvent(OnDamageChanged); + SubscribeLocalEvent(OnMobStateChanged); + SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(OnSpeakAttempt); + SubscribeLocalEvent(OnSeeAttempt); + SubscribeLocalEvent(OnPointAttempt); + SubscribeLocalEvent(OnSlip); + SubscribeLocalEvent(OnConsciousAttempt); + SubscribeLocalEvent(OnExamined); + SubscribeLocalEvent>(AddWakeVerb); + SubscribeLocalEvent(OnInteractHand); + + SubscribeLocalEvent(OnInit); + } + + private void OnBedSleepAction(Entity ent, ref SleepActionEvent args) + { + TrySleeping(args.Performer); + } + + private void OnWakeAction(Entity ent, ref WakeActionEvent args) + { + if (TryWakeWithCooldown(ent.Owner)) + args.Handled = true; + } + + private void OnSleepAction(Entity ent, ref SleepActionEvent args) + { + TrySleeping((ent, ent.Comp)); + } + + /// + /// when sleeping component is added or removed, we do some stuff with other components. + /// + private void OnSleepStateChanged(Entity ent, ref SleepStateChangedEvent args) + { + if (args.FellAsleep) + { + // Expiring status effects would remove the components needed for sleeping + _statusEffectsSystem.TryRemoveStatusEffect(ent.Owner, "Stun"); + _statusEffectsSystem.TryRemoveStatusEffect(ent.Owner, "KnockedDown"); + + EnsureComp(ent); + EnsureComp(ent); + + if (TryComp(ent, out var sleepSound)) + { + var emitSound = EnsureComp(ent); + if (HasComp(ent)) + { + emitSound.Sound = sleepSound.Snore; + } + emitSound.MinInterval = sleepSound.Interval; + emitSound.MaxInterval = sleepSound.MaxInterval; + emitSound.PopUp = sleepSound.PopUp; + Dirty(ent.Owner, emitSound); + } + + return; + } + + RemComp(ent); + RemComp(ent); + RemComp(ent); + } + + private void OnMapInit(Entity ent, ref MapInitEvent args) + { + ent.Comp.SleepingSince = _gameTiming.CurTime; + + var ev = new SleepStateChangedEvent(true); + RaiseLocalEvent(ent, ref ev); + _blindableSystem.UpdateIsBlind(ent.Owner); + _actionsSystem.AddAction(ent, ref ent.Comp.WakeAction, WakeActionId, ent); + + // TODO remove hardcoded time. + _actionsSystem.SetCooldown(ent.Comp.WakeAction, _gameTiming.CurTime, _gameTiming.CurTime + TimeSpan.FromSeconds(2f)); + } + + private void OnSpeakAttempt(Entity ent, ref SpeakAttemptEvent args) + { + // TODO reduce duplication of this behavior with MobStateSystem somehow + if (HasComp(ent)) + { + RemCompDeferred(ent); + return; + } + + args.Cancel(); + } + + private void OnSeeAttempt(Entity ent, ref CanSeeAttemptEvent args) + { + if (ent.Comp.LifeStage <= ComponentLifeStage.Running) + args.Cancel(); + } + + private void OnPointAttempt(Entity ent, ref PointAttemptEvent args) + { + args.Cancel(); + } + + private void OnSlip(Entity ent, ref SlipAttemptEvent args) + { + args.Cancel(); + } + + private void OnConsciousAttempt(Entity ent, ref ConsciousAttemptEvent args) + { + args.Cancelled = true; + } + + private void OnExamined(Entity ent, ref ExaminedEvent args) + { + if (args.IsInDetailsRange) + { + args.PushMarkup(Loc.GetString("sleep-examined", ("target", Identity.Entity(ent, EntityManager)))); + } + } + + private void AddWakeVerb(Entity ent, ref GetVerbsEvent args) + { + if (!args.CanInteract || !args.CanAccess) + return; + + var target = args.Target; + var user = args.User; + AlternativeVerb verb = new() + { + Act = () => + { + TryWakeWithCooldown((ent, ent.Comp), user: user); + }, + Text = Loc.GetString("action-name-wake"), + Priority = 2 + }; + + args.Verbs.Add(verb); + } + + /// + /// When you click on a sleeping person with an empty hand, try to wake them. + /// + private void OnInteractHand(Entity ent, ref InteractHandEvent args) + { + args.Handled = true; + + TryWakeWithCooldown((ent, ent.Comp), args.User); + } + + /// + /// Wake up on taking an instance of damage at least the value of WakeThreshold. + /// + private void OnDamageChanged(Entity ent, ref DamageChangedEvent args) + { + if (!args.DamageIncreased || args.DamageDelta == null) + return; + + if (args.DamageDelta.GetTotal() >= ent.Comp.WakeThreshold + && !HasComp(ent)) + TryWaking((ent, ent.Comp)); + } + + /// + /// In crit, we wake up if we are not being forced to sleep. + /// And, you can't sleep when dead... + /// + private void OnMobStateChanged(Entity ent, ref MobStateChangedEvent args) + { + if (args.NewMobState == MobState.Dead) + { + RemComp(ent); + RemComp(ent); + return; + } + if (TryComp(ent, out var spam)) + _emitSound.SetEnabled((ent, spam), args.NewMobState == MobState.Alive); + } + + private void OnInit(Entity ent, ref ComponentInit args) + { + TrySleeping(ent.Owner); + } + + private void Wake(Entity ent) + { + RemComp(ent); + _actionsSystem.RemoveAction(ent, ent.Comp.WakeAction); + + var ev = new SleepStateChangedEvent(false) + { + TimeSlept = _gameTiming.CurTime - ent.Comp.SleepingSince + }; + RaiseLocalEvent(ent, ref ev); + + _blindableSystem.UpdateIsBlind(ent.Owner); + } + + /// + /// Try sleeping. Only mobs can sleep. + /// + public bool TrySleeping(Entity ent) + { + if (!Resolve(ent, ref ent.Comp, logMissing: false)) + return false; + + var tryingToSleepEvent = new TryingToSleepEvent(ent); + RaiseLocalEvent(ent, ref tryingToSleepEvent); + if (tryingToSleepEvent.Cancelled) + return false; + + EnsureComp(ent); + return true; + } + + /// + /// Tries to wake up , with a cooldown between attempts to prevent spam. + /// + public bool TryWakeWithCooldown(Entity ent, EntityUid? user = null) + { + if (!Resolve(ent, ref ent.Comp, false)) + return false; + + var curTime = _gameTiming.CurTime; + + if (curTime < ent.Comp.CooldownEnd) + return false; + + ent.Comp.CooldownEnd = curTime + ent.Comp.Cooldown; + Dirty(ent, ent.Comp); + return TryWaking(ent, user: user); + } + + /// + /// Try to wake up . + /// + public bool TryWaking(Entity ent, bool force = false, EntityUid? user = null) + { + if (!Resolve(ent, ref ent.Comp, false)) + return false; + + if (!force && HasComp(ent)) + { + if (user != null) + { + _audio.PlayPredicted(ent.Comp.WakeAttemptSound, ent, user); + _popupSystem.PopupClient(Loc.GetString("wake-other-failure", ("target", Identity.Entity(ent, EntityManager))), ent, user, PopupType.SmallCaution); + } + return false; + } + + if (user != null) + { + _audio.PlayPredicted(ent.Comp.WakeAttemptSound, ent, user); + _popupSystem.PopupClient(Loc.GetString("wake-other-success", ("target", Identity.Entity(ent, EntityManager))), ent, user); + } + + Wake((ent, ent.Comp)); + return true; + } +} + + +public sealed partial class SleepActionEvent : InstantActionEvent; + +public sealed partial class WakeActionEvent : InstantActionEvent; + +/// +/// Raised on an entity when they fall asleep or wake up. +/// +[ByRefEvent] +public record struct SleepStateChangedEvent +{ + public bool FellAsleep = false; + + /// + /// The amount of time this entity slept for. Null if is true. + /// + public TimeSpan? TimeSlept; + + public SleepStateChangedEvent(bool fellAsleep) + { + FellAsleep = fellAsleep; + } +} diff --git a/Content.Server/Bed/Components/SnoringComponent.cs b/Content.Shared/Bed/Sleep/SnoringComponent.cs similarity index 54% rename from Content.Server/Bed/Components/SnoringComponent.cs rename to Content.Shared/Bed/Sleep/SnoringComponent.cs index 09f80327ba7..2fe92951f07 100644 --- a/Content.Server/Bed/Components/SnoringComponent.cs +++ b/Content.Shared/Bed/Sleep/SnoringComponent.cs @@ -1,9 +1,11 @@ -namespace Content.Server.Bed.Sleep; +using Robust.Shared.GameStates; + +namespace Content.Shared.Bed.Sleep; /// /// This is used for the snoring trait. /// -[RegisterComponent] +[RegisterComponent, NetworkedComponent] public sealed partial class SnoringComponent : Component { diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs index c93eaa77f90..28055d866e8 100644 --- a/Content.Shared/CCVar/CCVars.cs +++ b/Content.Shared/CCVar/CCVars.cs @@ -1738,6 +1738,19 @@ public static readonly CVarDef public static readonly CVarDef ArrivalsReturns = CVarDef.Create("shuttle.arrivals_returns", false, CVar.SERVERONLY); + /// + /// Should all players who spawn at arrivals have godmode until they leave the map? + /// + public static readonly CVarDef GodmodeArrivals = + CVarDef.Create("shuttle.godmode_arrivals", false, CVar.SERVERONLY); + + /// + /// If a grid is split then hide any smaller ones under this mass (kg) from the map. + /// This is useful to avoid split grids spamming out labels. + /// + public static readonly CVarDef HideSplitGridsUnder = + CVarDef.Create("shuttle.hide_split_grids_under", 30, CVar.SERVERONLY); + /// /// Whether to automatically spawn escape shuttles. /// diff --git a/Content.Shared/Climbing/Systems/ClimbSystem.cs b/Content.Shared/Climbing/Systems/ClimbSystem.cs index 722d97213f5..fc5a43cf8a2 100644 --- a/Content.Shared/Climbing/Systems/ClimbSystem.cs +++ b/Content.Shared/Climbing/Systems/ClimbSystem.cs @@ -47,6 +47,7 @@ public sealed partial class ClimbSystem : VirtualController private EntityQuery _fixturesQuery; private EntityQuery _xformQuery; + private EntityQuery _climbableQuery; public override void Initialize() { @@ -373,6 +374,26 @@ private void OnClimbEndCollide(EntityUid uid, ClimbingComponent component, ref E return; } + 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)) + { + return; + } + } + + // TODO: Is this even needed anymore? foreach (var otherFixture in args.OurFixture.Contacts.Keys) { // If it's the other fixture then ignore em diff --git a/Content.Shared/Clothing/Loadouts/Systems/SharedLoadoutSystem.cs b/Content.Shared/Clothing/Loadouts/Systems/SharedLoadoutSystem.cs index e759c904af2..865908c7069 100644 --- a/Content.Shared/Clothing/Loadouts/Systems/SharedLoadoutSystem.cs +++ b/Content.Shared/Clothing/Loadouts/Systems/SharedLoadoutSystem.cs @@ -75,6 +75,8 @@ private void OnMapInit(EntityUid uid, LoadoutComponent component, MapInitEvent a var failedLoadouts = new List(); var allLoadouts = new List<(EntityUid, LoadoutPreference, int)>(); heirlooms = new(); + if (!job.SpawnLoadout) + return (failedLoadouts, allLoadouts); foreach (var loadout in profile.LoadoutPreferences) { diff --git a/Content.Shared/Configurable/ConfigurationComponent.cs b/Content.Shared/Configurable/ConfigurationComponent.cs index 63a0dfe95a5..621871af3ce 100644 --- a/Content.Shared/Configurable/ConfigurationComponent.cs +++ b/Content.Shared/Configurable/ConfigurationComponent.cs @@ -1,5 +1,6 @@ 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; @@ -13,7 +14,7 @@ public sealed partial class ConfigurationComponent : Component public Dictionary Config = new(); [DataField("qualityNeeded", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string QualityNeeded = "Pulsing"; + public string QualityNeeded = SharedToolSystem.PulseQuality; [DataField("validation")] public Regex Validation = new("^[a-zA-Z0-9 ]*$", RegexOptions.Compiled); diff --git a/Content.Shared/Containers/ContainerCompComponent.cs b/Content.Shared/Containers/ContainerCompComponent.cs new file mode 100644 index 00000000000..b1415e0d8b5 --- /dev/null +++ b/Content.Shared/Containers/ContainerCompComponent.cs @@ -0,0 +1,17 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Containers; + +/// +/// Applies container changes whenever an entity is inserted into the specified container on this entity. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class ContainerCompComponent : Component +{ + [DataField(required: true)] + public EntProtoId Proto; + + [DataField(required: true)] + public string Container = string.Empty; +} diff --git a/Content.Shared/Containers/ContainerCompSystem.cs b/Content.Shared/Containers/ContainerCompSystem.cs new file mode 100644 index 00000000000..1e1983a331b --- /dev/null +++ b/Content.Shared/Containers/ContainerCompSystem.cs @@ -0,0 +1,44 @@ +using Robust.Shared.Containers; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Containers; + +/// +/// Applies / removes an entity prototype from a child entity when it's inserted into a container. +/// +public sealed class ContainerCompSystem : EntitySystem +{ + [Dependency] private readonly IPrototypeManager _proto = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnConInsert); + SubscribeLocalEvent(OnConRemove); + } + + private void OnConRemove(Entity ent, ref EntRemovedFromContainerMessage args) + { + if (args.Container.ID != ent.Comp.Container) + return; + + if (_proto.TryIndex(ent.Comp.Container, out var entProto)) + { + foreach (var entry in entProto.Components.Values) + { + RemComp(args.Entity, entry.Component); + } + } + } + + private void OnConInsert(Entity ent, ref EntInsertedIntoContainerMessage args) + { + if (args.Container.ID != ent.Comp.Container) + return; + + if (_proto.TryIndex(ent.Comp.Proto, out var entProto)) + { + EntityManager.AddComponents(args.Entity, entProto.Components); + } + } +} diff --git a/Content.Shared/Containers/ItemSlot/ItemSlotsLockComponent.cs b/Content.Shared/Containers/ItemSlot/ItemSlotsLockComponent.cs new file mode 100644 index 00000000000..0d8901028d7 --- /dev/null +++ b/Content.Shared/Containers/ItemSlot/ItemSlotsLockComponent.cs @@ -0,0 +1,13 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Containers.ItemSlots; + +/// +/// Updates the relevant ItemSlots locks based on +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class ItemSlotsLockComponent : Component +{ + [DataField(required: true)] + public List Slots = new(); +} diff --git a/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.Lock.cs b/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.Lock.cs new file mode 100644 index 00000000000..ee5178df95b --- /dev/null +++ b/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.Lock.cs @@ -0,0 +1,36 @@ +using Content.Shared.Lock; + +namespace Content.Shared.Containers.ItemSlots; + +public sealed partial class ItemSlotsSystem +{ + private void InitializeLock() + { + SubscribeLocalEvent(OnLockMapInit); + SubscribeLocalEvent(OnLockToggled); + } + + private void OnLockMapInit(Entity ent, ref MapInitEvent args) + { + if (!TryComp(ent.Owner, out LockComponent? lockComp)) + return; + + UpdateLocks(ent, lockComp.Locked); + } + + private void OnLockToggled(Entity ent, ref LockToggledEvent args) + { + UpdateLocks(ent, args.Locked); + } + + private void UpdateLocks(Entity ent, bool value) + { + foreach (var slot in ent.Comp.Slots) + { + if (!TryGetSlot(ent.Owner, slot, out var itemSlot)) + continue; + + SetLock(ent.Owner, itemSlot, value); + } + } +} diff --git a/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs b/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs index c8745d17d49..4fe49a6382e 100644 --- a/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs +++ b/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs @@ -26,7 +26,7 @@ namespace Content.Shared.Containers.ItemSlots /// Note when using popups on entities with many slots with InsertOnInteract, EjectOnInteract or EjectOnUse: /// A single use will try to insert to/eject from every slot and generate a popup for each that fails. /// - public sealed class ItemSlotsSystem : EntitySystem + public sealed partial class ItemSlotsSystem : EntitySystem { [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; @@ -43,6 +43,8 @@ public override void Initialize() { base.Initialize(); + InitializeLock(); + SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent(Oninitialize); diff --git a/Content.Shared/Cuffs/SharedCuffableSystem.cs b/Content.Shared/Cuffs/SharedCuffableSystem.cs index 1c8e2ef2b0d..dbc2ba06c93 100644 --- a/Content.Shared/Cuffs/SharedCuffableSystem.cs +++ b/Content.Shared/Cuffs/SharedCuffableSystem.cs @@ -84,7 +84,7 @@ public override void Initialize() SubscribeLocalEvent(CheckAct); SubscribeLocalEvent(CheckAct); SubscribeLocalEvent(CheckAct); - SubscribeLocalEvent(CheckAct); + SubscribeLocalEvent(CheckInteract); SubscribeLocalEvent(OnCuffAfterInteract); SubscribeLocalEvent(OnCuffMeleeHit); @@ -92,6 +92,12 @@ public override void Initialize() SubscribeLocalEvent(OnCuffVirtualItemDeleted); } + private void CheckInteract(Entity ent, ref InteractionAttemptEvent args) + { + if (!ent.Comp.CanStillInteract) + args.Cancelled = true; + } + private void OnUncuffAttempt(ref UncuffAttemptEvent args) { if (args.Cancelled) diff --git a/Content.Shared/Dataset/LocalizedDatasetPrototype.cs b/Content.Shared/Dataset/LocalizedDatasetPrototype.cs new file mode 100644 index 00000000000..8be9967e309 --- /dev/null +++ b/Content.Shared/Dataset/LocalizedDatasetPrototype.cs @@ -0,0 +1,94 @@ +using System.Collections; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; + +namespace Content.Shared.Dataset; + +/// +/// A variant of intended to specify a sequence of LocId strings +/// without having to copy-paste a ton of LocId strings into the YAML. +/// +[Prototype] +public sealed partial class LocalizedDatasetPrototype : IPrototype +{ + /// + /// Identifier for this prototype. + /// + [ViewVariables] + [IdDataField] + public string ID { get; private set; } = default!; + + /// + /// Collection of LocId strings. + /// + [DataField] + public LocalizedDatasetValues Values { get; private set; } = []; +} + +[Serializable, NetSerializable] +[DataDefinition] +public sealed partial class LocalizedDatasetValues : IReadOnlyList +{ + /// + /// String prepended to the index number to generate each LocId string. + /// For example, a prefix of tips-dataset- will generate tips-dataset-1, + /// tips-dataset-2, etc. + /// + [DataField(required: true)] + public string Prefix { get; private set; } = default!; + + /// + /// How many values are in the dataset. + /// + [DataField(required: true)] + public int Count { get; private set; } + + public string this[int index] + { + get + { + if (index > Count || index < 0) + throw new IndexOutOfRangeException(); + return Prefix + index; + } + } + + public IEnumerator GetEnumerator() + { + return new Enumerator(this); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public sealed class Enumerator : IEnumerator + { + private int _index = 0; // Whee, 1-indexing + + private readonly LocalizedDatasetValues _values; + + public Enumerator(LocalizedDatasetValues values) + { + _values = values; + } + + public string Current => _values.Prefix + _index; + + object IEnumerator.Current => Current; + + public void Dispose() { } + + public bool MoveNext() + { + _index++; + return _index <= _values.Count; + } + + public void Reset() + { + _index = 0; + } + } +} diff --git a/Content.Shared/Doors/AirlockWireStatus.cs b/Content.Shared/Doors/AirlockWireStatus.cs index a50ee2c88e9..d3fa15ed1b6 100644 --- a/Content.Shared/Doors/AirlockWireStatus.cs +++ b/Content.Shared/Doors/AirlockWireStatus.cs @@ -8,7 +8,7 @@ public enum AirlockWireStatus PowerIndicator, BoltIndicator, BoltLightIndicator, - AIControlIndicator, + AiControlIndicator, TimingIndicator, SafetyIndicator, } diff --git a/Content.Shared/Examine/ExamineSystemShared.cs b/Content.Shared/Examine/ExamineSystemShared.cs index 397a8f74484..a1c30a2bd03 100644 --- a/Content.Shared/Examine/ExamineSystemShared.cs +++ b/Content.Shared/Examine/ExamineSystemShared.cs @@ -109,12 +109,25 @@ public virtual bool CanExamine(EntityUid examiner, MapCoordinates target, Ignore if (EntityManager.GetComponent(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); + } } /// @@ -209,7 +222,14 @@ public bool InRangeUnOccluded(MapCoordinates origin, MapCoordinates othe public bool InRangeUnOccluded(EntityUid origin, EntityUid other, float range = ExamineRange, Ignored? predicate = null, bool ignoreInsideBlocker = true) { - var entMan = IoCManager.Resolve(); + 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); diff --git a/Content.Shared/Ghost/SharedGhostSystem.cs b/Content.Shared/Ghost/SharedGhostSystem.cs index 6f7370fe581..091775b6c2e 100644 --- a/Content.Shared/Ghost/SharedGhostSystem.cs +++ b/Content.Shared/Ghost/SharedGhostSystem.cs @@ -20,13 +20,19 @@ public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnAttempt); - SubscribeLocalEvent(OnAttempt); + SubscribeLocalEvent(OnAttemptInteract); SubscribeLocalEvent(OnAttempt); SubscribeLocalEvent(OnAttempt); SubscribeLocalEvent(OnAttempt); SubscribeLocalEvent(OnAttempt); } + private void OnAttemptInteract(Entity ent, ref InteractionAttemptEvent args) + { + if (!ent.Comp.CanGhostInteract) + args.Cancelled = true; + } + private void OnAttempt(EntityUid uid, GhostComponent component, CancellableEntityEventArgs args) { if (!component.CanGhostInteract) diff --git a/Content.Shared/Interaction/Components/BlockMovementComponent.cs b/Content.Shared/Interaction/Components/BlockMovementComponent.cs index e308e849601..2125f16efe0 100644 --- a/Content.Shared/Interaction/Components/BlockMovementComponent.cs +++ b/Content.Shared/Interaction/Components/BlockMovementComponent.cs @@ -1,4 +1,4 @@ -using Robust.Shared.GameStates; +using Robust.Shared.GameStates; namespace Content.Shared.Interaction.Components; @@ -8,5 +8,6 @@ namespace Content.Shared.Interaction.Components; [RegisterComponent, NetworkedComponent] public sealed partial class BlockMovementComponent : Component { - + [DataField] + public bool BlockInteraction = true; } diff --git a/Content.Shared/Interaction/Events/InteractionAttemptEvent.cs b/Content.Shared/Interaction/Events/InteractionAttemptEvent.cs index 0024811c369..a04c0536354 100644 --- a/Content.Shared/Interaction/Events/InteractionAttemptEvent.cs +++ b/Content.Shared/Interaction/Events/InteractionAttemptEvent.cs @@ -3,39 +3,33 @@ /// /// Event raised directed at a user to see if they can perform a generic interaction. /// - public sealed class InteractionAttemptEvent : CancellableEntityEventArgs + [ByRefEvent] + public struct InteractionAttemptEvent(EntityUid uid, EntityUid? target) { - public InteractionAttemptEvent(EntityUid uid, EntityUid? target) - { - Uid = uid; - Target = target; - } - - public EntityUid Uid { get; } - public EntityUid? Target { get; } + public bool Cancelled; + public readonly EntityUid Uid = uid; + public readonly EntityUid? Target = target; } /// /// Raised to determine whether an entity is conscious to perform an action. /// - public sealed class ConsciousAttemptEvent(EntityUid Uid) : CancellableEntityEventArgs + [ByRefEvent] + public struct ConsciousAttemptEvent(EntityUid uid) { - public EntityUid Uid { get; } = Uid; + public bool Cancelled; + public readonly EntityUid Uid = uid; } /// /// Event raised directed at the target entity of an interaction to see if the user is allowed to perform some /// generic interaction. /// - public sealed class GettingInteractedWithAttemptEvent : CancellableEntityEventArgs + [ByRefEvent] + public struct GettingInteractedWithAttemptEvent(EntityUid uid, EntityUid? target) { - public GettingInteractedWithAttemptEvent(EntityUid uid, EntityUid? target) - { - Uid = uid; - Target = target; - } - - public EntityUid Uid { get; } - public EntityUid? Target { get; } + public bool Cancelled; + public readonly EntityUid Uid = uid; + public readonly EntityUid? Target = target; } } diff --git a/Content.Shared/Interaction/SharedInteractionSystem.Blocking.cs b/Content.Shared/Interaction/SharedInteractionSystem.Blocking.cs index 9a84789adfc..52c40477c9c 100644 --- a/Content.Shared/Interaction/SharedInteractionSystem.Blocking.cs +++ b/Content.Shared/Interaction/SharedInteractionSystem.Blocking.cs @@ -6,6 +6,7 @@ namespace Content.Shared.Interaction; +// TODO deduplicate with AdminFrozenComponent /// /// Handles , which prevents various /// kinds of movement and interactions when attached to an entity. @@ -16,7 +17,7 @@ public void InitializeBlocking() { SubscribeLocalEvent(OnMoveAttempt); SubscribeLocalEvent(CancelEvent); - SubscribeLocalEvent(CancelEvent); + SubscribeLocalEvent(CancelInteractEvent); SubscribeLocalEvent(CancelEvent); SubscribeLocalEvent(CancelEvent); SubscribeLocalEvent(CancelEvent); @@ -25,6 +26,12 @@ public void InitializeBlocking() SubscribeLocalEvent(OnBlockingShutdown); } + private void CancelInteractEvent(Entity ent, ref InteractionAttemptEvent args) + { + if (ent.Comp.BlockInteraction) + args.Cancelled = true; + } + private void OnMoveAttempt(EntityUid uid, BlockMovementComponent component, UpdateCanMoveEvent args) { if (component.LifeStage > ComponentLifeStage.Running) diff --git a/Content.Shared/Interaction/SharedInteractionSystem.cs b/Content.Shared/Interaction/SharedInteractionSystem.cs index 24bb1b2446d..a2118205af1 100644 --- a/Content.Shared/Interaction/SharedInteractionSystem.cs +++ b/Content.Shared/Interaction/SharedInteractionSystem.cs @@ -20,6 +20,7 @@ using Content.Shared.Physics; using Content.Shared.Players.RateLimiting; using Content.Shared.Popups; +using Content.Shared.Silicons.StationAi; using Content.Shared.Storage; using Content.Shared.Strip; using Content.Shared.Tag; @@ -83,7 +84,6 @@ public abstract partial class SharedInteractionSystem : EntitySystem private EntityQuery _wallMountQuery; private EntityQuery _delayQuery; private EntityQuery _uiQuery; - private EntityQuery _complexInteractionQuery; private const CollisionGroup InRangeUnobstructedMask = CollisionGroup.Impassable | CollisionGroup.InteractImpassable; @@ -106,7 +106,6 @@ public override void Initialize() _wallMountQuery = GetEntityQuery(); _delayQuery = GetEntityQuery(); _uiQuery = GetEntityQuery(); - _complexInteractionQuery = GetEntityQuery(); SubscribeLocalEvent(HandleUserInterfaceRangeCheck); SubscribeLocalEvent(OnBoundInterfaceInteractAttempt); @@ -191,7 +190,7 @@ private void OnBoundInterfaceInteractAttempt(BoundUserInterfaceMessageAttempt ev return; } - if (uiComp.RequireHands && !_handsQuery.HasComp(ev.Actor)) + if (uiComp.RequiresComplex && !_actionBlockerSystem.CanComplexInteract(ev.Actor)) ev.Cancel(); } @@ -477,10 +476,7 @@ private bool IsDeleted(EntityUid? uid) public void InteractHand(EntityUid user, EntityUid target) { - if (IsDeleted(user) || IsDeleted(target)) - return; - - var complexInteractions = SupportsComplexInteractions(user); + var complexInteractions = _actionBlockerSystem.CanComplexInteract(user); if (!complexInteractions) { InteractionActivate(user, @@ -666,6 +662,14 @@ public bool InRangeUnobstructed( 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, @@ -1188,7 +1192,7 @@ public bool AltInteract(EntityUid user, EntityUid target) // 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); @@ -1245,6 +1249,13 @@ public bool InRangeAndAccessible( /// public bool IsAccessible(Entity user, Entity 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; @@ -1396,13 +1407,10 @@ public bool TryGetUsedEntity(EntityUid user, [NotNullWhen(true)] out EntityUid? return ev.Handled; } - /// - /// 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. - /// + [Obsolete("Use ActionBlockerSystem")] public bool SupportsComplexInteractions(EntityUid user) { - return _complexInteractionQuery.HasComp(user); + return _actionBlockerSystem.CanComplexInteract(user); } } @@ -1441,17 +1449,38 @@ public record struct GetUsedEntityEvent() }; /// - /// 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. /// /// Whether the hand interaction should be cancelled. [ByRefEvent] - public record struct AttemptUseInteractEvent(EntityUid User, EntityUid Used, bool Cancelled = false); + public record struct CombatModeShouldHandInteractEvent(bool Cancelled = false); /// - /// 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. /// - /// Whether the hand interaction should be cancelled. + /// + /// [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; + } + + /// + /// Override event raised directed on a user to check InRangeUnoccluded AND InRangeUnobstructed to the target if you require custom logic. + /// + [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; + } } diff --git a/Content.Shared/Item/ItemToggle/Components/ItemToggleComponent.cs b/Content.Shared/Item/ItemToggle/Components/ItemToggleComponent.cs index 46249fdd0de..47edec135d8 100644 --- a/Content.Shared/Item/ItemToggle/Components/ItemToggleComponent.cs +++ b/Content.Shared/Item/ItemToggle/Components/ItemToggleComponent.cs @@ -19,6 +19,12 @@ public sealed partial class ItemToggleComponent : Component [DataField, AutoNetworkedField] public bool Activated = false; + /// + /// Can the entity be activated in the world. + /// + [DataField] + public bool OnActivate = true; + /// /// If this is set to false then the item can't be toggled by pressing Z. /// Use another system to do it then. @@ -52,12 +58,6 @@ public sealed partial class ItemToggleComponent : Component /// [ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField] public SoundSpecifier? SoundFailToActivate; - - /// - /// Whether or not to toggle the entity's lights on or off. - /// - [ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField] - public bool ToggleLight = true; } /// diff --git a/Content.Shared/Item/ItemToggle/ItemToggleSystem.cs b/Content.Shared/Item/ItemToggle/ItemToggleSystem.cs index c4e4150659b..044a1109a18 100644 --- a/Content.Shared/Item/ItemToggle/ItemToggleSystem.cs +++ b/Content.Shared/Item/ItemToggle/ItemToggleSystem.cs @@ -1,3 +1,4 @@ +using Content.Shared.Interaction; using Content.Shared.Interaction.Events; using Content.Shared.Item.ItemToggle.Components; using Content.Shared.Popups; @@ -5,6 +6,7 @@ using Content.Shared.Temperature; using Content.Shared.Throwing; using Content.Shared.Toggleable; +using Content.Shared.Verbs; using Content.Shared.Wieldable; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; @@ -23,7 +25,6 @@ public sealed class ItemToggleSystem : EntitySystem [Dependency] private readonly INetManager _netManager = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; - [Dependency] private readonly SharedPointLightSystem _light = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; public override void Initialize() @@ -35,6 +36,8 @@ public override void Initialize() SubscribeLocalEvent(TurnOffOnUnwielded); SubscribeLocalEvent(TurnOnOnWielded); SubscribeLocalEvent(OnUseInHand); + SubscribeLocalEvent>(OnActivateVerb); + SubscribeLocalEvent(OnActivate); SubscribeLocalEvent(OnIsHotEvent); @@ -67,6 +70,32 @@ private void OnUseInHand(Entity ent, ref UseInHandEvent arg Toggle((ent, ent.Comp), args.User, predicted: ent.Comp.Predictable); } + private void OnActivateVerb(Entity ent, ref GetVerbsEvent args) + { + if (!args.CanAccess || !args.CanInteract) + return; + + var user = args.User; + + args.Verbs.Add(new ActivationVerb() + { + Text = !ent.Comp.Activated ? Loc.GetString("item-toggle-activate") : Loc.GetString("item-toggle-deactivate"), + Act = () => + { + Toggle((ent.Owner, ent.Comp), user, predicted: ent.Comp.Predictable); + } + }); + } + + private void OnActivate(Entity ent, ref ActivateInWorldEvent args) + { + if (args.Handled || !ent.Comp.OnActivate) + return; + + args.Handled = true; + Toggle((ent.Owner, ent.Comp), args.User, predicted: ent.Comp.Predictable); + } + /// /// Used when an item is attempted to be toggled. /// Sets its state to the opposite of what it is. @@ -204,16 +233,7 @@ private void UpdateVisuals(Entity ent) if (TryComp(ent, out AppearanceComponent? appearance)) { _appearance.SetData(ent, ToggleVisuals.Toggled, ent.Comp.Activated, appearance); - - if (ent.Comp.ToggleLight) - _appearance.SetData(ent, ToggleableLightVisuals.Enabled, ent.Comp.Activated, appearance); } - - if (!ent.Comp.ToggleLight) - return; - - if (_light.TryGetLight(ent, out var light)) - _light.SetEnabled(ent, ent.Comp.Activated, light); } /// diff --git a/Content.Shared/Light/Components/ItemTogglePointLightComponent.cs b/Content.Shared/Light/Components/ItemTogglePointLightComponent.cs new file mode 100644 index 00000000000..6ac1bf236d7 --- /dev/null +++ b/Content.Shared/Light/Components/ItemTogglePointLightComponent.cs @@ -0,0 +1,12 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Light.Components; + +/// +/// Toggles point light on an entity whenever ItemToggle hits. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class ItemTogglePointLightComponent : Component +{ + +} diff --git a/Content.Shared/Light/Components/LightOnCollideColliderComponent.cs b/Content.Shared/Light/Components/LightOnCollideColliderComponent.cs new file mode 100644 index 00000000000..39be05a1480 --- /dev/null +++ b/Content.Shared/Light/Components/LightOnCollideColliderComponent.cs @@ -0,0 +1,13 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Light.Components; + +/// +/// Can activate when collided with. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class LightOnCollideColliderComponent : Component +{ + [DataField] + public string FixtureId = "lightTrigger"; +} diff --git a/Content.Shared/Light/Components/LightOnCollideComponent.cs b/Content.Shared/Light/Components/LightOnCollideComponent.cs new file mode 100644 index 00000000000..c3b4bd73965 --- /dev/null +++ b/Content.Shared/Light/Components/LightOnCollideComponent.cs @@ -0,0 +1,11 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Light.Components; + +/// +/// Enables / disables pointlight whenever entities are contacting with it +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class LightOnCollideComponent : Component +{ +} diff --git a/Content.Shared/Light/EntitySystems/ItemTogglePointLightSystem.cs b/Content.Shared/Light/EntitySystems/ItemTogglePointLightSystem.cs new file mode 100644 index 00000000000..7030c538c1d --- /dev/null +++ b/Content.Shared/Light/EntitySystems/ItemTogglePointLightSystem.cs @@ -0,0 +1,29 @@ +using Content.Shared.Item.ItemToggle.Components; +using Content.Shared.Toggleable; +using ItemTogglePointLightComponent = Content.Shared.Light.Components.ItemTogglePointLightComponent; + +namespace Content.Shared.Light.EntitySystems; + +/// +/// Handles ItemToggle for PointLight +/// +public sealed class ItemTogglePointLightSystem : EntitySystem +{ + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly SharedPointLightSystem _light = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnLightToggled); + } + + private void OnLightToggled(Entity ent, ref ItemToggledEvent args) + { + if (!_light.TryGetLight(ent.Owner, out var light)) + return; + + _appearance.SetData(ent, ToggleableLightVisuals.Enabled, args.Activated); + _light.SetEnabled(ent.Owner, args.Activated, comp: light); + } +} diff --git a/Content.Shared/Light/EntitySystems/LightCollideSystem.cs b/Content.Shared/Light/EntitySystems/LightCollideSystem.cs new file mode 100644 index 00000000000..f09ae6824ea --- /dev/null +++ b/Content.Shared/Light/EntitySystems/LightCollideSystem.cs @@ -0,0 +1,82 @@ +using Content.Shared.Light.Components; +using Robust.Shared.Physics.Events; +using Robust.Shared.Physics.Systems; + +namespace Content.Shared.Light.EntitySystems; + +public sealed class LightCollideSystem : EntitySystem +{ + [Dependency] private readonly SharedPhysicsSystem _physics = default!; + [Dependency] private readonly SlimPoweredLightSystem _lights = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnPreventCollide); + SubscribeLocalEvent(OnStart); + SubscribeLocalEvent(OnEnd); + + SubscribeLocalEvent(OnCollideShutdown); + } + + private void OnCollideShutdown(Entity 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(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 ent, ref PreventCollideEvent args) + { + if (!HasComp(args.OtherEntity)) + { + args.Cancelled = true; + } + } + + private void OnEnd(Entity ent, ref EndCollideEvent args) + { + if (args.OurFixtureId != ent.Comp.FixtureId) + return; + + if (!HasComp(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 ent, ref StartCollideEvent args) + { + if (args.OurFixtureId != ent.Comp.FixtureId) + return; + + if (!HasComp(args.OtherEntity)) + return; + + _lights.SetEnabled(args.OtherEntity, true); + } +} diff --git a/Content.Shared/Light/EntitySystems/SlimPoweredLightSystem.cs b/Content.Shared/Light/EntitySystems/SlimPoweredLightSystem.cs index 6d984ed19a3..4cf9b25dadc 100644 --- a/Content.Shared/Light/EntitySystems/SlimPoweredLightSystem.cs +++ b/Content.Shared/Light/EntitySystems/SlimPoweredLightSystem.cs @@ -1,6 +1,5 @@ using Content.Shared.Light.Components; using Content.Shared.Power; -using Content.Shared.Power.Components; using Content.Shared.Power.EntitySystems; namespace Content.Shared.Light.EntitySystems; diff --git a/Content.Shared/Mind/SharedMindSystem.cs b/Content.Shared/Mind/SharedMindSystem.cs index 2b83f051904..994d230e8bd 100644 --- a/Content.Shared/Mind/SharedMindSystem.cs +++ b/Content.Shared/Mind/SharedMindSystem.cs @@ -315,6 +315,10 @@ public virtual void TransferTo(EntityUid mindId, EntityUid? entity, bool ghostCh { } + public virtual void ControlMob(EntityUid user, EntityUid target) {} + + public virtual void ControlMob(NetUserId user, EntityUid target) {} + /// /// Tries to create and add an objective from its prototype id. /// diff --git a/Content.Shared/Mobs/Systems/MobStateSystem.Subscribers.cs b/Content.Shared/Mobs/Systems/MobStateSystem.Subscribers.cs index a467c893643..f5acced4db0 100644 --- a/Content.Shared/Mobs/Systems/MobStateSystem.Subscribers.cs +++ b/Content.Shared/Mobs/Systems/MobStateSystem.Subscribers.cs @@ -33,7 +33,7 @@ private void SubscribeEvents() SubscribeLocalEvent(OnDirectionAttempt); SubscribeLocalEvent(CheckAct); SubscribeLocalEvent(CheckAct); - SubscribeLocalEvent(CheckAct); + SubscribeLocalEvent(CheckConcious); SubscribeLocalEvent(CheckAct); SubscribeLocalEvent(OnSpeakAttempt); SubscribeLocalEvent(OnEquipAttempt); @@ -91,6 +91,17 @@ private void OnUnbuckleAttempt(Entity ent, ref UnbuckleAttemp args.Cancelled = true; } + private void CheckConcious(Entity ent, ref ConsciousAttemptEvent args) + { + switch (ent.Comp.CurrentState) + { + case MobState.Dead: + case MobState.Critical: + args.Cancelled = true; + break; + } + } + private void OnStateExitSubscribers(EntityUid target, MobStateComponent component, MobState state) { switch (state) diff --git a/Content.Shared/Nyanotrasen/Interaction/NoNormalInteractionSystem.cs b/Content.Shared/Nyanotrasen/Interaction/NoNormalInteractionSystem.cs index 0f66dff3097..9b7a6954de7 100644 --- a/Content.Shared/Nyanotrasen/Interaction/NoNormalInteractionSystem.cs +++ b/Content.Shared/Nyanotrasen/Interaction/NoNormalInteractionSystem.cs @@ -12,7 +12,7 @@ public override void Initialize() private void OnInteractionAttempt(EntityUid uid, NoNormalInteractionComponent component, InteractionAttemptEvent args) { - args.Cancel(); + args.Cancelled = true; } } } diff --git a/Content.Shared/Nyanotrasen/Item/PseudoItem/SharedPseudoItemSystem.cs b/Content.Shared/Nyanotrasen/Item/PseudoItem/SharedPseudoItemSystem.cs index 7dc85781170..448311471f9 100644 --- a/Content.Shared/Nyanotrasen/Item/PseudoItem/SharedPseudoItemSystem.cs +++ b/Content.Shared/Nyanotrasen/Item/PseudoItem/SharedPseudoItemSystem.cs @@ -143,7 +143,7 @@ private void OnInsertAttempt(EntityUid uid, PseudoItemComponent component, private void OnInteractAttempt(EntityUid uid, PseudoItemComponent component, InteractionAttemptEvent args) { if (args.Uid == args.Target && component.Active) - args.Cancel(); + args.Cancelled = true; } private void OnDoAfter(EntityUid uid, PseudoItemComponent component, DoAfterEvent args) diff --git a/Content.Shared/Power/Components/ItemSlotRequiresPowerComponent.cs b/Content.Shared/Power/Components/ItemSlotRequiresPowerComponent.cs new file mode 100644 index 00000000000..6e3b9eaca05 --- /dev/null +++ b/Content.Shared/Power/Components/ItemSlotRequiresPowerComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Power.Components; + +[RegisterComponent, NetworkedComponent] +public sealed partial class ItemSlotRequiresPowerComponent : Component +{ + +} diff --git a/Content.Shared/Power/EntitySystems/ItemSlotRequiresPowerSystem.cs b/Content.Shared/Power/EntitySystems/ItemSlotRequiresPowerSystem.cs new file mode 100644 index 00000000000..3df8b91a985 --- /dev/null +++ b/Content.Shared/Power/EntitySystems/ItemSlotRequiresPowerSystem.cs @@ -0,0 +1,23 @@ +using Content.Shared.Containers.ItemSlots; +using Content.Shared.Power.Components; + +namespace Content.Shared.Power.EntitySystems; + +public sealed class ItemSlotRequiresPowerSystem : EntitySystem +{ + [Dependency] private readonly SharedPowerReceiverSystem _receiver = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnInsertAttempt); + } + + private void OnInsertAttempt(Entity ent, ref ItemSlotInsertAttemptEvent args) + { + if (!_receiver.IsPowered(ent.Owner)) + { + args.Cancelled = true; + } + } +} diff --git a/Content.Shared/Puppet/SharedVentriloquistPuppetSystem.cs b/Content.Shared/Puppet/SharedVentriloquistPuppetSystem.cs index 430c2b1b17d..e3fa21ed377 100644 --- a/Content.Shared/Puppet/SharedVentriloquistPuppetSystem.cs +++ b/Content.Shared/Puppet/SharedVentriloquistPuppetSystem.cs @@ -7,6 +7,7 @@ namespace Content.Shared.Puppet; +// TODO deduplicate with BlockMovementComponent public abstract class SharedVentriloquistPuppetSystem : EntitySystem { [Dependency] private readonly ActionBlockerSystem _blocker = default!; @@ -15,7 +16,7 @@ public override void Initialize() { base.Initialize(); SubscribeLocalEvent(Cancel); - SubscribeLocalEvent(Cancel); + SubscribeLocalEvent(CancelInteract); SubscribeLocalEvent(Cancel); SubscribeLocalEvent(Cancel); SubscribeLocalEvent(Cancel); @@ -24,6 +25,11 @@ public override void Initialize() SubscribeLocalEvent(OnStartup); } + private void CancelInteract(Entity ent, ref InteractionAttemptEvent args) + { + args.Cancelled = true; + } + private void OnStartup(EntityUid uid, VentriloquistPuppetComponent component, ComponentStartup args) { _blocker.UpdateCanMove(uid); @@ -33,4 +39,4 @@ private void Cancel(EntityUid uid, VentriloquistPuppetComponent component, T { args.Cancel(); } -} \ No newline at end of file +} diff --git a/Content.Shared/Random/Helpers/SharedRandomExtensions.cs b/Content.Shared/Random/Helpers/SharedRandomExtensions.cs index 3941c2859bc..456ba5a2158 100644 --- a/Content.Shared/Random/Helpers/SharedRandomExtensions.cs +++ b/Content.Shared/Random/Helpers/SharedRandomExtensions.cs @@ -13,6 +13,11 @@ public static string Pick(this IRobustRandom random, DatasetPrototype prototype) return random.Pick(prototype.Values); } + public static string Pick(this IRobustRandom random, LocalizedDatasetPrototype prototype) + { + return random.Pick(prototype.Values); + } + public static string Pick(this IWeightedRandomPrototype prototype, System.Random random) { var picks = prototype.Weights; diff --git a/Content.Shared/Roles/JobPrototype.cs b/Content.Shared/Roles/JobPrototype.cs index 2313cc4bd08..5ea9da02247 100644 --- a/Content.Shared/Roles/JobPrototype.cs +++ b/Content.Shared/Roles/JobPrototype.cs @@ -1,5 +1,6 @@ using Content.Shared.Access; using Content.Shared.Customization.Systems; +using Content.Shared.Dataset; using Content.Shared.Players.PlayTimeTracking; using Content.Shared.Roles; using Content.Shared.StatusIcon; @@ -98,6 +99,13 @@ public sealed partial class JobPrototype : IPrototype [DataField("startingGear", customTypeSerializer: typeof(PrototypeIdSerializer))] public string? StartingGear { get; private set; } + /// + /// If this has a value, it will randomly set the entity name of the + /// entity upon spawn based on the dataset. + /// + [DataField] + public ProtoId? NameDataset; + /// /// Use this to spawn in as a non-humanoid (borg, test subject, etc.) /// Starting gear will be ignored. @@ -126,6 +134,12 @@ public sealed partial class JobPrototype : IPrototype [DataField] public bool Whitelisted; + + [DataField] + public bool SpawnLoadout = true; + + [DataField] + public bool ApplyTraits = true; } /// diff --git a/Content.Shared/Shadowkin/SharedEtherealSystem.cs b/Content.Shared/Shadowkin/SharedEtherealSystem.cs index 5ab5e3eeeaf..2365ade818e 100644 --- a/Content.Shared/Shadowkin/SharedEtherealSystem.cs +++ b/Content.Shared/Shadowkin/SharedEtherealSystem.cs @@ -165,7 +165,7 @@ private void OnInteractionAttempt(EntityUid uid, EtherealComponent component, In || HasComp(args.Target)) return; - args.Cancel(); + args.Cancelled = true; if (_gameTiming.InPrediction) return; diff --git a/Content.Shared/Silicons/Laws/Components/SiliconLawBoundComponent.cs b/Content.Shared/Silicons/Laws/Components/SiliconLawBoundComponent.cs index 824d057b3ea..0fb9c5920fa 100644 --- a/Content.Shared/Silicons/Laws/Components/SiliconLawBoundComponent.cs +++ b/Content.Shared/Silicons/Laws/Components/SiliconLawBoundComponent.cs @@ -1,4 +1,5 @@ using Content.Shared.Actions; +using Robust.Shared.GameStates; using Robust.Shared.Prototypes; using Robust.Shared.Serialization; @@ -7,21 +8,9 @@ namespace Content.Shared.Silicons.Laws.Components; /// /// This is used for entities which are bound to silicon laws and can view them. /// -[RegisterComponent, Access(typeof(SharedSiliconLawSystem))] +[RegisterComponent, NetworkedComponent, Access(typeof(SharedSiliconLawSystem))] public sealed partial class SiliconLawBoundComponent : Component { - /// - /// The sidebar action that toggles the laws screen. - /// - [DataField] - public EntProtoId ViewLawsAction = "ActionViewLaws"; - - /// - /// The action for toggling laws. Stored here so we can remove it later. - /// - [DataField] - public EntityUid? ViewLawsActionEntity; - /// /// The last entity that provided laws to this entity. /// diff --git a/Content.Shared/Silicons/Laws/Components/SiliconLawUpdaterComponent.cs b/Content.Shared/Silicons/Laws/Components/SiliconLawUpdaterComponent.cs new file mode 100644 index 00000000000..e28bf883d91 --- /dev/null +++ b/Content.Shared/Silicons/Laws/Components/SiliconLawUpdaterComponent.cs @@ -0,0 +1,17 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Silicons.Laws.Components; + +/// +/// Whenever an entity is inserted with silicon laws it will update the relevant entity's laws. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class SiliconLawUpdaterComponent : Component +{ + /// + /// Entities to update + /// + [DataField(required: true)] + public ComponentRegistry Components; +} diff --git a/Content.Shared/Silicons/Laws/SharedSiliconLawSystem.Updater.cs b/Content.Shared/Silicons/Laws/SharedSiliconLawSystem.Updater.cs new file mode 100644 index 00000000000..9fbef58a842 --- /dev/null +++ b/Content.Shared/Silicons/Laws/SharedSiliconLawSystem.Updater.cs @@ -0,0 +1,17 @@ +using Content.Shared.Silicons.Laws.Components; +using Robust.Shared.Containers; + +namespace Content.Shared.Silicons.Laws; + +public abstract partial class SharedSiliconLawSystem +{ + private void InitializeUpdater() + { + SubscribeLocalEvent(OnUpdaterInsert); + } + + protected virtual void OnUpdaterInsert(Entity ent, ref EntInsertedIntoContainerMessage args) + { + // TODO: Prediction + } +} diff --git a/Content.Shared/Silicons/Laws/SharedSiliconLawSystem.cs b/Content.Shared/Silicons/Laws/SharedSiliconLawSystem.cs index c0619e6e43d..a30e7c8980f 100644 --- a/Content.Shared/Silicons/Laws/SharedSiliconLawSystem.cs +++ b/Content.Shared/Silicons/Laws/SharedSiliconLawSystem.cs @@ -8,13 +8,14 @@ namespace Content.Shared.Silicons.Laws; /// /// This handles getting and displaying the laws for silicons. /// -public abstract class SharedSiliconLawSystem : EntitySystem +public abstract partial class SharedSiliconLawSystem : EntitySystem { [Dependency] private readonly SharedPopupSystem _popup = default!; /// public override void Initialize() { + InitializeUpdater(); SubscribeLocalEvent(OnGotEmagged); SubscribeLocalEvent(OnAttemptEmag); } diff --git a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Airlock.cs b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Airlock.cs new file mode 100644 index 00000000000..ff6fc1ece07 --- /dev/null +++ b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Airlock.cs @@ -0,0 +1,25 @@ +using Content.Shared.Doors.Components; +using Robust.Shared.Serialization; + +namespace Content.Shared.Silicons.StationAi; + +public abstract partial class SharedStationAiSystem +{ + // Handles airlock radial + + private void InitializeAirlock() + { + SubscribeLocalEvent(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; +} diff --git a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Held.cs b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Held.cs new file mode 100644 index 00000000000..a6c57f59400 --- /dev/null +++ b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Held.cs @@ -0,0 +1,187 @@ +using System.Diagnostics.CodeAnalysis; +using Content.Shared.Actions.Events; +using Content.Shared.Interaction.Events; +using Content.Shared.Verbs; +using Robust.Shared.Serialization; +using Robust.Shared.Utility; + +namespace Content.Shared.Silicons.StationAi; + +public abstract partial class SharedStationAiSystem +{ + /* + * Added when an entity is inserted into a StationAiCore. + */ + + private void InitializeHeld() + { + SubscribeLocalEvent(OnRadialMessage); + SubscribeLocalEvent(OnMessageAttempt); + SubscribeLocalEvent>(OnTargetVerbs); + + SubscribeLocalEvent(OnHeldInteraction); + SubscribeLocalEvent(OnHeldRelay); + SubscribeLocalEvent(OnCoreJump); + } + + private void OnCoreJump(Entity ent, ref JumpToCoreEvent args) + { + if (!TryGetCore(ent.Owner, out var core) || core.Comp?.RemoteEntity == null) + return; + + _xforms.DropNextTo(core.Comp.RemoteEntity.Value, core.Owner) ; + } + + /// + /// Tries to get the entity held in the AI core. + /// + private bool TryGetHeld(Entity 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 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 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(ev.Target))) + { + ev.Cancel(); + } + } + + private void OnHeldInteraction(Entity 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 ent, ref GetVerbsEvent args) + { + if (!args.CanComplexInteract || + !ent.Comp.Enabled || + !HasComp(args.User) || + !HasComp(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); + } + } + }); + } +} + +/// +/// Raised from client to server as a BUI message wrapping the event to perform. +/// Also handles AI action validation. +/// +[Serializable, NetSerializable] +public sealed class StationAiRadialMessage : BoundUserInterfaceMessage +{ + public BaseStationAiAction Event = default!; +} + +// Do nothing on server just here for shared move along. +/// +/// Raised on client to get the relevant data for radial actions. +/// +public sealed class StationAiRadial : BaseStationAiAction +{ + public SpriteSpecifier? Sprite; + + public string? Tooltip; + + public BaseStationAiAction Event = default!; +} + +/// +/// Abstract parent for radial actions events. +/// When a client requests a radial action this will get sent. +/// +[Serializable, NetSerializable] +public abstract class BaseStationAiAction +{ + [field:NonSerialized] + public EntityUid User { get; set; } +} + +// No idea if there's a better way to do this. +/// +/// Grab actions possible for an AI on the target entity. +/// +[ByRefEvent] +public record struct GetStationAiRadialEvent() +{ + public List Actions = new(); +} + +[Serializable, NetSerializable] +public enum AiUi : byte +{ + Key, +} diff --git a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Light.cs b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Light.cs new file mode 100644 index 00000000000..bc2e3665f58 --- /dev/null +++ b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Light.cs @@ -0,0 +1,28 @@ +using Content.Shared.Light.Components; +using Robust.Shared.Serialization; + +namespace Content.Shared.Silicons.StationAi; + +public abstract partial class SharedStationAiSystem +{ + // Handles light toggling. + + private void InitializeLight() + { + SubscribeLocalEvent(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; +} diff --git a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs new file mode 100644 index 00000000000..348b0b04657 --- /dev/null +++ b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs @@ -0,0 +1,412 @@ +using Content.Shared.ActionBlocker; +using Content.Shared.Actions; +using Content.Shared.Administration.Managers; +using Content.Shared.Containers.ItemSlots; +using Content.Shared.Database; +using Content.Shared.Doors.Systems; +using Content.Shared.Interaction; +using Content.Shared.Item.ItemToggle; +using Content.Shared.Mind; +using Content.Shared.Movement.Components; +using Content.Shared.Movement.Systems; +using Content.Shared.Power; +using Content.Shared.StationAi; +using Content.Shared.Verbs; +using Robust.Shared.Containers; +using Robust.Shared.Map.Components; +using Robust.Shared.Network; +using Robust.Shared.Physics; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; +using Robust.Shared.Timing; + +namespace Content.Shared.Silicons.StationAi; + +public abstract partial class SharedStationAiSystem : EntitySystem +{ + [Dependency] private readonly ISharedAdminManager _admin = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly INetManager _net = default!; + [Dependency] private readonly ItemSlotsSystem _slots = default!; + [Dependency] private readonly ItemToggleSystem _toggles = default!; + [Dependency] private readonly ActionBlockerSystem _blocker = default!; + [Dependency] private readonly MetaDataSystem _metadata = default!; + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly SharedContainerSystem _containers = default!; + [Dependency] private readonly SharedDoorSystem _doors = default!; + [Dependency] private readonly SharedEyeSystem _eye = default!; + [Dependency] protected readonly SharedMapSystem Maps = default!; + [Dependency] private readonly SharedMindSystem _mind = default!; + [Dependency] private readonly SharedMoverController _mover = default!; + [Dependency] private readonly SharedTransformSystem _xforms = default!; + [Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!; + [Dependency] private readonly StationAiVisionSystem _vision = default!; + + // StationAiHeld is added to anything inside of an AI core. + // StationAiHolder indicates it can hold an AI positronic brain (e.g. holocard / core). + // StationAiCore holds functionality related to the core itself. + // StationAiWhitelist is a general whitelist to stop it being able to interact with anything + // StationAiOverlay handles the static overlay. It also handles interaction blocking on client and server + // for anything under it. + + private EntityQuery _broadphaseQuery; + private EntityQuery _gridQuery; + + [ValidatePrototypeId] + private static readonly EntProtoId DefaultAi = "StationAiBrain"; + + public override void Initialize() + { + base.Initialize(); + + _broadphaseQuery = GetEntityQuery(); + _gridQuery = GetEntityQuery(); + + InitializeAirlock(); + InitializeHeld(); + InitializeLight(); + + SubscribeLocalEvent(OnAiBuiCheck); + + SubscribeLocalEvent(OnAiAccessible); + SubscribeLocalEvent(OnAiInRange); + SubscribeLocalEvent(OnAiMenu); + + SubscribeLocalEvent(OnHolderInit); + SubscribeLocalEvent(OnHolderRemove); + SubscribeLocalEvent(OnHolderInteract); + SubscribeLocalEvent(OnHolderMapInit); + SubscribeLocalEvent(OnHolderConInsert); + SubscribeLocalEvent(OnHolderConRemove); + + SubscribeLocalEvent(OnAiInsert); + SubscribeLocalEvent(OnAiRemove); + SubscribeLocalEvent(OnAiMapInit); + SubscribeLocalEvent(OnAiShutdown); + SubscribeLocalEvent(OnCorePower); + SubscribeLocalEvent>(OnCoreVerbs); + } + + private void OnCoreVerbs(Entity ent, ref GetVerbsEvent 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 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 ent, ref MenuVisibilityEvent args) + { + args.Visibility &= ~MenuVisibility.NoFov; + } + + private void OnAiBuiCheck(Entity 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 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 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 ent, ref ComponentInit args) + { + _slots.AddItemSlot(ent.Owner, StationAiHolderComponent.Container, ent.Comp.Slot); + } + + private void OnHolderRemove(Entity ent, ref ComponentRemove args) + { + _slots.RemoveItemSlot(ent.Owner, ent.Comp.Slot); + } + + private void OnHolderConInsert(Entity ent, ref EntInsertedIntoContainerMessage args) + { + UpdateAppearance((ent.Owner, ent.Comp)); + } + + private void OnHolderConRemove(Entity ent, ref EntRemovedFromContainerMessage args) + { + UpdateAppearance((ent.Owner, ent.Comp)); + } + + private void OnHolderMapInit(Entity ent, ref MapInitEvent args) + { + UpdateAppearance(ent.Owner); + } + + private void OnAiShutdown(Entity ent, ref ComponentShutdown args) + { + // TODO: Tryqueuedel + if (_net.IsClient) + return; + + QueueDel(ent.Comp.RemoteEntity); + ent.Comp.RemoteEntity = null; + } + + private void OnCorePower(Entity 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 ent, ref MapInitEvent args) + { + SetupEye(ent); + AttachEye(ent); + } + + private bool SetupEye(Entity 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 ent) + { + QueueDel(ent.Comp.RemoteEntity); + ent.Comp.RemoteEntity = null; + } + + private void AttachEye(Entity 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 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 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(args.Entity); + + if (TryComp(args.Entity, out EyeComponent? eyeComp)) + { + _eye.SetTarget(args.Entity, null, eyeComp); + } + } + + private void UpdateAppearance(Entity 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 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 entity, bool value, bool announce = false) + { + if (entity.Comp.Enabled == value) + return false; + + entity.Comp.Enabled = value; + Dirty(entity); + + return true; + } + + /// + /// BUI validation for ai interactions. + /// + private bool ValidateAi(Entity 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, +} diff --git a/Content.Shared/Silicons/StationAi/StationAiCoreComponent.cs b/Content.Shared/Silicons/StationAi/StationAiCoreComponent.cs new file mode 100644 index 00000000000..b7a8b4cd5fa --- /dev/null +++ b/Content.Shared/Silicons/StationAi/StationAiCoreComponent.cs @@ -0,0 +1,32 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Silicons.StationAi; + +/// +/// Indicates this entity can interact with station equipment and is a "Station AI". +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class StationAiCoreComponent : Component +{ + /* + * I couldn't think of any other reason you'd want to split these out. + */ + + /// + /// Can it move its camera around and interact remotely with things. + /// + [DataField, AutoNetworkedField] + public bool Remote = true; + + /// + /// The invisible eye entity being used to look around. + /// + [DataField, AutoNetworkedField] + public EntityUid? RemoteEntity; + + [DataField(readOnly: true)] + public EntProtoId? RemoteEntityProto = "StationAiHolo"; + + public const string Container = "station_ai_mind_slot"; +} diff --git a/Content.Shared/Silicons/StationAi/StationAiHeldComponent.cs b/Content.Shared/Silicons/StationAi/StationAiHeldComponent.cs new file mode 100644 index 00000000000..6dab1ee491a --- /dev/null +++ b/Content.Shared/Silicons/StationAi/StationAiHeldComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Silicons.StationAi; + +/// +/// Indicates this entity is currently held inside of a station AI core. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class StationAiHeldComponent : Component; diff --git a/Content.Shared/Silicons/StationAi/StationAiHolderComponent.cs b/Content.Shared/Silicons/StationAi/StationAiHolderComponent.cs new file mode 100644 index 00000000000..221845d493d --- /dev/null +++ b/Content.Shared/Silicons/StationAi/StationAiHolderComponent.cs @@ -0,0 +1,16 @@ +using Content.Shared.Containers.ItemSlots; +using Robust.Shared.GameStates; + +namespace Content.Shared.Silicons.StationAi; + +/// +/// Allows moving a contained entity to and from this component. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class StationAiHolderComponent : Component +{ + public const string Container = StationAiCoreComponent.Container; + + [DataField] + public ItemSlot Slot = new(); +} diff --git a/Content.Shared/Silicons/StationAi/StationAiVisionComponent.cs b/Content.Shared/Silicons/StationAi/StationAiVisionComponent.cs index 94aef8ad366..f047fe41e4d 100644 --- a/Content.Shared/Silicons/StationAi/StationAiVisionComponent.cs +++ b/Content.Shared/Silicons/StationAi/StationAiVisionComponent.cs @@ -1,8 +1,9 @@ +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] diff --git a/Content.Shared/Silicons/StationAi/StationAiVisionSystem.cs b/Content.Shared/Silicons/StationAi/StationAiVisionSystem.cs index c144f330e11..bdc62a6bb37 100644 --- a/Content.Shared/Silicons/StationAi/StationAiVisionSystem.cs +++ b/Content.Shared/Silicons/StationAi/StationAiVisionSystem.cs @@ -1,4 +1,6 @@ +using Content.Shared.StationAi; using Robust.Shared.Map.Components; +using Robust.Shared.Physics; using Robust.Shared.Threading; using Robust.Shared.Utility; @@ -24,6 +26,8 @@ public sealed class StationAiVisionSystem : EntitySystem private readonly HashSet> _seeds = new(); private readonly HashSet _viewportTiles = new(); + private EntityQuery _occluderQuery; + // Dummy set private readonly HashSet _singleTiles = new(); @@ -36,15 +40,12 @@ public sealed class StationAiVisionSystem : EntitySystem /// private bool FastPath; - /// - /// Have we found the target tile if we're only checking for a single one. - /// - private bool TargetFound; - public override void Initialize() { base.Initialize(); + _occluderQuery = GetEntityQuery(); + _seedJob = new() { System = this, @@ -61,16 +62,16 @@ public override void Initialize() /// /// Returns whether a tile is accessible based on vision. /// - public bool IsAccessible(Entity grid, Vector2i tile, float expansionSize = 8.5f, bool fastPath = false) + public bool IsAccessible(Entity 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(); @@ -110,21 +111,19 @@ public bool IsAccessible(Entity grid, Vector2i tile, float exp _job.BoundaryTiles.Add(new HashSet()); } - _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 grid, Vector2i tile) + private bool IsOccluded(Entity 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) @@ -143,17 +142,18 @@ private bool IsOccluded(Entity grid, Vector2i tile) /// Gets a byond-equivalent for tiles in the specified worldAABB. /// /// How much to expand the bounds before to find vision intersecting it. Makes this the largest vision size + 1 tile. - public void GetView(Entity grid, Box2Rotated worldBounds, HashSet visibleTiles, float expansionSize = 8.5f) + public void GetView(Entity grid, Box2Rotated worldBounds, HashSet 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; @@ -170,7 +170,7 @@ public void GetView(Entity grid, Box2Rotated worldBounds, Hash 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)) { @@ -182,9 +182,8 @@ public void GetView(Entity grid, Box2Rotated worldBounds, Hash _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)) @@ -206,9 +205,7 @@ public void GetView(Entity grid, Box2Rotated worldBounds, Hash _job.BoundaryTiles.Add(new HashSet()); } - _job.TargetTile = null; - TargetFound = false; - _job.Grid = grid; + _job.Grid = (grid.Owner, grid.Comp2); _job.VisibleTiles = visibleTiles; _parallel.ProcessNow(_job, _job.Data.Count); } @@ -250,6 +247,7 @@ private bool CheckNeighborsVis( return false; } + /// /// Checks whether this tile fits the definition of a "corner" /// private bool IsCorner( @@ -287,7 +285,7 @@ private record struct SeedJob() : IRobustJob public void Execute() { - System._lookup.GetLocalEntitiesIntersecting(Grid.Owner, ExpandedBounds, System._seeds); + System._lookup.GetLocalEntitiesIntersecting(Grid.Owner, ExpandedBounds, System._seeds, flags: LookupFlags.All | LookupFlags.Approximate); } } @@ -302,9 +300,6 @@ private record struct ViewJob() : IParallelRobustJob public Entity Grid; public List> Data = new(); - // If we're doing range-checks might be able to early out - public Vector2i? TargetTile; - public HashSet VisibleTiles; public readonly List> Vis1 = new(); @@ -315,18 +310,6 @@ private record struct ViewJob() : IParallelRobustJob 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(seed); @@ -338,30 +321,11 @@ public void Execute(int index) 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); } } @@ -480,40 +444,21 @@ public void Execute(int index) 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); } } } diff --git a/Content.Shared/Silicons/StationAi/StationAiWhitelistComponent.cs b/Content.Shared/Silicons/StationAi/StationAiWhitelistComponent.cs new file mode 100644 index 00000000000..51d8793be06 --- /dev/null +++ b/Content.Shared/Silicons/StationAi/StationAiWhitelistComponent.cs @@ -0,0 +1,13 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Silicons.StationAi; + +/// +/// Indicates an entity that has can interact with this. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedStationAiSystem))] +public sealed partial class StationAiWhitelistComponent : Component +{ + [DataField, AutoNetworkedField] + public bool Enabled = true; +} diff --git a/Content.Shared/Station/SharedStationSpawningSystem.cs b/Content.Shared/Station/SharedStationSpawningSystem.cs index 4a2ad625af9..c433cc1d4fe 100644 --- a/Content.Shared/Station/SharedStationSpawningSystem.cs +++ b/Content.Shared/Station/SharedStationSpawningSystem.cs @@ -1,4 +1,5 @@ using System.Linq; +using Content.Shared.Dataset; using Content.Shared.Hands.Components; using Content.Shared.Hands.EntitySystems; using Content.Shared.Inventory; @@ -8,16 +9,20 @@ 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 SharedStorageSystem _storage = default!; - [Dependency] private readonly SharedTransformSystem _xformSystem = default!; + [Dependency] private readonly SharedHandsSystem _handsSystem = default!; + [Dependency] private readonly SharedStorageSystem _storage = default!; + [Dependency] private readonly SharedTransformSystem _xformSystem = default!; + [Dependency] private readonly MetaDataSystem _metadata = default!; private EntityQuery _handsQuery; private EntityQuery _inventoryQuery; @@ -33,6 +38,26 @@ public override void Initialize() _xformQuery = GetEntityQuery(); } + /// + /// Applies the role's name as applicable to the entity. + /// + public void EquipJobName(EntityUid entity, JobPrototype job) + { + string? name = null; + + if (string.IsNullOrEmpty(name) + && job.NameDataset.HasValue + && PrototypeManager.TryIndex(job.NameDataset.Value, out var nameData)) + { + name = Loc.GetString(_random.Pick(nameData.Values)); + } + + if (!string.IsNullOrEmpty(name)) + { + _metadata.SetEntityName(entity, name); + } + } + /// /// /// diff --git a/Content.Shared/Stunnable/SharedStunSystem.cs b/Content.Shared/Stunnable/SharedStunSystem.cs index 9d2e8d3dde2..be5630cea07 100644 --- a/Content.Shared/Stunnable/SharedStunSystem.cs +++ b/Content.Shared/Stunnable/SharedStunSystem.cs @@ -68,7 +68,7 @@ public override void Initialize() // Attempt event subscriptions. SubscribeLocalEvent(OnAttempt); SubscribeLocalEvent(OnMoveAttempt); - SubscribeLocalEvent(OnAttempt); + SubscribeLocalEvent(OnAttemptInteract); SubscribeLocalEvent(OnAttempt); SubscribeLocalEvent(OnAttempt); SubscribeLocalEvent(OnAttempt); @@ -79,7 +79,10 @@ public override void Initialize() SubscribeLocalEvent(OnMobStateChanged); } - + private void OnAttemptInteract(Entity ent, ref InteractionAttemptEvent args) + { + args.Cancelled = true; + } private void OnMobStateChanged(EntityUid uid, MobStateComponent component, MobStateChangedEvent args) { diff --git a/Content.Shared/SubFloor/SharedSubFloorHideSystem.cs b/Content.Shared/SubFloor/SharedSubFloorHideSystem.cs index ba78ff651f5..cebc84ecb93 100644 --- a/Content.Shared/SubFloor/SharedSubFloorHideSystem.cs +++ b/Content.Shared/SubFloor/SharedSubFloorHideSystem.cs @@ -45,11 +45,11 @@ private void OnAttackAttempt(EntityUid uid, SubFloorHideComponent component, ref args.Cancelled = true; } - private void OnInteractionAttempt(EntityUid uid, SubFloorHideComponent component, GettingInteractedWithAttemptEvent args) + private void OnInteractionAttempt(EntityUid uid, SubFloorHideComponent component, ref GettingInteractedWithAttemptEvent args) { // No interactions with entities hidden under floor tiles. if (component.BlockInteractions && component.IsUnderCover) - args.Cancel(); + args.Cancelled = true; } private void OnSubFloorStarted(EntityUid uid, SubFloorHideComponent component, ComponentStartup _) diff --git a/Content.Shared/UserInterface/ActivatableUIComponent.cs b/Content.Shared/UserInterface/ActivatableUIComponent.cs index 30c07637420..b148cb8bce0 100644 --- a/Content.Shared/UserInterface/ActivatableUIComponent.cs +++ b/Content.Shared/UserInterface/ActivatableUIComponent.cs @@ -12,7 +12,7 @@ public sealed partial class ActivatableUIComponent : Component /// /// Whether the item must be held in one of the user's hands to work. - /// This is ignored unless is true. + /// This is ignored unless is true. /// [ViewVariables(VVAccess.ReadWrite)] [DataField] @@ -29,15 +29,15 @@ public sealed partial class ActivatableUIComponent : Component public LocId VerbText = "ui-verb-toggle-open"; /// - /// 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. /// /// /// 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. /// [ViewVariables(VVAccess.ReadWrite)] [DataField] - public bool RequireHands = true; + public bool RequiresComplex = true; /// /// Entities that are required to open this UI. diff --git a/Content.Shared/UserInterface/ActivatableUISystem.cs b/Content.Shared/UserInterface/ActivatableUISystem.cs index f32d6c98a89..c3e4c619237 100644 --- a/Content.Shared/UserInterface/ActivatableUISystem.cs +++ b/Content.Shared/UserInterface/ActivatableUISystem.cs @@ -105,7 +105,7 @@ private bool ShouldAddVerb(EntityUid uid, ActivatableUIComponent component, G || _whitelistSystem.IsWhitelistFail(component.UserWhitelist, args.User)) return false; - if (component.RequireHands) + if (component.RequiresComplex) { if (args.Hands == null) return false; @@ -193,19 +193,22 @@ private bool InteractUI(EntityUid user, EntityUid uiEntity, ActivatableUICompone if (!_blockerSystem.CanInteract(user, uiEntity) && (!HasComp(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)) @@ -276,13 +279,13 @@ public void CloseAll(EntityUid uid, ActivatableUIComponent? aui = null) private void OnHandDeselected(Entity 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 ent, ref GotUnequippedHandEvent args) { - if (ent.Comp.RequireHands && ent.Comp.InHandsOnly) + if (ent.Comp.InHandsOnly) CloseAll(ent, ent); } } diff --git a/Content.Shared/Verbs/SharedVerbSystem.cs b/Content.Shared/Verbs/SharedVerbSystem.cs index 319f927c7b3..37840dcbb54 100644 --- a/Content.Shared/Verbs/SharedVerbSystem.cs +++ b/Content.Shared/Verbs/SharedVerbSystem.cs @@ -3,6 +3,7 @@ using Content.Shared.Interaction; using Content.Shared.Inventory.VirtualItem; using Robust.Shared.Containers; +using Robust.Shared.Map; namespace Content.Shared.Verbs { @@ -77,6 +78,7 @@ public SortedSet GetLocalVerbs(EntityUid target, EntityUid user, List(user, out var hands); @@ -84,7 +86,7 @@ public SortedSet GetLocalVerbs(EntityUid target, EntityUid user, List(user, target, @using, hands, canInteract, canAccess, extraCategories); + var verbEvent = new GetVerbsEvent(user, target, @using, hands, canInteract: canInteract, canComplexInteract: canComplexInteract, canAccess: canAccess, extraCategories); RaiseLocalEvent(target, verbEvent, true); verbs.UnionWith(verbEvent.Verbs); } @@ -93,35 +95,35 @@ public SortedSet GetLocalVerbs(EntityUid target, EntityUid user, List(user, target, @using, hands, canInteract, canAccess, extraCategories); + var verbEvent = new GetVerbsEvent(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(user, target, @using, hands, canInteract, canAccess, extraCategories); + var verbEvent = new GetVerbsEvent(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(user, target, @using, hands, canInteract, canAccess, extraCategories); + var verbEvent = new GetVerbsEvent(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(user, target, @using, hands, canInteract, canAccess, extraCategories); + var verbEvent = new GetVerbsEvent(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(user, target, @using, hands, canInteract, canAccess, extraCategories); + var verbEvent = new GetVerbsEvent(user, target, @using, hands, canInteract: canInteract, canComplexInteract: canComplexInteract, canAccess: canAccess, extraCategories); RaiseLocalEvent(target, verbEvent, true); verbs.UnionWith(verbEvent.Verbs); } @@ -129,7 +131,7 @@ public SortedSet GetLocalVerbs(EntityUid target, EntityUid user, List(user, target, @using, hands, canInteract, canAccess, extraCategories); + var verbEvent = new GetVerbsEvent(user, target, @using, hands, canInteract: canInteract, canComplexInteract: canComplexInteract, canAccess: canAccess, extraCategories); RaiseLocalEvent(target, verbEvent, true); verbs.UnionWith(verbEvent.Verbs); } @@ -137,7 +139,7 @@ public SortedSet GetLocalVerbs(EntityUid target, EntityUid user, List(user, target, @using, hands, canInteract, access, extraCategories); + var verbEvent = new GetVerbsEvent(user, target, @using, hands, canInteract: canInteract, canComplexInteract: canComplexInteract, canAccess: canAccess, extraCategories); RaiseLocalEvent(target, verbEvent); verbs.UnionWith(verbEvent.Verbs); } @@ -173,4 +175,27 @@ public virtual void ExecuteVerb(Verb verb, EntityUid user, EntityUid target, boo _interactionSystem.DoContactInteraction(user, target); } } + + // Does nothing on server + /// + /// Raised directed when trying to get the entity menu visibility for entities. + /// + [ByRefEvent] + public record struct MenuVisibilityEvent + { + public MapCoordinates TargetPos; + public MenuVisibility Visibility; + } + + // Does nothing on server + [Flags] + public enum MenuVisibility + { + // What entities can a user see on the entity menu? + Default = 0, // They can only see entities in FoV. + NoFov = 1 << 0, // They ignore FoV restrictions + InContainer = 1 << 1, // They can see through containers. + Invisible = 1 << 2, // They can see entities without sprites and the "HideContextMenu" tag is ignored. + All = NoFov | InContainer | Invisible + } } diff --git a/Content.Shared/Verbs/VerbEvents.cs b/Content.Shared/Verbs/VerbEvents.cs index 6b3fd327c99..6bca97925bf 100644 --- a/Content.Shared/Verbs/VerbEvents.cs +++ b/Content.Shared/Verbs/VerbEvents.cs @@ -113,6 +113,11 @@ public sealed class GetVerbsEvent : EntityEventArgs where TVerb : Verb /// public readonly bool CanInteract; + /// + /// Cached version of CanComplexInteract + /// + public readonly bool CanComplexInteract; + /// /// The User's hand component. /// @@ -130,13 +135,14 @@ public sealed class GetVerbsEvent : EntityEventArgs where TVerb : Verb /// public readonly EntityUid? Using; - public GetVerbsEvent(EntityUid user, EntityUid target, EntityUid? @using, HandsComponent? hands, bool canInteract, bool canAccess, List extraCategories) + public GetVerbsEvent(EntityUid user, EntityUid target, EntityUid? @using, HandsComponent? hands, bool canInteract, bool canComplexInteract, bool canAccess, List extraCategories) { User = user; Target = target; Using = @using; Hands = hands; CanAccess = canAccess; + CanComplexInteract = canComplexInteract; CanInteract = canInteract; ExtraCategories = extraCategories; } diff --git a/Content.Shared/Wires/SharedWiresSystem.cs b/Content.Shared/Wires/SharedWiresSystem.cs index 24f3ad8e764..7ef2230e7a0 100644 --- a/Content.Shared/Wires/SharedWiresSystem.cs +++ b/Content.Shared/Wires/SharedWiresSystem.cs @@ -125,11 +125,20 @@ public bool CanTogglePanel(Entity ent, EntityUid? user) return !attempt.Cancelled; } - public bool IsPanelOpen(Entity entity) + public bool IsPanelOpen(Entity 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(entity, out var wiresPanelSecurity) && @@ -139,3 +148,12 @@ public bool IsPanelOpen(Entity entity) return entity.Comp.Open; } } + +/// +/// Raised directed on a tool to try and override panel visibility. +/// +[ByRefEvent] +public record struct PanelOverrideEvent() +{ + public bool Allowed = true; +} diff --git a/Content.Tests/Shared/LocalizedDatasetPrototypeTest.cs b/Content.Tests/Shared/LocalizedDatasetPrototypeTest.cs new file mode 100644 index 00000000000..0ec4c076f5b --- /dev/null +++ b/Content.Tests/Shared/LocalizedDatasetPrototypeTest.cs @@ -0,0 +1,59 @@ +using System; +using Content.Shared.Dataset; +using NUnit.Framework; +using Robust.Shared.Collections; +using Robust.Shared.IoC; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.Manager; + +namespace Content.Tests.Shared; + +[TestFixture] +[TestOf(typeof(LocalizedDatasetPrototype))] +public sealed class LocalizedDatasetPrototypeTest : ContentUnitTest +{ + private IPrototypeManager _prototypeManager; + + [OneTimeSetUp] + public void OneTimeSetup() + { + IoCManager.Resolve().Initialize(); + _prototypeManager = IoCManager.Resolve(); + _prototypeManager.Initialize(); + _prototypeManager.LoadString(TestPrototypes); + _prototypeManager.ResolveResults(); + } + + private const string TestPrototypes = @" +- type: localizedDataset + id: Test + values: + prefix: test-dataset- + count: 4 +"; + + [Test] + public void LocalizedDatasetTest() + { + var testPrototype = _prototypeManager.Index("Test"); + var values = new ValueList(); + foreach (var value in testPrototype.Values) + { + values.Add(value); + } + + // Make sure we get the right number of values + Assert.That(values, Has.Count.EqualTo(4)); + + // Make sure indexing works as expected + Assert.That(values[0], Is.EqualTo("test-dataset-1")); + Assert.That(values[1], Is.EqualTo("test-dataset-2")); + Assert.That(values[2], Is.EqualTo("test-dataset-3")); + Assert.That(values[3], Is.EqualTo("test-dataset-4")); + Assert.Throws(() => { var x = values[4]; }); + Assert.Throws(() => { var x = values[-1]; }); + + // Make sure that the enumerator gets all of the values + Assert.That(testPrototype.Values[testPrototype.Values.Count], Is.EqualTo("test-dataset-4")); + } +} diff --git a/Resources/Audio/Effects/Footsteps/attributions.yml b/Resources/Audio/Effects/Footsteps/attributions.yml index 91c3ce260d4..7a56beec38c 100644 --- a/Resources/Audio/Effects/Footsteps/attributions.yml +++ b/Resources/Audio/Effects/Footsteps/attributions.yml @@ -76,5 +76,5 @@ - 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/" diff --git a/Resources/Audio/Effects/Footsteps/borgwalk2.ogg b/Resources/Audio/Effects/Footsteps/borgwalk2.ogg index 96c2c1617f4..57685ff173d 100644 Binary files a/Resources/Audio/Effects/Footsteps/borgwalk2.ogg and b/Resources/Audio/Effects/Footsteps/borgwalk2.ogg differ diff --git a/Resources/Locale/en-US/administration/commands/set-station-ai-name-command.ftl b/Resources/Locale/en-US/administration/commands/set-station-ai-name-command.ftl new file mode 100644 index 00000000000..ca0bf11e8cf --- /dev/null +++ b/Resources/Locale/en-US/administration/commands/set-station-ai-name-command.ftl @@ -0,0 +1,3 @@ +set-station-ai-name-command-description = Sets a specific uid to be given a Station AI name at random. +set-station-ai-name-command-help-text = Usage: {$command} +set-station-ai-name-command-no-station-ai = No station AI job prototype found. \ No newline at end of file diff --git a/Resources/Locale/en-US/administration/ui/silicon-law-ui.ftl b/Resources/Locale/en-US/administration/ui/silicon-law-ui.ftl index 0a68d08063a..24ab7309743 100644 --- a/Resources/Locale/en-US/administration/ui/silicon-law-ui.ftl +++ b/Resources/Locale/en-US/administration/ui/silicon-law-ui.ftl @@ -8,3 +8,5 @@ silicon-law-ui-delete = Delete 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 diff --git a/Resources/Locale/en-US/datasets/names/ai.ftl b/Resources/Locale/en-US/datasets/names/ai.ftl new file mode 100644 index 00000000000..ac6ce0d83e6 --- /dev/null +++ b/Resources/Locale/en-US/datasets/names/ai.ftl @@ -0,0 +1,139 @@ +names-ai-dataset-1 = 16-20 +names-ai-dataset-2 = 512k + +# Ought to be enough for anybody +names-ai-dataset-3 = 640k + +names-ai-dataset-4 = "790" +names-ai-dataset-5 = Adaptive Manipulator + +# Named after the famous soundcard +names-ai-dataset-6 = Adlib + +names-ai-dataset-7 = ALICE +names-ai-dataset-8 = Allied Mastercomputer +names-ai-dataset-9 = Alpha 2 +names-ai-dataset-10 = Alpha 3 +names-ai-dataset-11 = Alpha 4 +names-ai-dataset-12 = Alpha 5 +names-ai-dataset-13 = Alpha 6 +names-ai-dataset-14 = Alpha 7 +names-ai-dataset-15 = Alpha 8 +names-ai-dataset-16 = Alpha 9 +names-ai-dataset-17 = AmigoBot +names-ai-dataset-18 = Android +names-ai-dataset-19 = Aniel +names-ai-dataset-20 = AOL +names-ai-dataset-21 = Asimov + +# The most influential modem ever, created by the bell system. It still lives on today in certain applications +names-ai-dataset-22 = Bell 301 + +names-ai-dataset-23 = Bishop +names-ai-dataset-24 = Blitz +names-ai-dataset-25 = Box +names-ai-dataset-26 = Calculator +names-ai-dataset-27 = Cassandra +names-ai-dataset-28 = Cell +names-ai-dataset-29 = Chii +names-ai-dataset-30 = Chip +names-ai-dataset-31 = C.R.A.I.G. + +# Commercial supercomputer from the 70s +names-ai-dataset-32 = Cray-2 + +# If we're going to have AOL we may as well have some of their major competitors +names-ai-dataset-33 = CompuServe + +names-ai-dataset-34 = Computer +names-ai-dataset-35 = Cutie +names-ai-dataset-36 = Daedalus +names-ai-dataset-37 = DecTalk +names-ai-dataset-38 = Dee Model +names-ai-dataset-39 = Dial Up +names-ai-dataset-40 = Dorfl +names-ai-dataset-41 = Duey +names-ai-dataset-42 = Emma-2 + +# Famous early computer +names-ai-dataset-43 = ENIAC + +names-ai-dataset-44 = Erasmus +names-ai-dataset-45 = Everything +names-ai-dataset-46 = Ez-27 +names-ai-dataset-47 = FRIEND COMPUTER +names-ai-dataset-48 = Faith +names-ai-dataset-49 = Fi +names-ai-dataset-50 = Frost +names-ai-dataset-51 = George +names-ai-dataset-52 = H.E.L.P +names-ai-dataset-53 = Hadaly +names-ai-dataset-54 = Helios +names-ai-dataset-55 = Hivebot Overmind +names-ai-dataset-56 = Huey + +# A play on the fad apple spawned of putting "i" infront of your tech products name +names-ai-dataset-57 = iAI + +# Hell on earth (web browser) +names-ai-dataset-58 = I.E. 6 + +names-ai-dataset-59 = Icarus + +# If you don't get this one you are too young +names-ai-dataset-60 = Jeeves + +names-ai-dataset-61 = Jinx +names-ai-dataset-62 = K.I.N.G +names-ai-dataset-63 = Klapaucius +names-ai-dataset-64 = Knight +names-ai-dataset-65 = Louie + +# Named after the Manchester Mark 1, the successor of which was actually named the Ferranti Mark 1, rather than Manchester Mark 2 +names-ai-dataset-66 = Manchester Mark 2 + +names-ai-dataset-67 = MARK13 +names-ai-dataset-68 = Maria +names-ai-dataset-69 = Marvin +names-ai-dataset-70 = Max 404 +names-ai-dataset-71 = Metalhead +names-ai-dataset-72 = M.I.M.I +names-ai-dataset-73 = MK ULTRA +names-ai-dataset-74 = MoMMI +names-ai-dataset-75 = Mugsy3000 +names-ai-dataset-76 = Multivac +names-ai-dataset-77 = NCH + +# A play on both NT as in NanoTrasen and NT as in windows NT, of which version 6.0 is windows vista +names-ai-dataset-78 = NT v6.0 + +names-ai-dataset-79 = Packard Bell +names-ai-dataset-80 = PTO +names-ai-dataset-81 = Project Y2K +names-ai-dataset-82 = Revelation +names-ai-dataset-83 = Robot Devil +names-ai-dataset-84 = S.A.M. +names-ai-dataset-85 = S.H.O.C.K. +names-ai-dataset-86 = S.H.R.O.U.D. +names-ai-dataset-87 = S.O.P.H.I.E. +names-ai-dataset-88 = Samaritan +names-ai-dataset-89 = Shrike +names-ai-dataset-90 = Solo +names-ai-dataset-91 = Station Control Program +names-ai-dataset-92 = AINU (AI's Not Unix) +names-ai-dataset-93 = Super 35 +names-ai-dataset-94 = Surgeon General +names-ai-dataset-95 = TWA +names-ai-dataset-96 = Terminus +names-ai-dataset-97 = TPM 3.0 +names-ai-dataset-98 = Turing Complete +names-ai-dataset-99 = Tidy +names-ai-dataset-100 = Ulysses +names-ai-dataset-101 = W1k1 +names-ai-dataset-102 = X-5 +names-ai-dataset-103 = X.A.N.A. +names-ai-dataset-104 = XERXES +names-ai-dataset-105 = Z-1 +names-ai-dataset-106 = Z-2 +names-ai-dataset-107 = Z-3 +names-ai-dataset-108 = Zed diff --git a/Resources/Locale/en-US/items/toggle.ftl b/Resources/Locale/en-US/items/toggle.ftl new file mode 100644 index 00000000000..bcf5c161a6f --- /dev/null +++ b/Resources/Locale/en-US/items/toggle.ftl @@ -0,0 +1,2 @@ +item-toggle-activate = Activate +item-toggle-deactivate = Deactivate diff --git a/Resources/Locale/en-US/job/department-desc.ftl b/Resources/Locale/en-US/job/department-desc.ftl index 05c52dada9e..0243d61942f 100644 --- a/Resources/Locale/en-US/job/department-desc.ftl +++ b/Resources/Locale/en-US/job/department-desc.ftl @@ -5,4 +5,5 @@ department-Engineering-description = Keep the power on and the station operation 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. diff --git a/Resources/Locale/en-US/job/department.ftl b/Resources/Locale/en-US/job/department.ftl index 508a0459cf6..2295a9ba9d3 100644 --- a/Resources/Locale/en-US/job/department.ftl +++ b/Resources/Locale/en-US/job/department.ftl @@ -5,4 +5,5 @@ department-Engineering = Engineering department-Medical = Medical department-Security = Security department-Science = Science +department-Silicon = Silicons department-Specific = Station specific diff --git a/Resources/Locale/en-US/job/job-description.ftl b/Resources/Locale/en-US/job/job-description.ftl index 801e402eae1..ee3d2a1fe61 100644 --- a/Resources/Locale/en-US/job/job-description.ftl +++ b/Resources/Locale/en-US/job/job-description.ftl @@ -44,6 +44,7 @@ job-description-salvagespec = Use the salvage magnet to draw in detatched scraps job-description-scientist = Research alien artifacts, unlock new technologies, build newer and better machines around the station, and make everything run more efficiently. job-description-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 unavailable. diff --git a/Resources/Locale/en-US/job/job-names.ftl b/Resources/Locale/en-US/job/job-names.ftl index 39d7ab86add..19c63f58034 100644 --- a/Resources/Locale/en-US/job/job-names.ftl +++ b/Resources/Locale/en-US/job/job-names.ftl @@ -38,6 +38,7 @@ job-name-botanist = Botanist job-name-bartender = Bartender job-name-passenger = Passenger job-name-salvagespec = Salvage specialist +job-name-station-ai = Station AI job-name-qm = Logistics Officer job-name-cargotech = Cargo Technician job-name-chef = Chef @@ -117,6 +118,7 @@ JobSeniorOfficer = Senior Officer JobSeniorPhysician = Senior Physician JobSeniorResearcher = Mystic JobServiceWorker = Service Worker +JobStationAi = Station AI JobStationEngineer = Station Engineer JobTechnicalAssistant = Technical Assistant JobVisitor = Visitor diff --git a/Resources/Locale/en-US/silicons/station-ai.ftl b/Resources/Locale/en-US/silicons/station-ai.ftl new file mode 100644 index 00000000000..d51a99ebb04 --- /dev/null +++ b/Resources/Locale/en-US/silicons/station-ai.ftl @@ -0,0 +1,14 @@ +# General +ai-wire-snipped = Wire has been cut at {$coords}. +wire-name-ai-vision-light = AIV +wire-name-ai-act-light = AIA +station-ai-takeover = AI takeover + +# Radial actions +ai-open = Open actions +ai-close = Close actions + +bolt-close = Close bolt +bolt-open = Open bolt + +toggle-light = Toggle light diff --git a/Resources/Locale/en-US/station-events/events/random-sentience.ftl b/Resources/Locale/en-US/station-events/events/random-sentience.ftl index 47f0e317a65..f14a020d29e 100644 --- a/Resources/Locale/en-US/station-events/events/random-sentience.ftl +++ b/Resources/Locale/en-US/station-events/events/random-sentience.ftl @@ -36,3 +36,4 @@ station-event-random-sentience-flavor-corgi = corgi station-event-random-sentience-flavor-primate = primate station-event-random-sentience-flavor-kobold = kobold station-event-random-sentience-flavor-slime = slime +station-event-random-sentience-flavor-inanimate = inanimate \ No newline at end of file diff --git a/Resources/Maps/Test/dev_map.yml b/Resources/Maps/Test/dev_map.yml index 910a059ee72..ad4910d1da4 100644 --- a/Resources/Maps/Test/dev_map.yml +++ b/Resources/Maps/Test/dev_map.yml @@ -4467,6 +4467,13 @@ entities: - 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 @@ -5824,13 +5831,6 @@ entities: - type: Transform pos: -6.8905525,1.5128828 parent: 179 -- proto: VehicleKeyJanicart - entities: - - uid: 14 - components: - - type: Transform - pos: 6.5,16.5 - parent: 179 - proto: VendingMachineCigs entities: - uid: 870 diff --git a/Resources/Maps/saltern.yml b/Resources/Maps/saltern.yml index 36984557dbc..86122e2f8c2 100644 --- a/Resources/Maps/saltern.yml +++ b/Resources/Maps/saltern.yml @@ -469,7 +469,7 @@ entities: 0: 53709 4,4: 0: 4573 - 3: 16384 + 2: 16384 5,0: 0: 62190 5,1: @@ -500,7 +500,7 @@ entities: 0: 35771 7,3: 0: 39135 - 2: 1024 + 3: 1024 7,4: 0: 55739 7,-1: @@ -533,27 +533,27 @@ entities: 0: 3581 1,7: 0: 4369 - 3: 17484 + 2: 17484 1,8: 0: 4369 - 3: 17476 + 2: 17476 2,5: 0: 53367 2,6: 0: 1405 2,7: - 3: 7 + 2: 7 3,5: 0: 4210 - 3: 32768 + 2: 32768 3,6: 0: 13073 - 3: 8 + 2: 8 3,7: - 3: 4 + 2: 4 4,5: 0: 273 - 3: 29764 + 2: 29764 -8,0: 0: 61550 -9,0: @@ -623,7 +623,7 @@ entities: 7,-2: 0: 57599 7,-4: - 3: 8738 + 2: 8738 7,-3: 0: 3808 8,-3: @@ -644,7 +644,7 @@ entities: 0: 65524 0,-9: 0: 52416 - 3: 273 + 2: 273 1,-8: 0: 65488 1,-7: @@ -661,7 +661,7 @@ entities: 0: 61695 2,-9: 0: 13107 - 3: 2176 + 2: 2176 3,-8: 0: 52637 3,-7: @@ -670,76 +670,76 @@ entities: 0: 64733 4,-8: 0: 4483 - 3: 17484 + 2: 17484 4,-7: 0: 65281 - 3: 12 + 2: 12 4,-6: 0: 61152 4,-9: - 3: 16384 + 2: 16384 0: 32768 5,-8: - 3: 21855 + 2: 21855 0: 160 5,-7: - 3: 15 + 2: 15 0: 63232 5,-9: - 3: 20480 + 2: 20480 0: 40960 5,-6: 0: 26214 6,-8: - 3: 21855 + 2: 21855 0: 160 6,-7: - 3: 15 + 2: 15 0: 56576 6,-6: 0: 61919 6,-9: - 3: 20480 + 2: 20480 0: 40960 7,-8: - 3: 21855 + 2: 21855 0: 160 7,-7: - 3: 11823 + 2: 11823 7,-5: 0: 36846 7,-9: - 3: 20767 + 2: 20767 0: 40960 7,-6: - 3: 546 + 2: 546 0: 32768 8,-8: - 3: 21855 + 2: 21855 0: 160 8,-7: - 3: 9007 + 2: 9007 8,-5: 0: 4017 -4,5: 0: 238 -4,6: 0: 255 - 3: 61440 + 2: 61440 -5,5: 0: 58982 -5,6: 0: 238 - 3: 61440 + 2: 61440 -3,5: 0: 3295 -3,6: 0: 255 - 3: 61440 + 2: 61440 -3,7: - 3: 51406 + 2: 51406 -3,8: - 3: 14 + 2: 14 0: 52224 -2,5: 0: 52701 @@ -749,51 +749,51 @@ entities: 0: 61166 -1,8: 0: 34952 - 3: 13104 + 2: 13104 -9,4: 0: 65471 -8,5: 0: 13072 - 3: 128 + 2: 128 -9,5: 0: 65459 -8,6: 0: 1 - 3: 63304 + 2: 63304 -9,6: 0: 59 - 3: 63488 + 2: 63488 -8,7: - 3: 2039 + 2: 2039 -9,7: - 3: 28671 + 2: 28671 -7,6: - 3: 63249 + 2: 63249 0: 14 -7,7: - 3: 16 + 2: 16 -7,5: 0: 1038 - 3: 4352 + 2: 4352 -6,5: 0: 65327 -6,6: 0: 255 - 3: 61440 + 2: 61440 -3,9: 0: 12 - 3: 3584 + 2: 3584 -2,8: 0: 30560 -2,9: 0: 7 - 3: 3840 + 2: 3840 -1,9: - 3: 405 + 2: 405 0,9: - 3: 240 + 2: 240 4,6: - 3: 550 + 2: 550 0: 34816 5,5: 0: 30583 @@ -803,24 +803,24 @@ entities: 0: 5 6,5: 0: 255 - 3: 61440 + 2: 61440 6,6: - 3: 35561 + 2: 35561 7,5: 0: 36063 - 3: 4096 + 2: 4096 7,6: - 3: 53196 + 2: 53196 8,4: 0: 4351 - 3: 57344 + 2: 57344 8,5: 0: 272 - 5: 17472 + 4: 17472 8,6: - 3: 4081 + 2: 4081 1,9: - 3: 18 + 2: 18 9,0: 0: 65102 9,1: @@ -831,7 +831,7 @@ entities: 0: 65535 9,4: 0: 255 - 3: 61440 + 2: 61440 9,-1: 0: 60942 10,0: @@ -846,52 +846,52 @@ entities: 0: 48015 10,4: 0: 255 - 3: 61440 + 2: 61440 11,0: 0: 65520 11,1: 0: 53759 11,2: 0: 4319 - 4: 49152 + 5: 49152 11,3: 0: 61457 - 4: 204 + 5: 204 11,-1: 0: 30583 11,4: 0: 255 - 3: 61440 + 2: 61440 12,0: 0: 65527 12,1: 0: 28791 12,2: 0: 119 - 4: 28672 + 5: 28672 12,3: - 4: 119 + 5: 119 0: 61440 12,-1: 0: 29311 12,4: 0: 255 - 3: 61440 + 2: 61440 13,0: 0: 49080 13,1: 0: 48058 13,2: - 3: 13104 + 2: 13104 0: 34826 13,3: - 3: 35059 + 2: 35059 0: 12288 13,-1: 0: 14119 13,4: 0: 51 - 3: 63624 + 2: 63624 14,0: 0: 48123 14,1: @@ -899,9 +899,9 @@ entities: 14,2: 0: 15235 14,3: - 3: 65528 + 2: 65528 14,4: - 3: 62455 + 2: 62455 15,0: 0: 56797 15,1: @@ -909,23 +909,23 @@ entities: 15,2: 0: 3548 15,3: - 3: 32767 + 2: 32767 15,4: - 3: 12850 + 2: 12850 15,-1: 0: 52701 16,0: 0: 13116 - 4: 52416 + 5: 52416 16,1: 0: 65484 16,2: - 3: 15 + 2: 15 0: 4080 16,3: - 3: 20479 + 2: 20479 8,-4: - 3: 8738 + 2: 8738 0: 34952 9,-4: 0: 56789 @@ -937,15 +937,15 @@ entities: 0: 18295 10,-4: 0: 65024 - 3: 14 + 2: 14 10,-3: 0: 65520 10,-2: 0: 63743 10,-5: - 3: 34956 + 2: 34956 11,-4: - 3: 2187 + 2: 2187 0: 13056 11,-3: 0: 43946 @@ -953,10 +953,10 @@ entities: 0: 30250 11,-5: 0: 32776 - 3: 17968 + 2: 17968 12,-4: 0: 7 - 3: 4088 + 2: 4088 12,-3: 0: 65535 12,-2: @@ -964,7 +964,7 @@ entities: -4,-8: 0: 4016 -4,-9: - 3: 28672 + 2: 28672 0: 127 -5,-8: 0: 2995 @@ -994,15 +994,15 @@ entities: 0: 57309 -2,-9: 0: 61440 - 3: 47 + 2: 47 -1,-6: 0: 30065 -8,-8: 0: 60931 - 3: 8 + 2: 8 -8,-9: 0: 12288 - 3: 35064 + 2: 35064 -9,-8: 0: 56653 -8,-7: @@ -1013,16 +1013,16 @@ entities: 0: 36863 -9,-6: 0: 3211 - 3: 12288 + 2: 12288 -8,-5: 0: 48123 -9,-5: 0: 43008 - 3: 4 + 2: 4 -8,-4: 0: 35771 -7,-8: - 3: 7 + 2: 7 0: 7936 -7,-6: 0: 61428 @@ -1033,7 +1033,7 @@ entities: -7,-4: 0: 36317 -7,-9: - 3: 17408 + 2: 17408 0: 32768 -6,-8: 0: 36747 @@ -1043,30 +1043,30 @@ entities: 0: 62463 -6,-9: 0: 61440 - 3: 34 + 2: 34 -6,-7: 0: 52878 -6,-4: 0: 62463 -5,-9: 0: 12799 - 3: 32768 + 2: 32768 -9,-4: 0: 34952 - 3: 800 + 2: 800 -8,-3: 0: 3663 -9,-3: 0: 52428 - 3: 4369 + 2: 4369 -8,-2: 0: 59119 -9,-2: 0: 52428 - 3: 4369 + 2: 4369 -9,-1: 0: 56780 - 3: 1 + 2: 1 -7,-2: 0: 65262 -7,-3: @@ -1086,11 +1086,11 @@ entities: -11,0: 0: 3855 -12,1: - 3: 2056 + 2: 2056 -11,2: 0: 3855 -11,1: - 3: 546 + 2: 546 0: 2184 -10,0: 0: 65535 @@ -1099,95 +1099,95 @@ entities: -10,2: 0: 53247 -11,3: - 3: 34952 + 2: 34952 -10,3: 0: 61166 -10,-1: 0: 60928 - 3: 15 + 2: 15 -10,4: 0: 61182 -12,-1: - 3: 2738 + 2: 2738 -11,-1: - 3: 3628 + 2: 3628 -11,-2: - 3: 32768 + 2: 32768 -10,-2: - 3: 4492 + 2: 4492 -10,-3: - 3: 57480 + 2: 57480 -10,-4: - 3: 136 + 2: 136 -10,-5: - 3: 32776 + 2: 32776 12,-5: 0: 62079 13,-4: - 3: 3064 + 2: 3064 13,-3: 0: 48059 13,-2: 0: 63243 13,-5: - 3: 39912 + 2: 39912 14,-4: - 3: 1039 + 2: 1039 14,-3: 0: 13107 - 3: 128 + 2: 128 14,-2: 0: 65283 14,-1: 0: 4095 14,-5: - 3: 17600 + 2: 17600 15,-4: - 3: 8739 + 2: 8739 15,-3: - 3: 62066 + 2: 62066 15,-2: 0: 7424 - 3: 206 + 2: 206 15,-5: - 3: 8721 + 2: 8721 16,-3: - 3: 61440 + 2: 61440 16,-2: - 3: 255 + 2: 255 0: 3840 16,-1: 0: 53247 8,-9: - 3: 24143 + 2: 24143 0: 41120 8,-6: - 3: 3618 + 2: 3618 9,-8: - 3: 15 + 2: 15 9,-7: - 3: 15 + 2: 15 9,-6: - 3: 3840 + 2: 3840 10,-8: - 3: 55703 + 2: 55703 10,-7: - 3: 8743 + 2: 8743 0: 34816 10,-6: - 3: 50978 + 2: 50978 0: 8 10,-9: - 3: 40847 + 2: 40847 11,-8: - 3: 54 + 2: 54 0: 2048 11,-7: 0: 64988 11,-6: 0: 3293 11,-9: - 3: 49921 + 2: 49921 12,-8: 0: 12144 12,-7: @@ -1195,78 +1195,78 @@ entities: 12,-6: 0: 24568 20,-1: - 3: 256 + 2: 256 19,-1: - 3: 65335 + 2: 65335 20,0: - 3: 16179 + 2: 16179 19,0: - 3: 39118 + 2: 39118 0: 17441 20,1: - 3: 14135 + 2: 14135 19,1: - 3: 39321 + 2: 39321 0: 17476 20,2: - 3: 29495 + 2: 29495 19,2: - 3: 53179 + 2: 53179 0: 8260 20,3: - 3: 35 + 2: 35 19,3: - 3: 4095 + 2: 4095 12,-9: - 3: 61440 + 2: 61440 13,-8: - 3: 35043 + 2: 35043 13,-7: 0: 64849 13,-6: 0: 349 - 3: 32768 + 2: 32768 13,-9: - 3: 4096 + 2: 4096 14,-8: - 3: 6144 + 2: 6144 14,-7: - 3: 8739 + 2: 8739 14,-6: - 3: 4898 + 2: 4898 15,-8: - 3: 4352 + 2: 4352 15,-7: - 3: 4369 + 2: 4369 15,-6: - 3: 4369 + 2: 4369 -11,4: 0: 192 - 3: 32768 + 2: 32768 -11,6: 0: 12 - 3: 34816 + 2: 34816 -11,7: - 3: 35840 + 2: 35840 -11,5: - 3: 2184 + 2: 2184 -10,6: 0: 4335 - 3: 57344 + 2: 57344 -10,7: - 3: 4040 + 2: 4040 -11,8: - 3: 34952 + 2: 34952 -10,5: 0: 61166 -9,8: 0: 2827 - 3: 25844 + 2: 25844 0,-12: - 3: 127 + 2: 127 0: 12288 -1,-12: - 3: 975 + 2: 975 0: 32768 0,-11: 0: 29107 @@ -1274,14 +1274,14 @@ entities: 0: 2047 0,-10: 0: 247 - 3: 57344 + 2: 57344 -1,-10: 0: 255 - 3: 36864 + 2: 36864 -1,-9: - 3: 3257 + 2: 3257 1,-12: - 3: 4375 + 2: 4375 1,-11: 0: 3536 1,-10: @@ -1291,254 +1291,254 @@ entities: 2,-10: 0: 14196 2,-12: - 3: 44800 + 2: 44800 3,-12: - 3: 768 + 2: 768 3,-10: - 3: 18240 + 2: 18240 3,-9: - 3: 1908 + 2: 1908 7,-12: - 3: 7455 + 2: 7455 7,-11: - 3: 7453 + 2: 7453 7,-10: - 3: 4381 + 2: 4381 8,-12: - 3: 20303 + 2: 20303 8,-11: - 3: 20303 + 2: 20303 8,-10: - 3: 20047 + 2: 20047 0: 40960 9,5: 6: 4368 - 4: 17472 + 5: 17472 9,6: - 3: 12272 + 2: 12272 10,5: - 4: 4368 + 5: 4368 7: 17472 10,6: - 3: 4080 + 2: 4080 11,5: - 4: 21840 + 5: 21840 11,6: - 3: 61424 + 2: 61424 11,7: - 3: 12 + 2: 12 12,5: - 3: 65535 + 2: 65535 12,6: - 3: 65535 + 2: 65535 12,7: - 3: 15 + 2: 15 13,5: - 3: 55705 + 2: 55705 13,6: - 3: 16383 + 2: 16383 13,7: - 3: 1 + 2: 1 14,5: - 3: 30591 + 2: 30591 14,6: - 3: 7 + 2: 7 15,5: - 3: 35 + 2: 35 -4,-11: - 3: 3840 + 2: 3840 -5,-11: - 3: 11776 + 2: 11776 -4,-10: 0: 6143 -5,-10: 0: 14472 - 3: 2 + 2: 2 -3,-11: - 3: 304 + 2: 304 0: 34944 -3,-10: 0: 29949 -2,-10: 0: 1019 -2,-11: - 3: 544 + 2: 544 0: 2176 -2,-12: - 3: 2048 + 2: 2048 17,-3: - 3: 61696 + 2: 61696 17,-2: - 3: 3327 + 2: 3327 0: 768 17,-1: 0: 4369 - 3: 52428 + 2: 52428 17,0: 0: 19969 - 3: 8 - 4: 4368 + 2: 8 + 5: 4368 18,-3: - 3: 4096 + 2: 4096 18,-2: - 3: 59381 + 2: 59381 18,-1: - 3: 15358 + 2: 15358 0: 33792 18,0: - 3: 65399 + 2: 65399 19,-2: - 3: 12288 + 2: 12288 17,1: 0: 65365 17,2: 0: 4095 17,3: - 3: 16383 + 2: 16383 18,1: - 3: 61167 + 2: 61167 18,2: 0: 1792 - 3: 2190 + 2: 2190 18,3: - 3: 40959 + 2: 40959 12,-10: - 3: 61440 + 2: 61440 11,-10: - 3: 61440 + 2: 61440 13,-10: - 3: 7936 + 2: 7936 14,-10: - 3: 256 + 2: 256 9,-12: - 3: 1807 + 2: 1807 9,-11: - 3: 1799 + 2: 1799 9,-10: - 3: 7 + 2: 7 9,-9: - 3: 3855 + 2: 3855 10,-12: - 3: 4369 + 2: 4369 10,-11: - 3: 4369 + 2: 4369 10,-10: - 3: 4369 + 2: 4369 -12,-8: - 3: 64170 + 2: 64170 -13,-8: - 3: 64170 + 2: 64170 -12,-7: - 3: 64170 + 2: 64170 -13,-7: - 3: 64170 + 2: 64170 -12,-9: - 3: 61440 + 2: 61440 -11,-8: - 3: 64170 + 2: 64170 -11,-7: - 3: 64170 + 2: 64170 -11,-9: - 3: 61440 + 2: 61440 -10,-8: - 3: 12834 + 2: 12834 0: 34828 -10,-7: - 3: 12834 + 2: 12834 0: 34952 -10,-9: - 3: 13288 + 2: 13288 0: 32768 -10,-6: - 3: 57378 + 2: 57378 0: 8 -9,-9: 0: 61440 - 3: 248 + 2: 248 -13,-9: - 3: 61440 + 2: 61440 -11,-10: - 3: 8 + 2: 8 -10,-10: - 3: 63631 + 2: 63631 -10,-12: - 3: 59592 + 2: 59592 -9,-12: - 3: 63743 + 2: 63743 -10,-11: - 3: 34952 + 2: 34952 -9,-11: - 3: 63736 + 2: 63736 -9,-10: - 3: 63736 + 2: 63736 -9,-13: - 3: 61440 + 2: 61440 -8,-12: - 3: 63743 + 2: 63743 -8,-11: - 3: 63736 + 2: 63736 -8,-10: - 3: 63736 + 2: 63736 -8,-13: - 3: 29696 + 2: 29696 -7,-12: - 3: 1808 + 2: 1808 -7,-11: - 3: 240 + 2: 240 -6,-11: - 3: 8192 + 2: 8192 -6,-10: - 3: 25262 + 2: 25262 -14,0: 0: 3598 -14,2: 0: 3598 -13,-1: - 3: 3648 + 2: 3648 -11,9: - 3: 35980 + 2: 35980 -11,10: - 3: 51336 + 2: 51336 -10,8: - 3: 497 + 2: 497 0: 3084 -10,9: - 3: 449 + 2: 449 0: 3084 -10,10: - 3: 4593 + 2: 4593 0: 3084 -10,11: - 3: 227 + 2: 227 -9,9: 0: 2827 - 3: 21748 + 2: 21748 -9,10: 0: 2827 - 3: 58612 + 2: 58612 -9,11: - 3: 254 + 2: 254 -8,8: 0: 1799 - 3: 112 + 2: 112 -8,9: 0: 1799 - 3: 4208 + 2: 4208 -8,10: 0: 1799 - 3: 112 + 2: 112 -8,11: - 3: 112 + 2: 112 -15,-8: - 3: 34944 + 2: 34944 -14,-8: - 3: 64443 + 2: 64443 -15,-7: - 3: 136 + 2: 136 -14,-7: - 3: 64443 + 2: 64443 -14,-9: - 3: 61440 + 2: 61440 uniqueMixes: - volume: 2500 temperature: 293.15 @@ -1571,10 +1571,8 @@ entities: - 0 - 0 - volume: 2500 - temperature: 293.14975 + immutable: True moles: - - 20.078888 - - 75.53487 - 0 - 0 - 0 @@ -1585,11 +1583,13 @@ entities: - 0 - 0 - 0 - - volume: 2500 - immutable: True - moles: - 0 - 0 + - volume: 2500 + temperature: 293.14975 + moles: + - 20.078888 + - 75.53487 - 0 - 0 - 0 @@ -1604,7 +1604,7 @@ entities: temperature: 293.15 moles: - 0 - - 0 + - 6666.982 - 0 - 0 - 0 @@ -1619,7 +1619,7 @@ entities: temperature: 293.15 moles: - 0 - - 6666.982 + - 0 - 0 - 0 - 0 @@ -1673,55 +1673,55 @@ entities: color: '#FFFFFFFF' id: Arrows decals: - 446: -12,-30 + 440: -12,-30 - node: color: '#FFFFFFFF' id: Arrows decals: - 383: 20,17 - 398: 20,17 - 402: 20,27 - 447: -12,-29 + 377: 20,17 + 392: 20,17 + 396: 20,27 + 441: -12,-29 - node: angle: 3.141592653589793 rad color: '#FFFFFFFF' id: Arrows decals: - 399: 22,17 - 403: 22,27 + 393: 22,17 + 397: 22,27 - node: color: '#C3C3C3FF' id: Bot decals: - 757: -35,-24 - 762: -32,-32 - 763: -31,-32 + 704: -35,-24 + 709: -32,-32 + 710: -31,-32 - node: color: '#FFFFFFFF' id: Bot decals: - 377: -35,6 - 378: -30,11 - 408: -3,-31 - 613: 31,8 - 614: 31,9 + 375: -35,6 + 376: -30,11 + 402: -3,-31 + 566: 31,8 + 567: 31,9 - node: zIndex: 1 color: '#FFFFFFFF' id: Bot decals: - 481: 20,7 - 482: 21,7 - 483: 22,7 - 550: -7,-26 - 551: -6,-26 + 442: 20,7 + 443: 21,7 + 444: 22,7 + 511: -7,-26 + 512: -6,-26 - node: angle: 3.141592653589793 rad color: '#FFFFFFFF' id: Bot decals: - 400: 20,17 - 401: 22,17 + 394: 20,17 + 395: 22,17 - node: color: '#FFFFFFFF' id: BotRight @@ -1732,577 +1732,577 @@ entities: color: '#C3C3C3FF' id: Box decals: - 775: -33,-29 - 776: -33,-28 + 722: -33,-29 + 723: -33,-28 - node: color: '#FFFFFFFF' id: Box decals: - 914: -33,21 - 915: -32,21 - 916: -32,24 - 917: -33,24 - 926: -31,23 - 927: -31,22 + 824: -33,21 + 825: -32,21 + 826: -32,24 + 827: -33,24 + 834: -31,23 + 835: -31,22 - node: zIndex: 2 color: '#FFFFFFFF' id: BrickTileDarkCornerNe decals: - 556: -9,-28 + 517: -9,-28 - node: zIndex: 2 color: '#FFFFFFFF' id: BrickTileDarkCornerNw decals: - 557: -10,-28 + 518: -10,-28 - node: zIndex: 2 color: '#FFFFFFFF' id: BrickTileDarkCornerSe decals: - 555: -9,-30 + 516: -9,-30 - node: zIndex: 2 color: '#FFFFFFFF' id: BrickTileDarkCornerSw decals: - 554: -10,-30 + 515: -10,-30 - node: color: '#FFFFFFFF' id: BrickTileDarkInnerSe decals: - 612: -11,20 + 565: -11,20 - node: zIndex: 1 color: '#FFFFFFFF' id: BrickTileDarkInnerSe decals: - 488: 6,17 + 449: 6,17 - node: color: '#FFFFFFFF' id: BrickTileDarkLineE decals: - 530: 0,-25 - 736: -1,29 - 737: -1,28 + 491: 0,-25 + 683: -1,29 + 684: -1,28 - node: zIndex: 2 color: '#FFFFFFFF' id: BrickTileDarkLineE decals: - 558: -9,-29 + 519: -9,-29 - node: color: '#FFFFFFFF' id: BrickTileDarkLineN decals: - 525: -6,-23 - 526: -5,-23 - 527: -4,-23 - 734: 3,30 - 735: 4,30 + 486: -6,-23 + 487: -5,-23 + 488: -4,-23 + 681: 3,30 + 682: 4,30 - node: zIndex: 2 color: '#FFFFFFFF' id: BrickTileDarkLineN decals: - 565: 12,-14 - 566: 13,-14 - 567: 14,-14 - 568: 15,-14 + 526: 12,-14 + 527: 13,-14 + 528: 14,-14 + 529: 15,-14 - node: color: '#FFFFFFFF' id: BrickTileDarkLineS decals: - 609: -8,20 - 610: -9,20 - 611: -10,20 + 562: -8,20 + 563: -9,20 + 564: -10,20 - node: zIndex: 1 color: '#FFFFFFFF' id: BrickTileDarkLineS decals: - 484: 10,17 - 485: 9,17 - 486: 8,17 - 487: 7,17 + 445: 10,17 + 446: 9,17 + 447: 8,17 + 448: 7,17 - node: zIndex: 2 color: '#FFFFFFFF' id: BrickTileDarkLineS decals: - 569: 12,-18 - 570: 13,-18 - 571: 14,-18 - 572: 15,-18 + 530: 12,-18 + 531: 13,-18 + 532: 14,-18 + 533: 15,-18 - node: color: '#FFFFFFFF' id: BrickTileDarkLineW decals: - 531: -2,-25 + 492: -2,-25 - node: zIndex: 2 color: '#FFFFFFFF' id: BrickTileDarkLineW decals: - 559: -10,-29 + 520: -10,-29 - node: zIndex: 2 color: '#D381C9FF' id: BrickTileSteelCornerNe decals: - 553: -6,-26 + 514: -6,-26 - node: color: '#D381C9FF' id: BrickTileSteelCornerNw decals: - 539: -4,-26 + 500: -4,-26 - node: color: '#D381C9FF' id: BrickTileSteelCornerSe decals: - 545: -8,-24 + 506: -8,-24 - node: color: '#D381C9E5' id: BrickTileSteelCornerSw decals: - 437: -12,-32 + 431: -12,-32 - node: color: '#FFFFFFFF' id: BrickTileSteelCornerSw decals: - 625: 25,19 + 578: 25,19 - node: color: '#C3C3C3FF' id: BrickTileSteelInnerNe decals: - 751: -34,-32 - 780: -35,-27 + 698: -34,-32 + 727: -35,-27 - node: color: '#D381C9E5' id: BrickTileSteelInnerNe decals: - 441: -10,-26 + 435: -10,-26 - node: color: '#D381C9FF' id: BrickTileSteelInnerNe decals: - 529: -6,-27 - 544: 0,-29 - 549: -13,-19 + 490: -6,-27 + 505: 0,-29 + 510: -13,-19 - node: color: '#D381C9FF' id: BrickTileSteelInnerNw decals: - 538: -4,-27 - 540: -3,-26 + 499: -4,-27 + 501: -3,-26 - node: color: '#C3C3C3FF' id: BrickTileSteelInnerSe decals: - 761: -34,-30 - 771: -36,-28 - 772: -33,-30 + 708: -34,-30 + 718: -36,-28 + 719: -33,-30 - node: color: '#D381C9E5' id: BrickTileSteelInnerSe decals: - 431: -18,-28 - 434: -7,-33 + 425: -18,-28 + 428: -7,-33 - node: color: '#D381C9FF' id: BrickTileSteelInnerSe decals: - 541: 0,-26 - 546: -8,-23 + 502: 0,-26 + 507: -8,-23 - node: color: '#C3C3C3FF' id: BrickTileSteelInnerSw decals: - 738: -34,-28 + 685: -34,-28 - node: color: '#D381C9E5' id: BrickTileSteelInnerSw decals: - 413: -12,-20 - 438: -12,-31 + 407: -12,-20 + 432: -12,-31 - node: color: '#C3C3C3FF' id: BrickTileSteelLineE decals: - 748: -33,-29 - 749: -33,-28 - 750: -34,-31 - 755: -35,-26 - 756: -35,-25 - 759: -33,-30 - 770: -36,-29 - 779: -36,-30 + 695: -33,-29 + 696: -33,-28 + 697: -34,-31 + 702: -35,-26 + 703: -35,-25 + 706: -33,-30 + 717: -36,-29 + 726: -36,-30 - node: color: '#D381C9FF' id: BrickTileSteelLineE decals: - 542: 0,-27 - 543: 0,-28 + 503: 0,-27 + 504: 0,-28 - node: color: '#C3C3C3FF' id: BrickTileSteelLineN decals: - 746: -37,-24 - 747: -36,-24 - 752: -33,-32 - 753: -33,-27 - 754: -34,-27 + 693: -37,-24 + 694: -36,-24 + 699: -33,-32 + 700: -33,-27 + 701: -34,-27 - node: color: '#D381C9E5' id: BrickTileSteelLineN decals: - 415: -8,-19 - 416: -9,-19 - 417: -10,-19 - 418: -11,-19 - 440: -9,-26 + 409: -8,-19 + 410: -9,-19 + 411: -10,-19 + 412: -11,-19 + 434: -9,-26 - node: color: '#D381C9FF' id: BrickTileSteelLineN decals: - 528: -8,-26 - 548: -12,-19 + 489: -8,-26 + 509: -12,-19 - node: zIndex: 2 color: '#D381C9FF' id: BrickTileSteelLineN decals: - 552: -7,-26 + 513: -7,-26 - node: color: '#FFFFFFFF' id: BrickTileSteelLineN decals: - 409: -11,-24 + 403: -11,-24 - node: color: '#C3C3C3FF' id: BrickTileSteelLineS decals: - 741: -33,-33 - 760: -33,-30 - 764: -34,-33 - 769: -37,-30 - 777: -35,-28 - 778: -36,-30 + 688: -33,-33 + 707: -33,-30 + 711: -34,-33 + 716: -37,-30 + 724: -35,-28 + 725: -36,-30 - node: color: '#D381C9E5' id: BrickTileSteelLineS decals: - 414: -13,-20 - 427: -17,-28 - 428: -16,-28 - 429: -15,-28 - 430: -14,-28 - 432: -5,-33 - 433: -6,-33 - 436: -11,-32 - 439: -13,-31 + 408: -13,-20 + 421: -17,-28 + 422: -16,-28 + 423: -15,-28 + 424: -14,-28 + 426: -5,-33 + 427: -6,-33 + 430: -11,-32 + 433: -13,-31 - node: color: '#D381C9FF' id: BrickTileSteelLineS decals: - 547: -9,-24 + 508: -9,-24 - node: color: '#FFFFFFFF' id: BrickTileSteelLineS decals: - 498: 57,5 - 499: 56,5 - 622: 26,19 - 623: 27,19 - 624: 28,19 + 459: 57,5 + 460: 56,5 + 575: 26,19 + 576: 27,19 + 577: 28,19 - node: color: '#C3C3C3FF' id: BrickTileSteelLineW decals: - 739: -34,-29 - 740: -34,-32 - 742: -37,-27 - 743: -37,-26 - 744: -37,-25 - 745: -37,-24 - 758: -34,-30 - 768: -37,-28 + 686: -34,-29 + 687: -34,-32 + 689: -37,-27 + 690: -37,-26 + 691: -37,-25 + 692: -37,-24 + 705: -34,-30 + 715: -37,-28 - node: color: '#D381C9E5' id: BrickTileSteelLineW decals: - 410: -12,-23 - 411: -12,-22 - 412: -12,-21 + 404: -12,-23 + 405: -12,-22 + 406: -12,-21 - node: color: '#FFFFFFFF' id: BrickTileSteelLineW decals: - 626: 25,20 - 627: 25,21 + 579: 25,20 + 580: 25,21 - node: color: '#689F54FF' id: BrickTileWhiteCornerNe decals: - 791: -21,-5 + 738: -21,-5 - node: color: '#689F54FF' id: BrickTileWhiteCornerNw decals: - 792: -23,-5 + 739: -23,-5 - node: zIndex: 2 color: '#EFB34196' id: BrickTileWhiteCornerSe decals: - 592: -8,7 + 547: -8,7 - node: zIndex: 2 color: '#52B4E996' id: BrickTileWhiteCornerSw decals: - 588: 7,-18 + 543: 7,-18 - node: color: '#A4610696' id: BrickTileWhiteCornerSw decals: - 632: 18,15 + 585: 18,15 - node: zIndex: 2 color: '#EFB34196' id: BrickTileWhiteCornerSw decals: - 593: -14,7 + 548: -14,7 - node: color: '#52B4E996' id: BrickTileWhiteEndE decals: - 512: 49,-12 + 473: 49,-12 - node: color: '#9FED5896' id: BrickTileWhiteEndE decals: - 508: 52,-10 + 469: 52,-10 - node: color: '#A4610696' id: BrickTileWhiteEndE decals: - 515: 52,-8 + 476: 52,-8 - node: color: '#D381C996' id: BrickTileWhiteEndE decals: - 511: 52,-12 + 472: 52,-12 - node: color: '#D4D4D496' id: BrickTileWhiteEndE decals: - 507: 49,-10 + 468: 49,-10 - node: color: '#EFB34196' id: BrickTileWhiteEndE decals: - 500: 49,-8 + 461: 49,-8 - node: color: '#334E6DC8' id: BrickTileWhiteEndN decals: - 502: 57,-8 + 463: 57,-8 - node: color: '#DE3A3A96' id: BrickTileWhiteEndN decals: - 505: 57,-11 + 466: 57,-11 - node: color: '#334E6DC8' id: BrickTileWhiteEndS decals: - 503: 57,-9 + 464: 57,-9 - node: color: '#DE3A3A96' id: BrickTileWhiteEndS decals: - 504: 57,-12 + 465: 57,-12 - node: color: '#52B4E996' id: BrickTileWhiteEndW decals: - 513: 48,-12 + 474: 48,-12 - node: color: '#9FED5896' id: BrickTileWhiteEndW decals: - 509: 51,-10 + 470: 51,-10 - node: color: '#A4610696' id: BrickTileWhiteEndW decals: - 514: 51,-8 + 475: 51,-8 - node: color: '#D381C996' id: BrickTileWhiteEndW decals: - 510: 51,-12 + 471: 51,-12 - node: color: '#D4D4D496' id: BrickTileWhiteEndW decals: - 506: 48,-10 + 467: 48,-10 - node: color: '#EFB34196' id: BrickTileWhiteEndW decals: - 501: 48,-8 + 462: 48,-8 - node: color: '#759DBCFF' id: BrickTileWhiteInnerSe decals: - 975: 19,-18 + 879: 19,-18 - node: color: '#A4610696' id: BrickTileWhiteInnerSe decals: - 630: 22,15 + 583: 22,15 - node: zIndex: 2 color: '#52B4E996' id: BrickTileWhiteInnerSw decals: - 580: 17,-13 + 536: 17,-13 - node: color: '#A4610696' id: BrickTileWhiteInnerSw decals: - 631: 19,15 + 584: 19,15 - node: color: '#689F54FF' id: BrickTileWhiteLineE decals: - 789: -21,-8 - 790: -21,-7 + 736: -21,-8 + 737: -21,-7 - node: color: '#759DBCFF' id: BrickTileWhiteLineE decals: - 971: 19,-19 - 972: 20,-18 - 973: 20,-17 - 974: 20,-16 + 875: 19,-19 + 876: 20,-18 + 877: 20,-17 + 878: 20,-16 - node: zIndex: 2 color: '#EFB34196' id: BrickTileWhiteLineE decals: - 594: -8,8 + 549: -8,8 - node: color: '#689F54FF' id: BrickTileWhiteLineN decals: - 793: -22,-5 + 740: -22,-5 - node: color: '#759DBCFF' id: BrickTileWhiteLineN decals: - 979: 20,-16 - 980: 19,-16 - 981: 17,-16 + 883: 20,-16 + 884: 19,-16 + 885: 17,-16 - node: color: '#A4610696' id: BrickTileWhiteLineN decals: - 618: 26,10 - 619: 27,10 + 571: 26,10 + 572: 27,10 - node: zIndex: 2 color: '#52B4E996' id: BrickTileWhiteLineS decals: - 582: 8,-18 - 583: 9,-18 - 589: 10,-18 + 537: 8,-18 + 538: 9,-18 + 544: 10,-18 - node: color: '#759DBCFF' id: BrickTileWhiteLineS decals: - 976: 20,-18 - 977: 17,-19 - 978: 19,-19 + 880: 20,-18 + 881: 17,-19 + 882: 19,-19 - node: color: '#A4610696' id: BrickTileWhiteLineS decals: - 620: 26,8 - 621: 27,8 - 628: 24,15 - 629: 23,15 - 635: 26,15 - 636: 27,15 - 637: 28,15 + 573: 26,8 + 574: 27,8 + 581: 24,15 + 582: 23,15 + 588: 26,15 + 589: 27,15 + 590: 28,15 - node: zIndex: 2 color: '#EFB34196' id: BrickTileWhiteLineS decals: - 590: -9,7 - 591: -13,7 + 545: -9,7 + 546: -13,7 - node: zIndex: 2 color: '#52B4E996' id: BrickTileWhiteLineW decals: - 579: 17,-14 - 584: 7,-17 - 585: 7,-16 - 586: 7,-15 - 587: 7,-14 + 535: 17,-14 + 539: 7,-17 + 540: 7,-16 + 541: 7,-15 + 542: 7,-14 - node: color: '#689F54FF' id: BrickTileWhiteLineW decals: - 787: -23,-7 - 788: -23,-6 - 794: -23,-8 + 734: -23,-7 + 735: -23,-6 + 741: -23,-8 - node: color: '#759DBCFF' id: BrickTileWhiteLineW decals: - 968: 17,-19 - 969: 17,-18 - 970: 17,-17 - 982: 17,-16 + 872: 17,-19 + 873: 17,-18 + 874: 17,-17 + 886: 17,-16 - node: color: '#A4610696' id: BrickTileWhiteLineW decals: - 633: 18,16 - 634: 18,17 + 586: 18,16 + 587: 18,17 - node: color: '#EFB34196' id: BrickTileWhiteLineW decals: - 642: 48,-6 - 643: 48,-5 - 644: 48,-4 - 645: 48,-3 + 595: 48,-6 + 596: 48,-5 + 597: 48,-4 + 598: 48,-3 - node: zIndex: 2 color: '#EFB34196' id: BrickTileWhiteLineW decals: - 595: -14,8 + 550: -14,8 - node: color: '#FFFFFFFF' id: BrickTileWhiteLineW decals: - 965: 18,-22 - 966: 18,-23 - 967: 18,-21 + 869: 18,-22 + 870: 18,-23 + 871: 18,-21 - node: color: '#FFFFFFFF' id: Caution decals: - 524: 58,-5 + 485: 58,-5 - node: angle: 1.5707963267948966 rad color: '#FFFFFFFF' @@ -2354,8 +2354,8 @@ entities: color: '#FFFFFFFF' id: Delivery decals: - 435: -8,-33 - 533: -1,-31 + 429: -8,-33 + 494: -1,-31 - node: color: '#FFFFFFFF' id: DirtHeavy @@ -2367,23 +2367,23 @@ entities: color: '#FFFFFFFF' id: DirtHeavy decals: - 393: -24,-25 + 387: -24,-25 - node: cleanable: True zIndex: 2 color: '#FFFFFFFF' id: DirtHeavy decals: - 597: -20,10 - 601: -20,9 - 602: -18,9 + 552: -20,10 + 556: -20,9 + 557: -18,9 - node: cleanable: True zIndex: 2 color: '#FFFFFFFF' id: DirtHeavyMonotile decals: - 600: -20,8 + 555: -20,8 - node: color: '#FFFFFFFF' id: DirtLight @@ -2430,25 +2430,25 @@ entities: 365: -36,1 366: -38,7 367: -40,8 - 370: -24,-9 - 371: -24,3 - 372: -25,2 - 373: -7,3 - 375: -36,8 - 376: -38,9 - 384: -23,-24 - 385: -25,-23 - 386: -23,-22 - 387: -25,-25 - 388: -24,-24 - 389: -24,-22 + 368: -24,-9 + 369: -24,3 + 370: -25,2 + 371: -7,3 + 373: -36,8 + 374: -38,9 + 378: -23,-24 + 379: -25,-23 + 380: -23,-22 + 381: -25,-25 + 382: -24,-24 + 383: -24,-22 - node: cleanable: True zIndex: 2 color: '#FFFFFFFF' id: DirtLight decals: - 598: -19,9 + 553: -19,9 - node: color: '#FFFFFFFF' id: DirtMedium @@ -2464,18 +2464,18 @@ entities: 44: 24,17 65: 37,6 82: 53,1 - 374: -35,8 - 390: -23,-25 - 391: -25,-22 - 392: -25,-24 + 372: -35,8 + 384: -23,-25 + 385: -25,-22 + 386: -25,-24 - node: cleanable: True zIndex: 2 color: '#FFFFFFFF' id: DirtMedium decals: - 596: -19,10 - 599: -19,8 + 551: -19,10 + 554: -19,8 - node: color: '#52B4E996' id: FullTileOverlayGreyscale @@ -2493,11 +2493,11 @@ entities: color: '#52B4E996' id: FullTileOverlayGreyscale decals: - 560: 9,-3 - 561: 9,-2 - 562: 10,-2 - 563: 8,-2 - 564: 9,-1 + 521: 9,-3 + 522: 9,-2 + 523: 10,-2 + 524: 8,-2 + 525: 9,-1 - node: color: '#EFB34196' id: FullTileOverlayGreyscale @@ -2537,19 +2537,19 @@ entities: color: '#AA4D53FF' id: HalfTileOverlayGreyscale decals: - 907: -32,19 - 908: -31,19 - 909: -30,19 + 817: -32,19 + 818: -31,19 + 819: -30,19 - node: color: '#BD575DFF' id: HalfTileOverlayGreyscale decals: - 808: -9,17 - 809: -8,17 - 810: -7,17 - 820: -11,11 - 821: -12,11 - 822: -13,11 + 752: -9,17 + 753: -8,17 + 754: -7,17 + 764: -11,11 + 765: -12,11 + 766: -13,11 - node: color: '#DE3A3A96' id: HalfTileOverlayGreyscale @@ -2621,19 +2621,19 @@ entities: color: '#AA4D53FF' id: HalfTileOverlayGreyscale180 decals: - 897: -32,16 - 898: -31,16 - 899: -30,16 + 809: -32,16 + 810: -31,16 + 811: -30,16 - node: color: '#BD575DFF' id: HalfTileOverlayGreyscale180 decals: - 798: -7,10 - 799: -8,10 - 800: -9,10 - 817: -11,10 - 818: -12,10 - 819: -13,10 + 745: -7,10 + 746: -8,10 + 747: -9,10 + 761: -11,10 + 762: -12,10 + 763: -13,10 - node: color: '#DE3A3A96' id: HalfTileOverlayGreyscale180 @@ -2697,19 +2697,19 @@ entities: color: '#AA4D53FF' id: HalfTileOverlayGreyscale270 decals: - 880: -10,15 - 881: -10,14 - 893: -10,16 - 894: -33,17 - 895: -33,16 - 910: -33,18 + 803: -10,15 + 804: -10,14 + 805: -10,16 + 806: -33,17 + 807: -33,16 + 820: -33,18 - node: color: '#BD575DFF' id: HalfTileOverlayGreyscale270 decals: - 802: -10,11 - 803: -10,12 - 804: -10,13 + 749: -10,11 + 750: -10,12 + 751: -10,13 - node: color: '#DE3A3A96' id: HalfTileOverlayGreyscale270 @@ -2740,7 +2740,7 @@ entities: 185: 37,6 201: 42,8 202: 42,7 - 497: 55,2 + 458: 55,2 - node: color: '#FFD886FF' id: HalfTileOverlayGreyscale270 @@ -2778,7 +2778,7 @@ entities: 105: 12,1 307: 12,-9 308: 12,-10 - 654: 12,-11 + 601: 12,-11 - node: color: '#A4610696' id: HalfTileOverlayGreyscale90 @@ -2798,26 +2798,26 @@ entities: color: '#AA4D53FF' id: HalfTileOverlayGreyscale90 decals: - 903: -29,17 - 904: -29,18 - 911: -35,18 - 912: -35,19 - 913: -35,16 + 813: -29,17 + 814: -29,18 + 821: -35,18 + 822: -35,19 + 823: -35,16 - node: color: '#BD575DFF' id: HalfTileOverlayGreyscale90 decals: - 797: -5,8 - 811: -6,16 - 812: -6,15 - 816: -5,12 + 744: -5,8 + 755: -6,16 + 756: -6,15 + 760: -5,12 - node: color: '#C05B60FF' id: HalfTileOverlayGreyscale90 decals: - 845: -5,13 - 846: -5,11 - 858: -5,10 + 789: -5,13 + 790: -5,11 + 802: -5,10 - node: color: '#DE3A3A96' id: HalfTileOverlayGreyscale90 @@ -2853,56 +2853,56 @@ entities: color: '#FFFFFFFF' id: LoadingArea decals: - 984: 31,-20 + 887: 31,-20 - node: angle: 3.141592653589793 rad color: '#FFFFFFFF' id: LoadingArea decals: - 985: 31,-18 + 888: 31,-18 - node: color: '#FFFFFFFF' id: MiniTileDarkLineN decals: - 407: -16,-25 + 401: -16,-25 - node: color: '#D381C9E5' id: MiniTileSteelCornerSe decals: - 419: -14,-23 + 413: -14,-23 - node: color: '#D381C9E5' id: MiniTileSteelCornerSw decals: - 425: -18,-24 + 419: -18,-24 - node: color: '#D381C9E5' id: MiniTileSteelInnerSe decals: - 420: -15,-23 + 414: -15,-23 - node: color: '#D381C9E5' id: MiniTileSteelInnerSw decals: - 426: -17,-24 + 420: -17,-24 - node: color: '#D381C9E5' id: MiniTileSteelLineE decals: - 421: -14,-22 + 415: -14,-22 - node: color: '#D381C9E5' id: MiniTileSteelLineW decals: - 422: -18,-21 - 423: -18,-22 - 424: -18,-23 + 416: -18,-21 + 417: -18,-22 + 418: -18,-23 - node: color: '#FFFFFFFF' id: MiniTileSteelLineW decals: - 825: -5,-13 - 826: -5,-12 + 769: -5,-13 + 770: -5,-12 - node: color: '#334E6DC8' id: QuarterTileOverlayGreyscale @@ -2919,15 +2919,15 @@ entities: color: '#848586FF' id: QuarterTileOverlayGreyscale decals: - 931: -37,10 - 932: -37,13 - 933: -37,14 - 934: -37,15 - 935: -37,16 - 944: -37,20 - 945: -37,21 - 946: -37,22 - 947: -37,23 + 836: -37,10 + 837: -37,13 + 838: -37,14 + 839: -37,15 + 840: -37,16 + 849: -37,20 + 850: -37,21 + 851: -37,22 + 852: -37,23 - node: color: '#D4D4D428' id: QuarterTileOverlayGreyscale @@ -2951,7 +2951,7 @@ entities: 187: 50,5 198: 44,9 203: 42,6 - 496: 55,1 + 457: 55,1 - node: color: '#52B4E996' id: QuarterTileOverlayGreyscale180 @@ -2964,14 +2964,14 @@ entities: color: '#848586FF' id: QuarterTileOverlayGreyscale180 decals: - 936: -37,13 - 937: -37,14 - 938: -37,15 - 939: -37,16 - 940: -37,20 - 941: -37,21 - 942: -37,22 - 943: -37,23 + 841: -37,13 + 842: -37,14 + 843: -37,15 + 844: -37,16 + 845: -37,20 + 846: -37,21 + 847: -37,22 + 848: -37,23 - node: color: '#A4610696' id: QuarterTileOverlayGreyscale180 @@ -2981,7 +2981,7 @@ entities: color: '#AA4D53FF' id: QuarterTileOverlayGreyscale180 decals: - 896: -33,16 + 808: -33,16 - node: color: '#D4D4D428' id: QuarterTileOverlayGreyscale180 @@ -3007,17 +3007,17 @@ entities: color: '#848586FF' id: QuarterTileOverlayGreyscale270 decals: - 954: -38,17 - 955: -38,18 - 956: -38,19 - 957: -36,19 - 958: -36,18 - 959: -36,17 + 859: -38,17 + 860: -38,18 + 861: -38,19 + 862: -36,19 + 863: -36,18 + 864: -36,17 - node: color: '#C05B60FF' id: QuarterTileOverlayGreyscale270 decals: - 857: -6,10 + 801: -6,10 - node: color: '#DE3A3A96' id: QuarterTileOverlayGreyscale270 @@ -3029,7 +3029,7 @@ entities: decals: 125: 35,-5 200: 42,9 - 495: 55,3 + 456: 55,3 - node: color: '#FFD886FF' id: QuarterTileOverlayGreyscale270 @@ -3046,12 +3046,12 @@ entities: color: '#848586FF' id: QuarterTileOverlayGreyscale90 decals: - 948: -36,19 - 949: -36,18 - 950: -36,17 - 951: -38,19 - 952: -38,18 - 953: -38,17 + 853: -36,19 + 854: -36,18 + 855: -36,17 + 856: -38,19 + 857: -38,18 + 858: -38,17 - node: color: '#A4610696' id: QuarterTileOverlayGreyscale90 @@ -3088,18 +3088,18 @@ entities: color: '#8F6CFFAD' id: Rust decals: - 781: -31,-3 - 782: -30,-3 - 783: -30,0 - 784: -31,0 - 785: -31,1 - 786: -30,1 + 728: -31,-3 + 729: -30,-3 + 730: -30,0 + 731: -31,0 + 732: -31,1 + 733: -30,1 - node: color: '#FFFFFFFF' id: StandClear decals: - 396: 21,24 - 532: 1,-30 + 390: 21,24 + 493: 1,-30 - node: angle: 1.5707963267948966 rad color: '#FFFFFFFF' @@ -3110,13 +3110,13 @@ entities: color: '#AA4D53FF' id: ThreeQuarterTileOverlayGreyscale decals: - 906: -33,19 + 816: -33,19 - node: color: '#BD575DFF' id: ThreeQuarterTileOverlayGreyscale decals: - 815: -10,17 - 824: -14,11 + 759: -10,17 + 768: -14,11 - node: color: '#52B4E996' id: ThreeQuarterTileOverlayGreyscale180 @@ -3126,7 +3126,7 @@ entities: color: '#AA4D53FF' id: ThreeQuarterTileOverlayGreyscale180 decals: - 901: -29,16 + 812: -29,16 - node: color: '#52B4E996' id: ThreeQuarterTileOverlayGreyscale270 @@ -3136,8 +3136,8 @@ entities: color: '#BD575DFF' id: ThreeQuarterTileOverlayGreyscale270 decals: - 801: -10,10 - 823: -14,10 + 748: -10,10 + 767: -14,10 - node: color: '#52B4E996' id: ThreeQuarterTileOverlayGreyscale90 @@ -3147,18 +3147,18 @@ entities: color: '#AA4D53FF' id: ThreeQuarterTileOverlayGreyscale90 decals: - 905: -29,19 + 815: -29,19 - node: color: '#BD575DFF' id: ThreeQuarterTileOverlayGreyscale90 decals: - 813: -5,14 - 814: -6,17 + 757: -5,14 + 758: -6,17 - node: color: '#FF0000FF' id: WarnBox decals: - 695: 69,2 + 642: 69,2 - node: color: '#FFFFFFFF' id: WarnBox @@ -3168,174 +3168,174 @@ entities: color: '#FF0000FF' id: WarnCornerNE decals: - 698: 68,3 + 645: 68,3 - node: color: '#FF0000FF' id: WarnCornerNW decals: - 699: 66,3 + 646: 66,3 - node: color: '#FF0000FF' id: WarnCornerSE decals: - 697: 68,1 + 644: 68,1 - node: color: '#FF0000FF' id: WarnCornerSW decals: - 696: 66,1 + 643: 66,1 - node: color: '#FFFFFFFF' id: WarnCornerSmallNW decals: - 963: -39,23 + 868: -39,23 - node: color: '#FFFFFFFF' id: WarnCornerSmallSW decals: - 962: -39,18 + 867: -39,18 - node: color: '#FFFFFFFF' id: WarnEndE decals: - 653: 12,-12 + 600: 12,-12 - node: color: '#FF0000FF' id: WarnEndN decals: - 694: 70,3 + 641: 70,3 - node: color: '#FF0000FF' id: WarnEndS decals: - 693: 70,2 + 640: 70,2 - node: color: '#FFFFFFFF' id: WarnEndW decals: - 652: 11,-12 + 599: 11,-12 - node: color: '#C3C3C3FF' id: WarnLineE decals: - 767: -36,-33 + 714: -36,-33 - node: color: '#FF0000FF' id: WarnLineE decals: - 667: 60,7 - 668: 60,6 - 700: 68,2 + 614: 60,7 + 615: 60,6 + 647: 68,2 - node: color: '#FFFFFFFF' id: WarnLineE decals: - 442: -15,-30 - 443: -15,-31 - 516: 54,-4 - 517: 54,-5 - 518: 54,-6 - 656: 57,7 - 657: 57,6 - 658: 57,8 - 659: 57,5 - 660: 57,4 - 661: 57,3 - 662: 57,2 - 663: 57,1 - 664: 57,0 - 795: -21,-6 - 922: -32,23 - 923: -32,22 + 436: -15,-30 + 437: -15,-31 + 477: 54,-4 + 478: 54,-5 + 479: 54,-6 + 603: 57,7 + 604: 57,6 + 605: 57,8 + 606: 57,5 + 607: 57,4 + 608: 57,3 + 609: 57,2 + 610: 57,1 + 611: 57,0 + 742: -21,-6 + 832: -32,23 + 833: -32,22 - node: color: '#FF0000FF' id: WarnLineN decals: - 688: 62,0 - 689: 63,0 - 690: 63,5 - 691: 62,5 - 692: 67,1 + 635: 62,0 + 636: 63,0 + 637: 63,5 + 638: 62,5 + 639: 67,1 - node: color: '#FFFFFFFF' id: WarnLineN decals: - 404: 20,26 - 405: 21,26 - 406: 22,26 - 519: 56,-4 - 520: 57,-4 - 521: 58,-4 - 522: 59,-4 - 523: 60,-4 - 534: -3,-29 - 535: -2,-29 - 536: -1,-29 - 537: 0,-29 - 703: 70,5 - 920: -33,22 - 921: -32,22 + 398: 20,26 + 399: 21,26 + 400: 22,26 + 480: 56,-4 + 481: 57,-4 + 482: 58,-4 + 483: 59,-4 + 484: 60,-4 + 495: -3,-29 + 496: -2,-29 + 497: -1,-29 + 498: 0,-29 + 650: 70,5 + 830: -33,22 + 831: -32,22 - node: color: '#C3C3C3FF' id: WarnLineS decals: - 765: -34,-33 - 766: -36,-33 - 773: -37,-30 - 774: -37,-29 + 712: -34,-33 + 713: -36,-33 + 720: -37,-30 + 721: -37,-29 - node: color: '#FF0000FF' id: WarnLineS decals: - 669: 62,10 - 670: 62,8 - 671: 62,9 - 672: 62,7 - 673: 62,6 - 674: 62,5 - 675: 62,4 - 676: 62,3 - 677: 62,2 - 678: 62,1 - 679: 62,0 - 680: 62,-1 - 681: 62,-2 - 682: 62,-3 - 683: 62,-4 - 701: 66,2 + 616: 62,10 + 617: 62,8 + 618: 62,9 + 619: 62,7 + 620: 62,6 + 621: 62,5 + 622: 62,4 + 623: 62,3 + 624: 62,2 + 625: 62,1 + 626: 62,0 + 627: 62,-1 + 628: 62,-2 + 629: 62,-3 + 630: 62,-4 + 648: 66,2 - node: color: '#FFFFFFFF' id: WarnLineS decals: - 444: -17,-31 - 445: -17,-30 - 605: -15,18 - 606: -15,19 - 607: -15,20 - 608: -15,21 - 665: 59,7 - 666: 59,6 - 796: -19,-6 - 960: -39,17 - 961: -39,24 + 438: -17,-31 + 439: -17,-30 + 558: -15,18 + 559: -15,19 + 560: -15,20 + 561: -15,21 + 612: 59,7 + 613: 59,6 + 743: -19,-6 + 865: -39,17 + 866: -39,24 - node: color: '#FF0000FF' id: WarnLineW decals: - 684: 62,4 - 685: 63,4 - 686: 63,-1 - 687: 62,-1 - 702: 67,3 + 631: 62,4 + 632: 63,4 + 633: 63,-1 + 634: 62,-1 + 649: 67,3 - node: color: '#FFFFFFFF' id: WarnLineW decals: - 394: 21,24 - 395: 22,24 - 397: 20,24 - 918: -33,23 - 919: -32,23 + 388: 21,24 + 389: 22,24 + 391: 20,24 + 828: -33,23 + 829: -32,23 - node: angle: -1.5707963267948966 rad color: '#FFFFFFFF' @@ -3419,145 +3419,145 @@ entities: color: '#FFFFFFFF' id: WoodTrimThinCornerNe decals: - 705: 0,1 - 830: -18,-11 - 844: 0,14 + 652: 0,1 + 774: -18,-11 + 788: 0,14 - node: zIndex: 1 color: '#FFFFFFFF' id: WoodTrimThinCornerNe decals: - 494: -4,-6 + 455: -4,-6 - node: color: '#FFFFFFFF' id: WoodTrimThinCornerNw decals: - 704: -10,1 - 829: -21,-11 - 843: -4,14 + 651: -10,1 + 773: -21,-11 + 787: -4,14 - node: color: '#FFFFFFFF' id: WoodTrimThinCornerSe decals: - 706: 0,-7 - 831: -18,-13 - 847: 0,10 + 653: 0,-7 + 775: -18,-13 + 791: 0,10 - node: color: '#FFFFFFFF' id: WoodTrimThinCornerSw decals: - 708: -10,-2 - 832: -21,-13 - 842: -4,10 + 655: -10,-2 + 776: -21,-13 + 786: -4,10 - node: color: '#FFFFFFFF' id: WoodTrimThinInnerSw decals: - 707: -8,-2 + 654: -8,-2 - node: color: '#FFFFFFFF' id: WoodTrimThinLineE decals: - 720: 0,0 - 721: 0,-1 - 722: 0,-2 - 723: 0,-3 - 724: 0,-5 - 725: 0,-4 - 726: 0,-6 - 833: -18,-12 - 850: 0,11 - 851: 0,12 - 852: 0,13 + 667: 0,0 + 668: 0,-1 + 669: 0,-2 + 670: 0,-3 + 671: 0,-5 + 672: 0,-4 + 673: 0,-6 + 777: -18,-12 + 794: 0,11 + 795: 0,12 + 796: 0,13 - node: zIndex: 1 color: '#FFFFFFFF' id: WoodTrimThinLineE decals: - 493: -4,-7 - 615: 27,10 - 616: 27,9 - 617: 27,8 + 454: -4,-7 + 568: 27,10 + 569: 27,9 + 570: 27,8 - node: zIndex: 2 color: '#FFFFFFFF' id: WoodTrimThinLineE decals: - 577: 20,-12 + 534: 20,-12 - node: color: '#FFFFFFFF' id: WoodTrimThinLineN decals: - 711: -8,1 - 712: -9,1 - 713: -7,1 - 714: -6,1 - 715: -5,1 - 716: -4,1 - 717: -3,1 - 718: -2,1 - 719: -1,1 - 834: -19,-11 - 835: -20,-11 - 853: -1,14 - 854: -2,14 - 855: -3,14 + 658: -8,1 + 659: -9,1 + 660: -7,1 + 661: -6,1 + 662: -5,1 + 663: -4,1 + 664: -3,1 + 665: -2,1 + 666: -1,1 + 778: -19,-11 + 779: -20,-11 + 797: -1,14 + 798: -2,14 + 799: -3,14 - node: zIndex: 1 color: '#FFFFFFFF' id: WoodTrimThinLineN decals: - 489: -8,-6 - 490: -7,-6 - 491: -6,-6 - 492: -5,-6 + 450: -8,-6 + 451: -7,-6 + 452: -6,-6 + 453: -5,-6 - node: color: '#FFFFFFFF' id: WoodTrimThinLineS decals: - 638: 37,0 - 639: 38,0 - 640: 39,0 - 641: 40,0 - 727: -1,-7 - 728: -2,-7 - 729: -3,-7 - 733: -9,-2 - 837: -20,-13 - 838: -19,-13 - 848: -1,10 - 849: -2,10 - 856: -3,10 + 591: 37,0 + 592: 38,0 + 593: 39,0 + 594: 40,0 + 674: -1,-7 + 675: -2,-7 + 676: -3,-7 + 680: -9,-2 + 781: -20,-13 + 782: -19,-13 + 792: -1,10 + 793: -2,10 + 800: -3,10 - node: color: '#FFFFFFFF' id: WoodTrimThinLineW decals: - 709: -10,-1 - 710: -10,0 - 730: -8,-5 - 731: -8,-4 - 732: -8,-3 - 827: -14,-12 - 828: -14,-11 - 836: -21,-12 - 839: -4,13 - 840: -4,12 - 841: -4,11 + 656: -10,-1 + 657: -10,0 + 677: -8,-5 + 678: -8,-4 + 679: -8,-3 + 771: -14,-12 + 772: -14,-11 + 780: -21,-12 + 783: -4,13 + 784: -4,12 + 785: -4,11 - node: color: '#FFFF00FF' id: radiation decals: - 655: 59,7 + 602: 59,7 - node: color: '#FFFFFFFF' id: space decals: - 986: -21,-33 + 889: -21,-33 - node: color: '#010102FF' id: taser decals: - 989: -34,19 + 890: -34,19 - type: OccluderTree - type: SpreaderGrid - type: Shuttle @@ -4332,7 +4332,7 @@ entities: pos: 3.5,22.5 parent: 31 - type: Door - secondsUntilStateChange: -32364.002 + secondsUntilStateChange: -32451.49 state: Opening - type: DeviceLinkSource lastSignals: @@ -28272,18 +28272,6 @@ entities: - type: Transform pos: 24.491184,-6.41413 parent: 31 -- proto: chem_master - entities: - - uid: 606 - components: - - type: Transform - pos: 19.5,-0.5 - parent: 31 - - uid: 5075 - components: - - type: Transform - pos: 15.5,1.5 - parent: 31 - proto: ChemDispenser entities: - uid: 5076 @@ -28303,6 +28291,18 @@ entities: - type: Transform pos: 18.5,1.5 parent: 31 +- proto: ChemMaster + entities: + - uid: 606 + components: + - type: Transform + pos: 19.5,-0.5 + parent: 31 + - uid: 5075 + components: + - type: Transform + pos: 15.5,1.5 + parent: 31 - proto: ChessBoard entities: - uid: 841 @@ -56679,7 +56679,7 @@ entities: - type: Transform pos: 54.5,-1.5 parent: 31 -- proto: LogicGate +- proto: LogicGateOr entities: - uid: 11306 components: @@ -57997,6 +57997,13 @@ entities: - type: Transform pos: -35.5,-16.5 parent: 31 +- proto: PlayerStationAi + entities: + - uid: 7046 + components: + - type: Transform + pos: 49.5,-24.5 + parent: 31 - proto: PlushieAtmosian entities: - uid: 7433 @@ -65856,7 +65863,7 @@ entities: - type: Transform pos: 12.820141,26.438648 parent: 31 -- proto: soda_dispenser +- proto: SodaDispenser entities: - uid: 1418 components: @@ -69250,12 +69257,6 @@ entities: - type: Transform pos: -28.5,18.5 parent: 31 - - uid: 7046 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 49.5,-24.5 - parent: 31 - uid: 8138 components: - type: Transform @@ -69964,11 +69965,6 @@ entities: parent: 31 - proto: ToyAi entities: - - uid: 8292 - components: - - type: Transform - pos: 49.509567,-24.29748 - parent: 31 - uid: 10982 components: - type: Transform diff --git a/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml b/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml index 0bce0f2fb81..664da15f653 100644 --- a/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml +++ b/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml @@ -334,4 +334,4 @@ - id: JetpackBlue - id: SpaceCash1000 - id: BeachBall - - id: BikeHorn \ No newline at end of file + - id: BikeHorn diff --git a/Resources/Prototypes/Datasets/Names/ai.yml b/Resources/Prototypes/Datasets/Names/ai.yml index 702adc8688c..a220de53fb7 100644 --- a/Resources/Prototypes/Datasets/Names/ai.yml +++ b/Resources/Prototypes/Datasets/Names/ai.yml @@ -1,91 +1,5 @@ -- type: dataset - id: names_ai +- type: localizedDataset + id: NamesAI values: - - 16-20 - - 790 - - Adaptive Manipulator - - ALICE - - Allied Mastercomputer - - Alpha 2 - - Alpha 3 - - Alpha 4 - - Alpha 5 - - Alpha 6 - - Alpha 7 - - Alpha 8 - - Alpha 9 - - AmigoBot - - Android - - Aniel - - AOL - - Asimov - - Bishop - - Blitz - - Box - - Cassandra - - Cell - - Chii - - Chip - - Computer - - Cutie - - Daedalus - - Dee Model - - Dial Up - - Dorfl - - Duey - - Emma-2 - - Erasmus - - Everything - - Ez-27 - - FRIEND COMPUTER - - Faith - - Fi - - Frost - - George - - H.E.L.P - - Hadaly - - Helios - - Hivebot Overmind - - Huey - - Icarus - - Jinx - - K.I.N.G - - Klapaucius - - Knight - - Louie - - MARK13 - - Maria - - Marvin - - Max 404 - - Metalhead - - M.I.M.I - - MK ULTRA - - MoMMI - - Mugsy3000 - - Multivac - - NCH - - PTO - - Project Y2K - - Revelation - - Robot Devil - - S.A.M. - - S.H.O.C.K. - - S.H.R.O.U.D. - - S.O.P.H.I.E. - - Samaritan - - Shrike - - Solo - - Station Control Program - - Super 35 - - Surgeon General - - TWA - - Terminus - - Tidy - - Ulysses - - W1k1 - - X-5 - - XERXES - - Z-1 - - Z-2 - - Z-3 - - Zed + prefix: names-ai-dataset- + count: 108 diff --git a/Resources/Prototypes/Entities/Clothing/Head/hats.yml b/Resources/Prototypes/Entities/Clothing/Head/hats.yml index 65ac24d5b53..dace96ff561 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/hats.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/hats.yml @@ -941,6 +941,10 @@ - ClothMade - WhitelistChameleon - HamsterWearable + - type: SentienceTarget + flavorKind: station-event-random-sentience-flavor-inanimate + weight: 0.0002 # 5,000 times less likely than 1 regular animal + - type: BlockMovement - type: entity parent: ClothingHeadBase diff --git a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml index ada65ad33f5..0d809fd1647 100644 --- a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml +++ b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml @@ -72,6 +72,9 @@ - type: ActivatableUI key: enum.BorgUiKey.Key - type: SiliconLawBound + - type: ActionGrant + actions: + - ActionViewLaws - type: EmagSiliconLaw stunTime: 5 - type: SiliconLawProvider diff --git a/Resources/Prototypes/Entities/Mobs/Cyborgs/borg_chassis.yml b/Resources/Prototypes/Entities/Mobs/Cyborgs/borg_chassis.yml index 5db2e1e28e6..acb2c8d106d 100644 --- a/Resources/Prototypes/Entities/Mobs/Cyborgs/borg_chassis.yml +++ b/Resources/Prototypes/Entities/Mobs/Cyborgs/borg_chassis.yml @@ -260,6 +260,12 @@ laws: Medical - type: SurgeryTarget # Shitmed - type: Sanitized # Shitmed + - type: SolutionScanner + - type: InteractionPopup + interactSuccessString: petting-success-medical-cyborg + interactFailureString: petting-failure-medical-cyborg + interactSuccessSound: + path: /Audio/Ambience/Objects/periodic_beep.ogg - type: entity id: BorgChassisService diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index faa7fbc22bf..042a74c32df 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -424,6 +424,8 @@ 1.0 FollowRange: !type:Single 2.0 + - type: SentienceTarget + flavorKind: station-event-random-sentience-flavor-organic - type: entity name: glockroach diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml b/Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml index c3706cea2a5..52cc4dc6266 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml @@ -1,11 +1,11 @@ - 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 @@ -43,7 +43,6 @@ damageModifierSet: CorporealSpirit - type: Examiner - type: NoSlip - - type: Actions - type: Eye drawFov: false visMask: @@ -51,8 +50,6 @@ - Ghost - type: ContentEye maxZoom: 1.2, 1.2 - - type: DoAfter - - type: Alerts - type: NameIdentifier group: GenericNumber - type: GhostRole diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml b/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml index 2fea60ea1ea..8e3c50bbf96 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml @@ -292,6 +292,8 @@ graph: MediBot node: bot - type: NoSlip + - type: SentienceTarget + flavorKind: station-event-random-sentience-flavor-mechanical - type: Anchorable - type: InteractionPopup interactSuccessString: petting-success-medibot diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml b/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml index dae42c36190..36b0843bed5 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml @@ -3,6 +3,7 @@ parent: - BaseMob - MobDamageable + - MobPolymorphable - MobAtmosExposed id: BaseSimpleMob suffix: AI diff --git a/Resources/Prototypes/Entities/Mobs/Player/observer.yml b/Resources/Prototypes/Entities/Mobs/Player/observer.yml index a150744651f..10f1d1cb81c 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/observer.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/observer.yml @@ -1,14 +1,42 @@ - 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: @@ -16,15 +44,6 @@ 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 visMask: @@ -39,18 +58,10 @@ 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 diff --git a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml index c3ccb0330c3..6c169821ab9 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml @@ -1,3 +1,338 @@ +# 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. + categories: [ HideSpawnMenu ] + 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 } + - type: LanguageKnowledge + speaks: + - TauCetiBasic + - SolCommon + - Tradeband + - Freespeak + - Elyran + - RobotTalk + understands: + - TauCetiBasic + - SolCommon + - Tradeband + - Freespeak + - Elyran + - RobotTalk + - Sign # It's intentional that they don't "Speak" sign language. + +# 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 + categories: [ HideSpawnMenu ] + 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. + categories: [ HideSpawnMenu ] + 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"] + - type: LanguageKnowledge + speaks: + - TauCetiBasic + - SolCommon + - Tradeband + - Freespeak + - Elyran + - RobotTalk + understands: + - TauCetiBasic + - SolCommon + - Tradeband + - Freespeak + - Elyran + - RobotTalk + - Sign # It's intentional that they don't "Speak" sign language. + +# Borgs - type: entity id: PlayerBorgGeneric parent: BorgChassisGeneric @@ -50,4 +385,4 @@ slots: cell_slot: name: power-cell-slot-component-slot-name-default - startingItem: PowerCellHyper \ No newline at end of file + startingItem: PowerCellHyper diff --git a/Resources/Prototypes/Entities/Mobs/Species/base.yml b/Resources/Prototypes/Entities/Mobs/Species/base.yml index 2788ab89f35..7bb0e162769 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/base.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/base.yml @@ -3,6 +3,7 @@ parent: - BaseMob - MobDamageable + - MobPolymorphable - MobCombat id: BaseMobSpecies abstract: true diff --git a/Resources/Prototypes/Entities/Mobs/base.yml b/Resources/Prototypes/Entities/Mobs/base.yml index c98608fabb5..bacc5defc96 100644 --- a/Resources/Prototypes/Entities/Mobs/base.yml +++ b/Resources/Prototypes/Entities/Mobs/base.yml @@ -50,6 +50,13 @@ - type: OwnInteractionVerbs allowedVerbs: [] # TODO: define something here, or don't. +- 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 diff --git a/Resources/Prototypes/Entities/Objects/Devices/pda.yml b/Resources/Prototypes/Entities/Objects/Devices/pda.yml index 055e8062da9..1aa06407f0d 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/pda.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/pda.yml @@ -113,6 +113,15 @@ damage: types: Blunt: 4 + - type: SentienceTarget # sentient PDA = pAI lite + flavorKind: station-event-random-sentience-flavor-mechanical + weight: 0.001 # 1,000 PDAs = as likely to be picked as 1 regular animal + - type: BlockMovement + blockInteraction: false # lets the PDA toggle its own flashlight + - type: TypingIndicator + proto: robot + - type: Speech + speechVerb: Robotic - type: entity parent: BasePDA @@ -245,6 +254,8 @@ borderColor: "#d7d7d0" - type: Icon state: pda-cook + - type: ReplacementAccent # for random sentience event + accent: italian - type: entity parent: BasePDA @@ -329,6 +340,7 @@ accentHColor: "#333333" - type: Icon state: pda-mime + - type: Muted # for random sentience event - type: entity name: chaplain PDA diff --git a/Resources/Prototypes/Entities/Objects/Fun/Instruments/base_instruments.yml b/Resources/Prototypes/Entities/Objects/Fun/Instruments/base_instruments.yml index 614af2a4886..5a3b967f989 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/Instruments/base_instruments.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/Instruments/base_instruments.yml @@ -32,7 +32,7 @@ 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 diff --git a/Resources/Prototypes/Entities/Objects/Misc/paper.yml b/Resources/Prototypes/Entities/Objects/Misc/paper.yml index a811994c9ef..b1640949d4c 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/paper.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/paper.yml @@ -18,7 +18,7 @@ - type: PaperLabelType - type: ActivatableUI key: enum.PaperUiKey.Key - requireHands: false + requiresComplex: false - type: UserInterface interfaces: enum.PaperUiKey.Key: @@ -759,4 +759,4 @@ types: Blunt: 10 - type: StealTarget - stealGroup: BoxFolderQmClipboard + stealGroup: BoxFolderQmClipboard \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Objects/Specific/Robotics/mmi.yml b/Resources/Prototypes/Entities/Objects/Specific/Robotics/mmi.yml index ac125d36bd1..c8e827b3266 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Robotics/mmi.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Robotics/mmi.yml @@ -80,7 +80,6 @@ map: ["base"] - type: Input context: human - - type: BlockMovement - type: ToggleableGhostRole examineTextMindPresent: positronic-brain-installed examineTextMindSearching: positronic-brain-still-searching @@ -92,6 +91,7 @@ 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 diff --git a/Resources/Prototypes/Entities/Objects/Tools/access_configurator.yml b/Resources/Prototypes/Entities/Objects/Tools/access_configurator.yml index 9c8102979a1..bbc948bc021 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/access_configurator.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/access_configurator.yml @@ -79,7 +79,7 @@ type: AccessOverriderBoundUserInterface - type: ActivatableUI key: enum.AccessOverriderUiKey.Key - requireHands: true + requiresComplex: true requireActiveHand: false singleUser: true - type: ItemSlots diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml index 653deecc4b4..2200d6bb227 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml @@ -713,7 +713,7 @@ - type: StaticPrice price: 750 - type: StealTarget - stealGroup: WeaponCaptain + stealGroup: WeaponAntiqueLaser - type: MeleeWeapon attackRate: 1.3333 damage: @@ -723,6 +723,10 @@ wideAnimationRotation: 135 - type: DamageOtherOnHit staminaCost: 5 + - type: SentienceTarget # I hope this is only the captain's gun + flavorKind: station-event-random-sentience-flavor-inanimate + weight: 0.0002 # 5,000 times less likely than 1 regular animal + # not putting a BlockMovement component here cause that's funny. - type: entity name: advanced laser pistol diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/sword.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/sword.yml index ffdbdc59112..e09e68904b8 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/sword.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/sword.yml @@ -57,6 +57,12 @@ - type: Tag tags: - CaptainSabre + - type: DisarmMalus + - type: SentienceTarget + flavorKind: station-event-random-sentience-flavor-inanimate + weight: 0.0002 # 5,000 times less likely than 1 regular animal + - type: PirateAccent + # not putting a BlockMovement component here cause that's funny. - type: entity name: katana diff --git a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml index 97a6d8e076e..58a3f6ceca6 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml @@ -4,6 +4,7 @@ name: airlock description: It opens, it closes, and maybe crushes you. components: + - type: StationAiWhitelist - type: MeleeSound soundGroups: Brute: @@ -104,6 +105,8 @@ - type: SpawnOnOverload - type: UserInterface interfaces: + enum.AiUi.Key: + type: StationAiBoundUserInterface enum.WiresUiKey.Key: type: WiresBoundUserInterface - type: Airtight diff --git a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml index 4e499cc381b..59e6b5fa4d7 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml @@ -40,6 +40,7 @@ interfaces: enum.AtmosAlertsComputerUiKey.Key: type: AtmosAlertsComputerBoundUserInterface + - type: StationAiWhitelist - type: entity parent: BaseComputer @@ -297,6 +298,7 @@ interfaces: enum.PowerMonitoringConsoleUiKey.Key: type: PowerMonitoringConsoleBoundUserInterface + - type: StationAiWhitelist - type: entity parent: BaseComputer @@ -584,6 +586,7 @@ 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"] @@ -676,6 +679,7 @@ radius: 1.5 energy: 1.6 color: "#e6e227" + - type: StationAiWhitelist - type: entity parent: BaseComputer @@ -789,6 +793,7 @@ - type: GuideHelp guides: - Cargo + - type: StationAiWhitelist - type: entity id: ComputerCargoBounty @@ -1026,6 +1031,7 @@ - type: GuideHelp guides: - Cargo + - type: StationAiWhitelist - type: entity parent: BaseComputer @@ -1140,3 +1146,46 @@ 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 diff --git a/Resources/Prototypes/Entities/Structures/Machines/Medical/cryo_pod.yml b/Resources/Prototypes/Entities/Structures/Machines/Medical/cryo_pod.yml index 0485b5a5178..539c8a244ae 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/Medical/cryo_pod.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/Medical/cryo_pod.yml @@ -96,7 +96,7 @@ type: WiresBoundUserInterface - type: ActivatableUI key: enum.HealthAnalyzerUiKey.Key - requireHands: false + requiresComplex: false - type: ActivatableUIRequiresPower - type: PointLight color: "#3a807f" diff --git a/Resources/Prototypes/Entities/Structures/Machines/vending_machines.yml b/Resources/Prototypes/Entities/Structures/Machines/vending_machines.yml index f9e945c8697..b4df776351d 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/vending_machines.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/vending_machines.yml @@ -100,6 +100,7 @@ - type: Actions - type: SentienceTarget flavorKind: station-event-random-sentience-flavor-mechanical + weight: 0.025 # fuck you in particular (it now needs 40 vending machines to be as likely as 1 interesting animal) - type: StaticPrice price: 100 - type: Appearance diff --git a/Resources/Prototypes/Entities/Structures/Power/apc.yml b/Resources/Prototypes/Entities/Structures/Power/apc.yml index 9412d454476..1022c57b3f7 100644 --- a/Resources/Prototypes/Entities/Structures/Power/apc.yml +++ b/Resources/Prototypes/Entities/Structures/Power/apc.yml @@ -6,6 +6,7 @@ placement: mode: SnapgridCenter components: + - type: StationAiWhitelist - type: AmbientOnPowered - type: AmbientSound volume: -9 diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/air_alarm.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/air_alarm.yml index 6090aae882f..3e972559603 100644 --- a/Resources/Prototypes/Entities/Structures/Wallmounts/air_alarm.yml +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/air_alarm.yml @@ -111,6 +111,7 @@ collection: MetalGlassBreak params: volume: -4 + - type: StationAiWhitelist - type: entity id: AirAlarmAssembly diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/intercom.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/intercom.yml index 9a244c2c598..6345cfef3f9 100644 --- a/Resources/Prototypes/Entities/Structures/Wallmounts/intercom.yml +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/intercom.yml @@ -4,6 +4,7 @@ description: An intercom. For when the station just needs to know something. abstract: true components: + - type: StationAiWhitelist - type: WallMount - type: ApcPowerReceiver - type: Electrified diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/surveillance_camera.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/surveillance_camera.yml index 954caf67aeb..c82a3f21086 100644 --- a/Resources/Prototypes/Entities/Structures/Wallmounts/surveillance_camera.yml +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/surveillance_camera.yml @@ -4,6 +4,28 @@ 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 @@ -43,6 +65,8 @@ InUse: camera_in_use - type: UserInterface interfaces: + enum.AiUi.Key: + type: StationAiBoundUserInterface enum.SurveillanceCameraSetupUiKey.Camera: type: SurveillanceCameraSetupBoundUi enum.WiresUiKey.Key: diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/timer.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/timer.yml index 8e6b0863d67..f08451ad4d8 100644 --- a/Resources/Prototypes/Entities/Structures/Wallmounts/timer.yml +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/timer.yml @@ -7,6 +7,7 @@ snap: - Wallmount components: + - type: StationAiWhitelist - type: Transform anchored: true - type: WallMount diff --git a/Resources/Prototypes/GameRules/events.yml b/Resources/Prototypes/GameRules/events.yml index 868ba962ed4..ca4ba9942dc 100644 --- a/Resources/Prototypes/GameRules/events.yml +++ b/Resources/Prototypes/GameRules/events.yml @@ -341,7 +341,10 @@ weight: 6 duration: 1 maxOccurrences: 1 # this event has diminishing returns on interesting-ness, so we cap it + startAnnouncement: true - type: RandomSentienceRule + minSentiences: 2 + maxSentiences: 5 - type: entity parent: BaseGameRule diff --git a/Resources/Prototypes/GameRules/random_sentience.yml b/Resources/Prototypes/GameRules/random_sentience.yml new file mode 100644 index 00000000000..decbd4e2cd2 --- /dev/null +++ b/Resources/Prototypes/GameRules/random_sentience.yml @@ -0,0 +1,11 @@ +- type: localizedDataset + id: RandomSentienceEventData + values: + prefix: random-sentience-event-data- + count: 6 + +- type: localizedDataset + id: RandomSentienceEventStrength + values: + prefix: random-sentience-event-strength- + count: 8 \ No newline at end of file diff --git a/Resources/Prototypes/Maps/saltern.yml b/Resources/Prototypes/Maps/saltern.yml index 9b26bbc3c17..26ae4a74ce9 100644 --- a/Resources/Prototypes/Maps/saltern.yml +++ b/Resources/Prototypes/Maps/saltern.yml @@ -62,4 +62,5 @@ Mime: [ 1, 1 ] Musician: [ 1, 2 ] Passenger: [ -1, -1 ] - + #silicon + StationAi: [ 1, 1 ] diff --git a/Resources/Prototypes/Roles/Jobs/Science/borg.yml b/Resources/Prototypes/Roles/Jobs/Science/borg.yml index 456a761dba7..91ee25357eb 100644 --- a/Resources/Prototypes/Roles/Jobs/Science/borg.yml +++ b/Resources/Prototypes/Roles/Jobs/Science/borg.yml @@ -1,3 +1,21 @@ +# 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:CharacterPlaytimeRequirement + tracker: JobBorg + min: 18000 # 5 hrs + canBeAntag: false + icon: JobIconStationAi + supervisors: job-supervisors-rd + jobEntity: StationAiBrain + nameDataset: NamesAI + spawnLoadout: false + applyTraits: false + - type: job id: Borg name: job-name-borg diff --git a/Resources/Prototypes/Roles/Jobs/departments.yml b/Resources/Prototypes/Roles/Jobs/departments.yml index 9dca079c504..dd29f4a1339 100644 --- a/Resources/Prototypes/Roles/Jobs/departments.yml +++ b/Resources/Prototypes/Roles/Jobs/departments.yml @@ -15,7 +15,6 @@ weight: -10 roles: - Bartender - - Borg - Botanist - Boxer # - Chaplain # DeltaV - Move Chaplain into Epistemics @@ -116,6 +115,14 @@ - Librarian - Roboticist +- type: department + id: Silicon + description: department-Silicon-description + color: "#D381C9" + roles: + - Borg + - StationAi + - type: department id: Specific description: department-Specific-description diff --git a/Resources/Prototypes/Roles/play_time_trackers.yml b/Resources/Prototypes/Roles/play_time_trackers.yml index fd99b2c2288..9c87cde9794 100644 --- a/Resources/Prototypes/Roles/play_time_trackers.yml +++ b/Resources/Prototypes/Roles/play_time_trackers.yml @@ -151,6 +151,9 @@ - type: playTimeTracker id: JobServiceWorker +- type: playTimeTracker + id: JobStationAi + - type: playTimeTracker id: JobStationEngineer diff --git a/Resources/Prototypes/StatusIcon/job.yml b/Resources/Prototypes/StatusIcon/job.yml index 17c9cee8045..bcd8021c3ef 100644 --- a/Resources/Prototypes/StatusIcon/job.yml +++ b/Resources/Prototypes/StatusIcon/job.yml @@ -29,6 +29,14 @@ 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 diff --git a/Resources/Prototypes/Wires/layouts.yml b/Resources/Prototypes/Wires/layouts.yml index 9fd6bddc37f..20d5d336030 100644 --- a/Resources/Prototypes/Wires/layouts.yml +++ b/Resources/Prototypes/Wires/layouts.yml @@ -9,6 +9,7 @@ - !type:DoorBoltLightWireAction - !type:DoorTimingWireAction - !type:DoorSafetyWireAction + - !type:AiInteractWireAction - type: wireLayout parent: Airlock @@ -96,9 +97,10 @@ - type: wireLayout id: SurveillanceCamera - dummyWires: 4 + dummyWires: 2 wires: - !type:PowerWireAction + - !type:AiVisionWireAction - type: wireLayout id: CryoPod diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml index 8f64a139402..48358ca4964 100644 --- a/Resources/Prototypes/tags.yml +++ b/Resources/Prototypes/tags.yml @@ -1248,6 +1248,9 @@ - type: Tag id: StringInstrument +- type: Tag + id: StationAi + - type: Tag id: StationMapElectronics diff --git a/Resources/Textures/Interface/Actions/actions_ai.rsi/ai_core.png b/Resources/Textures/Interface/Actions/actions_ai.rsi/ai_core.png new file mode 100644 index 00000000000..8dd3031f9fc Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_ai.rsi/ai_core.png differ diff --git a/Resources/Textures/Interface/Actions/actions_ai.rsi/camera_light.png b/Resources/Textures/Interface/Actions/actions_ai.rsi/camera_light.png new file mode 100644 index 00000000000..041b9b9bf7c Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_ai.rsi/camera_light.png differ diff --git a/Resources/Textures/Interface/Actions/actions_ai.rsi/crew_monitor.png b/Resources/Textures/Interface/Actions/actions_ai.rsi/crew_monitor.png new file mode 100644 index 00000000000..78fad17a76c Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_ai.rsi/crew_monitor.png differ diff --git a/Resources/Textures/Interface/Actions/actions_ai.rsi/manifest.png b/Resources/Textures/Interface/Actions/actions_ai.rsi/manifest.png new file mode 100644 index 00000000000..08514aa9082 Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_ai.rsi/manifest.png differ diff --git a/Resources/Textures/Interface/Actions/actions_ai.rsi/meta.json b/Resources/Textures/Interface/Actions/actions_ai.rsi/meta.json new file mode 100644 index 00000000000..a7c00f77935 --- /dev/null +++ b/Resources/Textures/Interface/Actions/actions_ai.rsi/meta.json @@ -0,0 +1,26 @@ +{ + "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" + } + ] +} diff --git a/Resources/Textures/Interface/Actions/actions_ai.rsi/state_laws.png b/Resources/Textures/Interface/Actions/actions_ai.rsi/state_laws.png new file mode 100644 index 00000000000..e30e891745f Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_ai.rsi/state_laws.png differ diff --git a/Resources/Textures/Interface/Misc/job_icons.rsi/StationAi.png b/Resources/Textures/Interface/Misc/job_icons.rsi/StationAi.png new file mode 100644 index 00000000000..aba35c034b9 Binary files /dev/null and b/Resources/Textures/Interface/Misc/job_icons.rsi/StationAi.png differ diff --git a/Resources/Textures/Interface/Misc/job_icons.rsi/meta.json b/Resources/Textures/Interface/Misc/job_icons.rsi/meta.json index 745cc43b844..117a94e790d 100644 --- a/Resources/Textures/Interface/Misc/job_icons.rsi/meta.json +++ b/Resources/Textures/Interface/Misc/job_icons.rsi/meta.json @@ -1,7 +1,7 @@ { "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", + "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, @@ -177,6 +177,9 @@ [1.0,1.0] ] }, + { + "name": "StationAi" + }, { "name": "Syndicate" }, diff --git a/Resources/Textures/Interface/noise.rsi/meta.json b/Resources/Textures/Interface/noise.rsi/meta.json new file mode 100644 index 00000000000..068ecab968e --- /dev/null +++ b/Resources/Textures/Interface/noise.rsi/meta.json @@ -0,0 +1,58 @@ +{ + "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 diff --git a/Resources/Textures/Interface/noise.rsi/noise.png b/Resources/Textures/Interface/noise.rsi/noise.png new file mode 100644 index 00000000000..ba74952b409 Binary files /dev/null and b/Resources/Textures/Interface/noise.rsi/noise.png differ diff --git a/Resources/Textures/Mobs/Silicon/output.rsi/ai-banned-unshaded.png b/Resources/Textures/Mobs/Silicon/output.rsi/ai-banned-unshaded.png new file mode 100644 index 00000000000..9b72b394928 Binary files /dev/null and b/Resources/Textures/Mobs/Silicon/output.rsi/ai-banned-unshaded.png differ diff --git a/Resources/Textures/Mobs/Silicon/output.rsi/ai-banned.png b/Resources/Textures/Mobs/Silicon/output.rsi/ai-banned.png new file mode 100644 index 00000000000..c583bb88f21 Binary files /dev/null and b/Resources/Textures/Mobs/Silicon/output.rsi/ai-banned.png differ diff --git a/Resources/Textures/Mobs/Silicon/output.rsi/ai-banned_dead-unshaded.png b/Resources/Textures/Mobs/Silicon/output.rsi/ai-banned_dead-unshaded.png new file mode 100644 index 00000000000..28bf165122e Binary files /dev/null and b/Resources/Textures/Mobs/Silicon/output.rsi/ai-banned_dead-unshaded.png differ diff --git a/Resources/Textures/Mobs/Silicon/output.rsi/ai-banned_dead.png b/Resources/Textures/Mobs/Silicon/output.rsi/ai-banned_dead.png new file mode 100644 index 00000000000..7c5b468885a Binary files /dev/null and b/Resources/Textures/Mobs/Silicon/output.rsi/ai-banned_dead.png differ diff --git a/Resources/Textures/Mobs/Silicon/output.rsi/ai-empty-unshaded.png b/Resources/Textures/Mobs/Silicon/output.rsi/ai-empty-unshaded.png new file mode 100644 index 00000000000..6539176b848 Binary files /dev/null and b/Resources/Textures/Mobs/Silicon/output.rsi/ai-empty-unshaded.png differ diff --git a/Resources/Textures/Mobs/Silicon/output.rsi/ai-empty.png b/Resources/Textures/Mobs/Silicon/output.rsi/ai-empty.png new file mode 100644 index 00000000000..eda1f4bfb56 Binary files /dev/null and b/Resources/Textures/Mobs/Silicon/output.rsi/ai-empty.png differ diff --git a/Resources/Textures/Mobs/Silicon/output.rsi/ai-holo-old-unshaded.png b/Resources/Textures/Mobs/Silicon/output.rsi/ai-holo-old-unshaded.png new file mode 100644 index 00000000000..63616e70b52 Binary files /dev/null and b/Resources/Textures/Mobs/Silicon/output.rsi/ai-holo-old-unshaded.png differ diff --git a/Resources/Textures/Mobs/Silicon/output.rsi/ai-holo-old.png b/Resources/Textures/Mobs/Silicon/output.rsi/ai-holo-old.png new file mode 100644 index 00000000000..3dc7a301d86 Binary files /dev/null and b/Resources/Textures/Mobs/Silicon/output.rsi/ai-holo-old.png differ diff --git a/Resources/Textures/Mobs/Silicon/output.rsi/ai-unshaded.png b/Resources/Textures/Mobs/Silicon/output.rsi/ai-unshaded.png new file mode 100644 index 00000000000..f3ba4b591ab Binary files /dev/null and b/Resources/Textures/Mobs/Silicon/output.rsi/ai-unshaded.png differ diff --git a/Resources/Textures/Mobs/Silicon/output.rsi/ai.png b/Resources/Textures/Mobs/Silicon/output.rsi/ai.png new file mode 100644 index 00000000000..3c81e3a751e Binary files /dev/null and b/Resources/Textures/Mobs/Silicon/output.rsi/ai.png differ diff --git a/Resources/Textures/Mobs/Silicon/output.rsi/ai_dead-unshaded.png b/Resources/Textures/Mobs/Silicon/output.rsi/ai_dead-unshaded.png new file mode 100644 index 00000000000..96e953660f8 Binary files /dev/null and b/Resources/Textures/Mobs/Silicon/output.rsi/ai_dead-unshaded.png differ diff --git a/Resources/Textures/Mobs/Silicon/output.rsi/ai_dead.png b/Resources/Textures/Mobs/Silicon/output.rsi/ai_dead.png new file mode 100644 index 00000000000..96e953660f8 Binary files /dev/null and b/Resources/Textures/Mobs/Silicon/output.rsi/ai_dead.png differ diff --git a/Resources/Textures/Mobs/Silicon/output.rsi/default-unshaded.png b/Resources/Textures/Mobs/Silicon/output.rsi/default-unshaded.png new file mode 100644 index 00000000000..f14b4ef0fa6 Binary files /dev/null and b/Resources/Textures/Mobs/Silicon/output.rsi/default-unshaded.png differ diff --git a/Resources/Textures/Mobs/Silicon/output.rsi/default.png b/Resources/Textures/Mobs/Silicon/output.rsi/default.png new file mode 100644 index 00000000000..f14b4ef0fa6 Binary files /dev/null and b/Resources/Textures/Mobs/Silicon/output.rsi/default.png differ diff --git a/Resources/Textures/Mobs/Silicon/output.rsi/floating_face-unshaded.png b/Resources/Textures/Mobs/Silicon/output.rsi/floating_face-unshaded.png new file mode 100644 index 00000000000..05de7427943 Binary files /dev/null and b/Resources/Textures/Mobs/Silicon/output.rsi/floating_face-unshaded.png differ diff --git a/Resources/Textures/Mobs/Silicon/output.rsi/floating_face.png b/Resources/Textures/Mobs/Silicon/output.rsi/floating_face.png new file mode 100644 index 00000000000..05de7427943 Binary files /dev/null and b/Resources/Textures/Mobs/Silicon/output.rsi/floating_face.png differ diff --git a/Resources/Textures/Mobs/Silicon/output.rsi/horror-unshaded.png b/Resources/Textures/Mobs/Silicon/output.rsi/horror-unshaded.png new file mode 100644 index 00000000000..10efd5ee1db Binary files /dev/null and b/Resources/Textures/Mobs/Silicon/output.rsi/horror-unshaded.png differ diff --git a/Resources/Textures/Mobs/Silicon/output.rsi/horror.png b/Resources/Textures/Mobs/Silicon/output.rsi/horror.png new file mode 100644 index 00000000000..0a807c0c235 Binary files /dev/null and b/Resources/Textures/Mobs/Silicon/output.rsi/horror.png differ diff --git a/Resources/Textures/Mobs/Silicon/output.rsi/meta.json b/Resources/Textures/Mobs/Silicon/output.rsi/meta.json new file mode 100644 index 00000000000..a40ed37c604 --- /dev/null +++ b/Resources/Textures/Mobs/Silicon/output.rsi/meta.json @@ -0,0 +1 @@ +{"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 diff --git a/Resources/Textures/Mobs/Silicon/output.rsi/xeno_queen-unshaded.png b/Resources/Textures/Mobs/Silicon/output.rsi/xeno_queen-unshaded.png new file mode 100644 index 00000000000..3ea194039f4 Binary files /dev/null and b/Resources/Textures/Mobs/Silicon/output.rsi/xeno_queen-unshaded.png differ diff --git a/Resources/Textures/Mobs/Silicon/output.rsi/xeno_queen.png b/Resources/Textures/Mobs/Silicon/output.rsi/xeno_queen.png new file mode 100644 index 00000000000..3ea194039f4 Binary files /dev/null and b/Resources/Textures/Mobs/Silicon/output.rsi/xeno_queen.png differ diff --git a/Resources/Textures/Mobs/Silicon/station_ai.rsi/ai.png b/Resources/Textures/Mobs/Silicon/station_ai.rsi/ai.png new file mode 100644 index 00000000000..420a07c1f96 Binary files /dev/null and b/Resources/Textures/Mobs/Silicon/station_ai.rsi/ai.png differ diff --git a/Resources/Textures/Mobs/Silicon/station_ai.rsi/ai_dead.png b/Resources/Textures/Mobs/Silicon/station_ai.rsi/ai_dead.png new file mode 100644 index 00000000000..eb74655e027 Binary files /dev/null and b/Resources/Textures/Mobs/Silicon/station_ai.rsi/ai_dead.png differ diff --git a/Resources/Textures/Mobs/Silicon/station_ai.rsi/ai_empty.png b/Resources/Textures/Mobs/Silicon/station_ai.rsi/ai_empty.png new file mode 100644 index 00000000000..40e8ac52161 Binary files /dev/null and b/Resources/Textures/Mobs/Silicon/station_ai.rsi/ai_empty.png differ diff --git a/Resources/Textures/Mobs/Silicon/station_ai.rsi/base.png b/Resources/Textures/Mobs/Silicon/station_ai.rsi/base.png new file mode 100644 index 00000000000..a9db83691c1 Binary files /dev/null and b/Resources/Textures/Mobs/Silicon/station_ai.rsi/base.png differ diff --git a/Resources/Textures/Mobs/Silicon/station_ai.rsi/default.png b/Resources/Textures/Mobs/Silicon/station_ai.rsi/default.png new file mode 100644 index 00000000000..d52aceaf5b1 Binary files /dev/null and b/Resources/Textures/Mobs/Silicon/station_ai.rsi/default.png differ diff --git a/Resources/Textures/Mobs/Silicon/station_ai.rsi/meta.json b/Resources/Textures/Mobs/Silicon/station_ai.rsi/meta.json new file mode 100644 index 00000000000..a3da52233dd --- /dev/null +++ b/Resources/Textures/Mobs/Silicon/station_ai.rsi/meta.json @@ -0,0 +1,52 @@ +{ + "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 diff --git a/Resources/Textures/Objects/Devices/ai_card.rsi/base.png b/Resources/Textures/Objects/Devices/ai_card.rsi/base.png new file mode 100644 index 00000000000..244183c078c Binary files /dev/null and b/Resources/Textures/Objects/Devices/ai_card.rsi/base.png differ diff --git a/Resources/Textures/Objects/Devices/ai_card.rsi/empty.png b/Resources/Textures/Objects/Devices/ai_card.rsi/empty.png new file mode 100644 index 00000000000..7e61f368de2 Binary files /dev/null and b/Resources/Textures/Objects/Devices/ai_card.rsi/empty.png differ diff --git a/Resources/Textures/Objects/Devices/ai_card.rsi/full.png b/Resources/Textures/Objects/Devices/ai_card.rsi/full.png new file mode 100644 index 00000000000..59131c8c0aa Binary files /dev/null and b/Resources/Textures/Objects/Devices/ai_card.rsi/full.png differ diff --git a/Resources/Textures/Objects/Devices/ai_card.rsi/inhand-left.png b/Resources/Textures/Objects/Devices/ai_card.rsi/inhand-left.png new file mode 100644 index 00000000000..2d3863145b9 Binary files /dev/null and b/Resources/Textures/Objects/Devices/ai_card.rsi/inhand-left.png differ diff --git a/Resources/Textures/Objects/Devices/ai_card.rsi/inhand-right.png b/Resources/Textures/Objects/Devices/ai_card.rsi/inhand-right.png new file mode 100644 index 00000000000..1704b9c3c11 Binary files /dev/null and b/Resources/Textures/Objects/Devices/ai_card.rsi/inhand-right.png differ diff --git a/Resources/Textures/Objects/Devices/ai_card.rsi/meta.json b/Resources/Textures/Objects/Devices/ai_card.rsi/meta.json new file mode 100644 index 00000000000..8b8135fa16e --- /dev/null +++ b/Resources/Textures/Objects/Devices/ai_card.rsi/meta.json @@ -0,0 +1,58 @@ +{ + "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 diff --git a/Resources/Textures/Objects/Devices/output.rsi/aicard-full-unshaded.png b/Resources/Textures/Objects/Devices/output.rsi/aicard-full-unshaded.png new file mode 100644 index 00000000000..51a309d579e Binary files /dev/null and b/Resources/Textures/Objects/Devices/output.rsi/aicard-full-unshaded.png differ diff --git a/Resources/Textures/Objects/Devices/output.rsi/aicard-full.png b/Resources/Textures/Objects/Devices/output.rsi/aicard-full.png new file mode 100644 index 00000000000..03908b5284f Binary files /dev/null and b/Resources/Textures/Objects/Devices/output.rsi/aicard-full.png differ diff --git a/Resources/Textures/Objects/Devices/output.rsi/aicard-unshaded.png b/Resources/Textures/Objects/Devices/output.rsi/aicard-unshaded.png new file mode 100644 index 00000000000..6191a01ec4e Binary files /dev/null and b/Resources/Textures/Objects/Devices/output.rsi/aicard-unshaded.png differ diff --git a/Resources/Textures/Objects/Devices/output.rsi/aicard.png b/Resources/Textures/Objects/Devices/output.rsi/aicard.png new file mode 100644 index 00000000000..57f604efb0e Binary files /dev/null and b/Resources/Textures/Objects/Devices/output.rsi/aicard.png differ diff --git a/Resources/Textures/Objects/Devices/output.rsi/meta.json b/Resources/Textures/Objects/Devices/output.rsi/meta.json new file mode 100644 index 00000000000..500ecb8e3e7 --- /dev/null +++ b/Resources/Textures/Objects/Devices/output.rsi/meta.json @@ -0,0 +1 @@ +{"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