Skip to content

Commit

Permalink
Station AI (#1423)
Browse files Browse the repository at this point in the history
<!--
This is a semi-strict format, you can add/remove sections as needed but
the order/format should be kept the same
Remove these comments before submitting
-->

# Description

<!--
Explain this PR in as much detail as applicable

Some example prompts to consider:
How might this affect the game? The codebase?
What might be some alternatives to this?
How/Who does this benefit/hurt [the game/codebase]?
-->

Implements station AI and all relevant fixes/changes/features.

---

# Changelog

<!--
You can add an author after the `:cl:` to change the name that appears
in the changelog (ex: `:cl: Death`)
Leaving it blank will default to your GitHub display name
This includes all available types for the changelog
-->

:cl:
- add: Ported Station AI.
- add: Saltern is now fitted with a Station AI
  • Loading branch information
VMSolidus authored Jan 11, 2025
2 parents f0a9bbd + 5242ceb commit 6903924
Show file tree
Hide file tree
Showing 216 changed files with 4,404 additions and 1,735 deletions.
8 changes: 0 additions & 8 deletions Content.Client/Bed/SleepingSystem.cs

This file was deleted.

13 changes: 5 additions & 8 deletions Content.Client/Chat/UI/EmotesMenu.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,15 @@ 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<ProtoId<EmotePrototype>>? OnPlayEmote;

public EmotesMenu()
{
IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this);

_spriteSystem = _entManager.System<SpriteSystem>();
_whitelistSystem = _entManager.System<EntityWhitelistSystem>();
var spriteSystem = _entManager.System<SpriteSystem>();
var whitelistSystem = _entManager.System<EntityWhitelistSystem>();

var main = FindControl<RadialContainer>("Main");

Expand All @@ -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 &&
Expand All @@ -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),
};

Expand Down
1 change: 1 addition & 0 deletions Content.Client/Commands/SetMenuVisibilityCommand.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Content.Client.Verbs;
using Content.Shared.Verbs;
using JetBrains.Annotations;
using Robust.Shared.Console;

Expand Down
19 changes: 16 additions & 3 deletions Content.Client/ContextMenu/UI/EntityMenuUIController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<TransformComponent>();
Expand All @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion Content.Client/Interaction/DragDropSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ private void InitializeBlockers()
SubscribeLocalEvent<ReplaySpectatorComponent, UseAttemptEvent>(OnAttempt);
SubscribeLocalEvent<ReplaySpectatorComponent, PickupAttemptEvent>(OnAttempt);
SubscribeLocalEvent<ReplaySpectatorComponent, ThrowAttemptEvent>(OnAttempt);
SubscribeLocalEvent<ReplaySpectatorComponent, InteractionAttemptEvent>(OnAttempt);
SubscribeLocalEvent<ReplaySpectatorComponent, InteractionAttemptEvent>(OnInteractAttempt);
SubscribeLocalEvent<ReplaySpectatorComponent, AttackAttemptEvent>(OnAttempt);
SubscribeLocalEvent<ReplaySpectatorComponent, DropAttemptEvent>(OnAttempt);
SubscribeLocalEvent<ReplaySpectatorComponent, IsEquippingAttemptEvent>(OnAttempt);
Expand All @@ -27,6 +27,11 @@ private void InitializeBlockers()
SubscribeLocalEvent<ReplaySpectatorComponent, PullAttemptEvent>(OnPullAttempt);
}

private void OnInteractAttempt(Entity<ReplaySpectatorComponent> ent, ref InteractionAttemptEvent args)
{
args.Cancelled = true;
}

private void OnAttempt(EntityUid uid, ReplaySpectatorComponent component, CancellableEntityEventArgs args)
{
args.Cancel();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
28 changes: 28 additions & 0 deletions Content.Client/Silicons/StationAi/StationAiBoundUserInterface.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using Content.Shared.Silicons.StationAi;
using Robust.Client.UserInterface;

namespace Content.Client.Silicons.StationAi;

public sealed class StationAiBoundUserInterface : BoundUserInterface
{
private StationAiMenu? _menu;

public StationAiBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
}

protected override void Open()
{
base.Open();
_menu = this.CreateWindow<StationAiMenu>();
_menu.Track(Owner);

_menu.OnAiRadial += args =>
{
SendPredictedMessage(new StationAiRadialMessage()
{
Event = args,
});
};
}
}
13 changes: 13 additions & 0 deletions Content.Client/Silicons/StationAi/StationAiMenu.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<ui:RadialMenu xmlns="https://spacestation14.io"
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
BackButtonStyleClass="RadialMenuBackButton"
CloseButtonStyleClass="RadialMenuCloseButton"
VerticalExpand="True"
HorizontalExpand="True"
MinSize="450 450">

<!-- Main -->
<ui:RadialContainer Name="Main" VerticalExpand="True" HorizontalExpand="True" Radius="64" ReserveSpaceForHiddenChildren="False">
</ui:RadialContainer>

</ui:RadialMenu>
128 changes: 128 additions & 0 deletions Content.Client/Silicons/StationAi/StationAiMenu.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
using System.Numerics;
using Content.Client.UserInterface.Controls;
using Content.Shared.Silicons.StationAi;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Timing;

namespace Content.Client.Silicons.StationAi;

[GenerateTypedNameReferences]
public sealed partial class StationAiMenu : RadialMenu
{
[Dependency] private readonly IClyde _clyde = default!;
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;

public event Action<BaseStationAiAction>? OnAiRadial;

private EntityUid _tracked;

public StationAiMenu()
{
IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this);
}

public void Track(EntityUid owner)
{
_tracked = owner;

if (!_entManager.EntityExists(_tracked))
{
Close();
return;
}

BuildButtons();
UpdatePosition();
}

private void BuildButtons()
{
var ev = new GetStationAiRadialEvent();
_entManager.EventBus.RaiseLocalEvent(_tracked, ref ev);

var main = FindControl<RadialContainer>("Main");
main.DisposeAllChildren();
var sprites = _entManager.System<SpriteSystem>();

foreach (var action in ev.Actions)
{
// TODO: This radial boilerplate is quite annoying
var button = new StationAiMenuButton(action.Event)
{
StyleClasses = { "RadialMenuButton" },
SetSize = new Vector2(64f, 64f),
ToolTip = action.Tooltip != null ? Loc.GetString(action.Tooltip) : null,
};

if (action.Sprite != null)
{
var texture = sprites.Frame0(action.Sprite);
var scale = Vector2.One;

if (texture.Width <= 32)
{
scale *= 2;
}

var tex = new TextureRect
{
VerticalAlignment = VAlignment.Center,
HorizontalAlignment = HAlignment.Center,
Texture = texture,
TextureScale = scale,
};

button.AddChild(tex);
}

button.OnPressed += args =>
{
OnAiRadial?.Invoke(action.Event);
Close();
};
main.AddChild(button);
}
}

protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
UpdatePosition();
}

private void UpdatePosition()
{
if (!_entManager.TryGetComponent(_tracked, out TransformComponent? xform))
{
Close();
return;
}

if (!xform.Coordinates.IsValid(_entManager))
{
Close();
return;
}

var coords = _entManager.System<SpriteSystem>().GetSpriteScreenCoordinates((_tracked, null, xform));

if (!coords.IsValid)
{
Close();
return;
}

OpenScreenAt(coords.Position, _clyde);
}
}

public sealed class StationAiMenuButton(BaseStationAiAction action) : RadialMenuTextureButton
{
public BaseStationAiAction Action = action;
}
23 changes: 16 additions & 7 deletions Content.Client/Silicons/StationAi/StationAiOverlay.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@
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;

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!;

Expand All @@ -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);
Expand All @@ -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<EntityLookupSystem>();
var xforms = _entManager.System<SharedTransformSystem>();
_entManager.System<StationAiVisionSystem>().GetView((gridUid, grid), worldBounds, _visibleTiles);

if (_accumulator <= 0f)
{
_accumulator = MathF.Max(0f, _accumulator + _updateRate);
_visibleTiles.Clear();
_entManager.System<StationAiVisionSystem>().GetView((gridUid, broadphase, grid), worldBounds, _visibleTiles);
}

var gridMatrix = xforms.GetWorldMatrix(gridUid);
var matty = Matrix3x2.Multiply(gridMatrix, invMatrix);
Expand Down
30 changes: 30 additions & 0 deletions Content.Client/Silicons/StationAi/StationAiSystem.Airlock.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using Content.Shared.Doors.Components;
using Content.Shared.Silicons.StationAi;
using Robust.Shared.Utility;

namespace Content.Client.Silicons.StationAi;

public sealed partial class StationAiSystem
{
private void InitializeAirlock()
{
SubscribeLocalEvent<DoorBoltComponent, GetStationAiRadialEvent>(OnDoorBoltGetRadial);
}

private void OnDoorBoltGetRadial(Entity<DoorBoltComponent> ent, ref GetStationAiRadialEvent args)
{
args.Actions.Add(new StationAiRadial()
{
Sprite = ent.Comp.BoltsDown ?
new SpriteSpecifier.Rsi(
new ResPath("/Textures/Structures/Doors/Airlocks/Standard/basic.rsi"), "open") :
new SpriteSpecifier.Rsi(
new ResPath("/Textures/Structures/Doors/Airlocks/Standard/basic.rsi"), "closed"),
Tooltip = ent.Comp.BoltsDown ? Loc.GetString("bolt-open") : Loc.GetString("bolt-close"),
Event = new StationAiBoltEvent()
{
Bolted = !ent.Comp.BoltsDown,
}
});
}
}
Loading

0 comments on commit 6903924

Please sign in to comment.