Skip to content

Commit

Permalink
Station AI Features and Fixes (Also General Fixes) (#1525)
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]?
-->

Check the changelog for the full list.

---

# 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: Added Holopads (unmapped)
- add: Intellicards are now useful for removing/adding a Station AI's
brain.
- add: Added the Communications Console to Station AI actions.
- add: AI now has a warp point.
- add: Added more things for the AI to press.
- add: More AI laws have been added.
- fix: Fixed the mail system
- fix: Fixed AI actions
- fix: Fixed invalid spawns for station AI breaking and ruining your
ability to play it.
- fix: The Station AI's name will now properly send in "arrived to the
station" announcements.
- fix: Changed the CPR sound to simply not loop until fixed.
- fix: Fixed unlocalized messages being sent for the random sentience
event.

---------

Co-authored-by: metalgearsloth <[email protected]>
Co-authored-by: ScarKy0 <[email protected]>
Co-authored-by: Zachary Higgs <[email protected]>
Co-authored-by: MendaxxDev <[email protected]>
Co-authored-by: chromiumboy <[email protected]>
Co-authored-by: slarticodefast <[email protected]>
  • Loading branch information
7 people authored Jan 14, 2025
1 parent 8b74710 commit 3e8a7d9
Show file tree
Hide file tree
Showing 123 changed files with 4,958 additions and 876 deletions.
14 changes: 12 additions & 2 deletions Content.Client/Chat/UI/SpeechBubble.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
using Content.Client.Chat.Managers;
using Content.Shared.CCVar;
using Content.Shared.Chat;
using Content.Shared.Speech;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
Expand All @@ -17,6 +19,8 @@ public abstract class SpeechBubble : Control
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] protected readonly IConfigurationManager ConfigManager = default!;

private readonly SharedTransformSystem _transformSystem;

public enum SpeechType : byte
{
Emote,
Expand Down Expand Up @@ -83,6 +87,7 @@ public SpeechBubble(ChatMessage message, EntityUid senderEntity, string speechSt
{
IoCManager.InjectDependencies(this);
_senderEntity = senderEntity;
_transformSystem = _entityManager.System<SharedTransformSystem>();

// Use text clipping so new messages don't overlap old ones being pushed up.
RectClipContent = true;
Expand Down Expand Up @@ -139,8 +144,13 @@ protected override void FrameUpdate(FrameEventArgs args)
Modulate = Color.White;
}

var offset = (-_eyeManager.CurrentEye.Rotation).ToWorldVec() * -EntityVerticalOffset;
var worldPos = xform.WorldPosition + offset;
var baseOffset = 0f;

if (_entityManager.TryGetComponent<SpeechComponent>(_senderEntity, out var speech))
baseOffset = speech.SpeechBubbleOffset;

var offset = (-_eyeManager.CurrentEye.Rotation).ToWorldVec() * -(EntityVerticalOffset + baseOffset);
var worldPos = _transformSystem.GetWorldPosition(xform) + offset;

var lowerCenter = _eyeManager.WorldToScreen(worldPos) / UIScale;
var screenPos = lowerCenter - new Vector2(ContentSize.X / 2, ContentSize.Y + _verticalOffsetAchieved);
Expand Down
2 changes: 1 addition & 1 deletion Content.Client/DeltaV/Hologram/HologramSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public override void Initialize()
{
base.Initialize();

_shader = _protoMan.Index<ShaderPrototype>("Hologram").InstanceUnique();
_shader = _protoMan.Index<ShaderPrototype>("HologramDeltaV").InstanceUnique();
SubscribeLocalEvent<HologramComponent, ComponentShutdown>(OnShutdown);
SubscribeLocalEvent<HologramComponent, ComponentStartup>(OnStartup);
}
Expand Down
101 changes: 101 additions & 0 deletions Content.Client/Holopad/HolopadBoundUserInterface.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
using Content.Shared.Holopad;
using Content.Shared.Silicons.StationAi;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Shared.Player;
using System.Numerics;

namespace Content.Client.Holopad;

public sealed class HolopadBoundUserInterface : BoundUserInterface
{
[Dependency] private readonly ISharedPlayerManager _playerManager = default!;
[Dependency] private readonly IClyde _displayManager = default!;

[ViewVariables]
private HolopadWindow? _window;

public HolopadBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
IoCManager.InjectDependencies(this);
}

protected override void Open()
{
base.Open();

_window = this.CreateWindow<HolopadWindow>();
_window.Title = Loc.GetString("holopad-window-title", ("title", EntMan.GetComponent<MetaDataComponent>(Owner).EntityName));

if (this.UiKey is not HolopadUiKey)
{
Close();
return;
}

var uiKey = (HolopadUiKey)this.UiKey;

// AIs will see a different holopad interface to crew when interacting with them in the world
if (uiKey == HolopadUiKey.InteractionWindow && EntMan.HasComponent<StationAiHeldComponent>(_playerManager.LocalEntity))
uiKey = HolopadUiKey.InteractionWindowForAi;

_window.SetState(Owner, uiKey);
_window.UpdateState(new Dictionary<NetEntity, string>());

// Set message actions
_window.SendHolopadStartNewCallMessageAction += SendHolopadStartNewCallMessage;
_window.SendHolopadAnswerCallMessageAction += SendHolopadAnswerCallMessage;
_window.SendHolopadEndCallMessageAction += SendHolopadEndCallMessage;
_window.SendHolopadStartBroadcastMessageAction += SendHolopadStartBroadcastMessage;
_window.SendHolopadActivateProjectorMessageAction += SendHolopadActivateProjectorMessage;
_window.SendHolopadRequestStationAiMessageAction += SendHolopadRequestStationAiMessage;

// If this call is addressed to an AI, open the window in the bottom right hand corner of the screen
if (uiKey == HolopadUiKey.AiRequestWindow)
_window.OpenCenteredAt(new Vector2(1f, 1f));

// Otherwise offset to the left so the holopad can still be seen
else
_window.OpenCenteredAt(new Vector2(0.3333f, 0.50f));
}

protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);

var castState = (HolopadBoundInterfaceState)state;
EntMan.TryGetComponent<TransformComponent>(Owner, out var xform);

_window?.UpdateState(castState.Holopads);
}

public void SendHolopadStartNewCallMessage(NetEntity receiver)
{
SendMessage(new HolopadStartNewCallMessage(receiver));
}

public void SendHolopadAnswerCallMessage()
{
SendMessage(new HolopadAnswerCallMessage());
}

public void SendHolopadEndCallMessage()
{
SendMessage(new HolopadEndCallMessage());
}

public void SendHolopadStartBroadcastMessage()
{
SendMessage(new HolopadStartBroadcastMessage());
}

public void SendHolopadActivateProjectorMessage()
{
SendMessage(new HolopadActivateProjectorMessage());
}

public void SendHolopadRequestStationAiMessage()
{
SendMessage(new HolopadStationAiRequestMessage());
}
}
172 changes: 172 additions & 0 deletions Content.Client/Holopad/HolopadSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
using Content.Shared.Chat.TypingIndicator;
using Content.Shared.Holopad;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using System.Linq;

namespace Content.Client.Holopad;

public sealed class HolopadSystem : SharedHolopadSystem
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IGameTiming _timing = default!;

public override void Initialize()
{
base.Initialize();

SubscribeLocalEvent<HolopadHologramComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<HolopadHologramComponent, BeforePostShaderRenderEvent>(OnShaderRender);
SubscribeAllEvent<TypingChangedEvent>(OnTypingChanged);

SubscribeNetworkEvent<PlayerSpriteStateRequest>(OnPlayerSpriteStateRequest);
SubscribeNetworkEvent<PlayerSpriteStateMessage>(OnPlayerSpriteStateMessage);
}

private void OnComponentInit(EntityUid uid, HolopadHologramComponent component, ComponentInit ev)
{
if (!TryComp<SpriteComponent>(uid, out var sprite))
return;

UpdateHologramSprite(uid);
}

private void OnShaderRender(EntityUid uid, HolopadHologramComponent component, BeforePostShaderRenderEvent ev)
{
if (ev.Sprite.PostShader == null)
return;

ev.Sprite.PostShader.SetParameter("t", (float)_timing.CurTime.TotalSeconds * component.ScrollRate);
}

private void OnTypingChanged(TypingChangedEvent ev, EntitySessionEventArgs args)
{
var uid = args.SenderSession.AttachedEntity;

if (!Exists(uid))
return;

if (!HasComp<HolopadUserComponent>(uid))
return;

var netEv = new HolopadUserTypingChangedEvent(GetNetEntity(uid.Value), ev.IsTyping);
RaiseNetworkEvent(netEv);
}

private void OnPlayerSpriteStateRequest(PlayerSpriteStateRequest ev)
{
var targetPlayer = GetEntity(ev.TargetPlayer);
var player = _playerManager.LocalSession?.AttachedEntity;

// Ignore the request if received by a player who isn't the target
if (targetPlayer != player)
return;

if (!TryComp<SpriteComponent>(player, out var playerSprite))
return;

var spriteLayerData = new List<PrototypeLayerData>();

if (playerSprite.Visible)
{
// Record the RSI paths, state names and shader paramaters of all visible layers
for (int i = 0; i < playerSprite.AllLayers.Count(); i++)
{
if (!playerSprite.TryGetLayer(i, out var layer))
continue;

if (!layer.Visible ||
string.IsNullOrEmpty(layer.ActualRsi?.Path.ToString()) ||
string.IsNullOrEmpty(layer.State.Name))
continue;

var layerDatum = new PrototypeLayerData();
layerDatum.RsiPath = layer.ActualRsi.Path.ToString();
layerDatum.State = layer.State.Name;

if (layer.CopyToShaderParameters != null)
{
var key = (string)layer.CopyToShaderParameters.LayerKey;

if (playerSprite.LayerMapTryGet(key, out var otherLayerIdx) &&
playerSprite.TryGetLayer(otherLayerIdx, out var otherLayer) &&
otherLayer.Visible)
{
layerDatum.MapKeys = new() { key };

layerDatum.CopyToShaderParameters = new PrototypeCopyToShaderParameters()
{
LayerKey = key,
ParameterTexture = layer.CopyToShaderParameters.ParameterTexture,
ParameterUV = layer.CopyToShaderParameters.ParameterUV
};
}
}

spriteLayerData.Add(layerDatum);
}
}

// Return the recorded data to the server
var evResponse = new PlayerSpriteStateMessage(ev.TargetPlayer, spriteLayerData.ToArray());
RaiseNetworkEvent(evResponse);
}

private void OnPlayerSpriteStateMessage(PlayerSpriteStateMessage ev)
{
UpdateHologramSprite(GetEntity(ev.SpriteEntity), ev.SpriteLayerData);
}

private void UpdateHologramSprite(EntityUid uid, PrototypeLayerData[]? layerData = null)
{
if (!TryComp<SpriteComponent>(uid, out var hologramSprite))
return;

if (!TryComp<HolopadHologramComponent>(uid, out var holopadhologram))
return;

for (int i = hologramSprite.AllLayers.Count() - 1; i >= 0; i--)
hologramSprite.RemoveLayer(i);

if (layerData == null || layerData.Length == 0)
{
layerData = new PrototypeLayerData[1];
layerData[0] = new PrototypeLayerData()
{
RsiPath = holopadhologram.RsiPath,
State = holopadhologram.RsiState
};
}

for (int i = 0; i < layerData.Length; i++)
{
var layer = layerData[i];
layer.Shader = "unshaded";

hologramSprite.AddLayer(layerData[i], i);
}

UpdateHologramShader(uid, hologramSprite, holopadhologram);
}

private void UpdateHologramShader(EntityUid uid, SpriteComponent sprite, HolopadHologramComponent holopadHologram)
{
// Find the texture height of the largest layer
float texHeight = sprite.AllLayers.Max(x => x.PixelSize.Y);

var instance = _prototypeManager.Index<ShaderPrototype>(holopadHologram.ShaderName).InstanceUnique();
instance.SetParameter("color1", new Vector3(holopadHologram.Color1.R, holopadHologram.Color1.G, holopadHologram.Color1.B));
instance.SetParameter("color2", new Vector3(holopadHologram.Color2.R, holopadHologram.Color2.G, holopadHologram.Color2.B));
instance.SetParameter("alpha", holopadHologram.Alpha);
instance.SetParameter("intensity", holopadHologram.Intensity);
instance.SetParameter("texHeight", texHeight);
instance.SetParameter("t", (float)_timing.CurTime.TotalSeconds * holopadHologram.ScrollRate);

sprite.PostShader = instance;
sprite.RaiseShaderEvent = true;
}
}
Loading

0 comments on commit 3e8a7d9

Please sign in to comment.